class ReeMapper::Mapper
def self.build(strategies, type = nil)
def self.build(strategies, type = nil) if type strategies.each do |strategy| method = strategy.method next if type.respond_to?(method) raise ReeMapper::UnsupportedTypeError, "#{type.class} should implement method `#{method}`" end end klass = Class.new(self) klass.instance_eval do strategies.each do |strategy| method = strategy.method if type class_eval(<<~RUBY, __FILE__, __LINE__ + 1) def #{method}(obj, name: nil, role: nil, only: nil, except: nil, fields_filters: []) #{ if type.is_a?(ReeMapper::AbstractWrapper) "@type.#{method}(obj, name: name, role: role, fields_filters: fields_filters)" else "@type.#{method}(obj, name: name, role: role)" end } end RUBY else class_eval(<<~RUBY, __FILE__, __LINE__ + 1) def #{method}(obj, name: nil, role: nil, only: nil, except: nil, fields_filters: []) if only && !ReeMapper::FilterFieldsContract.valid?(only) raise ReeMapper::ArgumentError, "Invalid `only` format" end if except && !ReeMapper::FilterFieldsContract.valid?(except) raise ReeMapper::ArgumentError, "Invalid `except` format" end user_fields_filter = ReeMapper::FieldsFilter.build(only: only, except: except) @fields.each_with_object(@#{method}_strategy.build_object) do |(_, field), acc| field_fields_filters = fields_filters + [user_fields_filter] next unless field_fields_filters.all? { _1.allow? field.name } next unless field.has_role?(role) is_with_value = @#{method}_strategy.has_value?(obj, field) is_optional = field.optional || @#{method}_strategy.always_optional if !is_with_value && !is_optional raise ReeMapper::TypeError, "Missing required field `\#{field.from_as_str}` for `\#{name || 'root'}`" end next if !is_with_value && !field.has_default? value = if is_with_value @#{method}_strategy.get_value(obj, field) else field.default end unless value.nil? && field.null nested_name = name ? "\#{name}[\#{field.name_as_str}]" : field.name_as_str nested_fields_filters = field_fields_filters.map { _1.filter_for(field.name) } nested_fields_filters += [field.fields_filter] value = field.type.#{method}(value, name: nested_name, role: role, fields_filters: nested_fields_filters) end @#{method}_strategy.assign_value(acc, field, value) end end RUBY end end end klass.new(strategies, type) end
def add_field(field)
def add_field(field) raise ArgumentError if field.name.nil? @fields[field.name] = field nil end
def dto(strategy_method)
def dto(strategy_method) strategy = find_strategy(strategy_method) raise ArgumentError, "there is no :#{strategy_method} strategy" unless strategy strategy.dto end
def find_strategy(strategy_method)
def find_strategy(strategy_method) strategies.detect { _1.method == strategy_method } end
def initialize(strategies, type)
def initialize(strategies, type) @fields = {} @type = type @strategies = strategies @strategy_methods = strategies.map(&:method) strategies.each do |strategy| method = strategy.method instance_variable_set(:"@#{method}_strategy", strategy) end end
def name
def name @name end
def name=(name)
def name=(name) @name = name end
def prepare_dto
def prepare_dto raise ReeMapper::ArgumentError, "mapper should contain at least one field" if fields.empty? strategies.each { _1.prepare_dto(fields.keys) } nil end