[Day13] 扩充 Boxenn 的 Record Mapper

扩充 Record Mapper

大部分 domain 的 mapper 皆为 key 的转换,而在此范例中使用的外部资源为 ActiveRecord,他大部分的界面支援 Ruby 的原生物件 hash,因此可以如下扩充 base class ,让 mapper 里专注在处理 entity hash key 对 schema hash key 的 mapping。

class Mapper < Boxenn::Repositories::RecordMapper
  def build(hash)
    result = batch_mapping(value: hash, mapper: map)
    result = custom_mapping(input: hash, result: result) if respond_to?(:custom_mapping, :include_private)
    result
  end

  def batch_mapping(value:, mapper:)
    case value
    when Array
      value.map do |item|
        mapping(hash: item, mapper: mapper)
      end
    when Hash
      mapping(hash: value, mapper: mapper)
    end
  end

  def mapping(hash:, mapper:)
    result = {}
    hash.each do |key, value|
      next if !mapper.key?(key)

      if mapper[key].is_a?(Array)
        result[mapper[key].first] = batch_mapping(value: value, mapper: mapper[key][1])
      else
        result[mapper[key]] = value.is_a?(Hash) ? value.compact : value
      end
    end
    result
  end
end

范例

# User
# input (entity 的 hash)
input = {
	id_number: 'A123456789',
	profile: {
		name: '王小明',
		gender: :male,
		birthday: '1998/01/01',
		email: '[email protected]', 
	},
}

# output (schema 的 hash)
output = {
	id_number: 'A123456789',
	user_profile: {
		name: '王小明',
		gender: :male,
		birthdate: '1998/01/01',
		email: '[email protected]', 
		age: 23,
	}
}

# mapper
class UserMapper < Mapper
	# 继承此 Mapper 可以定义两个 method, map 和 custom_mapping
	# custom_mapping 只有在需要改值或做逻辑处理时才需要定义

	private

  # map 是用来将 entity hash 转为 db schema hash
  # 如果遇到 nested 的 hash 请使用 array,第一个值为 key, 後面则为对应 hash 里的 key 配对

	def map
		{
			id_number: :id_number,
			profile: [
				:user_profile,
				{
					name: :name,
					gender: :gender,
					birthday: :birthdate,
					email: :email,
				},
			]
		}
	end

	# custom_mapping 需传入两个参数, input 和 result
  # input 为 entity 转成的 hash
  # result 为已经转为 db schema key 的 hash
  # 最後回传的是再特制处理过的 db schema key 的 hash

	def custom_mapping(input:, result:)
		result[:user_profile][:age] = age(input[:profile][:birthday])
		result
	end
	
	def age(birthdate)
		today = Time.zone.today
    gap = today.year - birthdate.year
    gap = gap - 1 if (
      birthdate.month >  today.month or 
        (birthdate.month >= today.month and birthdate.day > today.day)
    )
    gap
	end
end

mapper = UserMapper.new
mapper.build(input) # => output

下一篇会介绍包装外部资源的 Source Wrapper。


<<:  Day13 iPhone捷径-媒体Part3

>>:  Day 28 品质分数等於产品绩效吗?

Day 25 Azure machine learning: Pipeline for data- 建立工作流程来收集资料

Azure machine learning: Pipeline for data- 建立工作流程来...

[Golang] Pointer

Go provide pointer similar to C and C++. Go use &a...

iOS APP 开发 OC 第十天,NSObject

tags: OC 30 day NSObject 是什麽? 是Foundation 框架中的类,在这...

props

昨天介绍了component,也知道要使用component要先注册才能使用,而今天我们要介绍的是p...

D-18. SQL & NoSQL、SQL injection、primary key & foreign key

SQL && NoSQL SQL Structured Query Language...