#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 #获取对象的某个属性的业务值 def buz_value(buz_attr = nil) result = nil if buz_attr db_key = self.class.mapping[buz_attr.to_sym] if db_key db_value = send(db_key) #TODO BETTER result = self.class.buz_hashize(db_key=>db_value)[buz_attr.to_sym] end end result end def erp_admin?(params = {}) self.class.erp_admin?(params) end module ClassMethods def _find_resource(params = {}) obj = find(params[:id]) if mapping.keys.include?(:status) && !erp_admin?(params) buz_value = get_map_value(:status, obj.send(mapping[:status])) if buz_value == '作废' raise "资源#{name}##{obj.object_id}已经作废不可用!" end end obj end #convert buz array to db array def dbize_array(buz_array = []) result = nil if buz_array && buz_array.is_a?(Array) result = buz_array.map do |e| db_key = mapping[e.to_sym] rescue nil end.compact end result end def select_fields(array = []) db_keys = dbize_array(array) || mapping.values #FIXME 添加是否是数据属性的判断 better attrs = new.attributes.keys.map(&:to_sym) db_keys & attrs rescue nil end #分页方法 # 加入文档说明 def paginate(params = {}) current_page = params[:current_page].to_i current_page = current_page < 1 ? 1 : current_page page_size = params[:page_size].to_i page_size = page_size <= 0 ? 10 : page_size #increment the limit page_size = page_size > 5000 ? 5000 : page_size offset = (current_page-1)*page_size #返回总数和当前页数据 起始位置, 数量 total_count, page_data = yield(offset, page_size) { :total_size=>total_count, :page_size=>page_size, :current_page=>current_page, :page_from=>offset + 1, :page_to=>offset + page_data.size, :page_items=>page_data } end #将外界条件转化为一个数组 def arrayify_conds(params = {}) #清除空条件 params.delete_if{|k, v| v.blank? } field_hash = {} mapping_hash = {} params.each do |k, v| the_key = k.to_sym if mapping.keys.include?(the_key) field_hash[the_key] = get_map_value(the_key, v) mapping_hash[the_key] = mapping[the_key] end end result_array = [] cond_str = "" if params[:extra_where_sql] cond_str << params[:extra_where_sql].to_s end if mapping_hash.present? cond_str << " and " unless cond_str.blank? cond_str << mapping_hash.map do |buz_key, db_key| "#{db_key} = :#{buz_key}" end.join(" and ") end unless erp_admin?(params) && mapping.keys.include?(:status) cond_str << " and " unless cond_str.blank? cond_str << " #{mapping[:status]} != -1" #TODO FIXME -1 use end result_array << cond_str result_array << field_hash result_array end #@params invoke_src: erp_admin def erp_admin?(params = {}) params[:invoke_src] == 'erp_admin' end #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 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 = false, 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!" end if !existed || force #获取数据库字段列表,屏蔽掉自动维护的created_at/updated_at,也可在生成的文件中配上 #20111203 讨论后打开此屏蔽 #.delete_if{|k| k =~ /^created_at|updated_at$/} keys = new.attributes.symbolize_keys.keys 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 "#==本文件由Nsp生成#于{Time.now.to_s(:db)},可根据规则编辑" 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 = {}) return {} if params.blank? 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