#coding: utf-8 =begin 字段映射,业务字段-数据库字段对应 TODO: 不需做字段转换的处理 =end require 'fileutils' module Ns module FieldMapping def self.included(base) base.extend(ClassMethods) end #在将model转成json数据时会自动调用该方法 def as_json(options={}) #获取数据库属性数据 #TODO 想到的一个改进点:数据库查的时候根据mapping来选字段,而非全查出来,影响效率,影响较大,先不改! default_attrs = self.class.buz_hashize(self.attributes) #支持自定义方法的传入 20111118 for gao add_virtual_buz_attributes!(default_attrs) end # 将对象转成json数据时,允许添加额外的属性字段,但并不存入数据库 def add_virtual_buz_attributes!(db_buz_attrs = {}) the_mapping = self.class.mapping #获取非数据库属性,称为业务虚拟属性 delta_attrs = the_mapping.keys - db_buz_attrs.keys delta_attrs.each do |key| method_name = the_mapping[key] db_buz_attrs[key] = if self.respond_to?(method_name) self.send(method_name) else self.class.get_map_value(key, nil)#给出默认实现 end end db_buz_attrs end def update_from_buz(params={}) update_attributes(self.class.db_hashize(params)) end module ClassMethods #Fix a bug on 20111117 by shang, this override the original method name def __model_clazz_name self.name.underscore end def model_key "#{__model_clazz_name}_mapping" end def mapping GlobalConst.send(model_key) end def get_map_value(k, value) #raise "Not implemented!" #提供默认处理 #TODO 根据数据类型做些更好的转换 case k when :created_at, :updated_at value.try(:to_s, :db) else value end end def dump_new(force=true) if defined?(Rails) file = "#{Rails.root}/data/#{model_key}.yml.#{Time.now.to_i}" path = File.dirname(file) FileUtils.mkpath(path) unless File.directory?(path) existed = File.exists?(file) if existed puts "Warning: #{file} has existed! check it! Default override it!" end if !existed || force File.open(file, 'w+') do |f| f.puts YAML.dump(buz_hashize) end end end end #辅助方法 def new_from_buz(params = {}) new(db_hashize(params)) end def create_from_buz(params = {}) create(db_hashize(params)) end #生成field mapping,dump到指定的yaml文件中 def dump_mapping(attr_prefix = nil, force = true, with_timestamp = false) file = nil if defined?(Rails) tstamp = with_timestamp ? ".#{Time.now.to_i}" : "" file = "#{Rails.root}/#{GlobalConst::APP_CODE_HASHES}mappings/#{model_key}.yml#{tstamp}" path = File.dirname(file) FileUtils.mkpath(path) unless File.directory?(path) existed = File.exists?(file) if existed puts "Warning: #{file} has existed! check it! Default override it!" end if !existed || force #获取数据库字段列表,屏蔽掉自动维护的created_at/updated_at,也可在生成的文件中配上 keys = new.attributes.symbolize_keys.keys.delete_if{|k| k =~ /^created_at|updated_at$/} h = keys.inject({}) do |r, k| #移除掉表前缀 rm_prefix = attr_prefix.nil? ? "#{__model_clazz_name}_" : attr_prefix new_key = k.to_s.sub(rm_prefix, '').to_sym r[new_key] = k r end FileUtils.rm_f(file) if existed File.open(file, 'w+') do |f| f.puts YAML.dump(model_key.to_sym=>h) end end file end end #TODO def dump_field_map(force=true) puts "==Warning: please use dump_mapping instead, this will be removed..." dump_mapping(nil, true) end #业务层到数据层字段参数转换并做一定值处理 def db_hashize(params = {}) new_params = params.symbolize_keys common_keys = new_params.keys & mapping.keys common_keys.inject({}) do |result, key| result[mapping[key]] = get_map_value(key, new_params[key]) result end end #将数据层表示转成业务层表示 def buz_hashize(attrs = {}) #return {} if attrs.blank? #获取默认初始值 attrs = new.attributes if attrs.blank? new_attrs = attrs.symbolize_keys invert_maps = mapping.invert_data common_db_attrs = new_attrs.keys & invert_maps.keys common_db_attrs.inject({}) do |result, attr| buz_field = invert_maps[attr] result[buz_field] = get_map_value(buz_field, new_attrs[attr]) result end end end end end