Class: LazyMapper
- Inherits:
-
Object
- Object
- LazyMapper
- Defined in:
- lib/lazy_mapper.rb
Overview
Wraps a JSON object and lazily maps its attributes to domain objects using either a set of default mappers (for Ruby’s built-in types), or custom mappers specified by the client.
The mapped values are memoized.
Example:
class Foo < LazyMapper
one :id, Integer, from: 'xmlId'
one :created_at, Time
one :amount, Money, map: Money.method(:parse)
many :users, User, map: ->(u) { User.new(u) }
end
Constant Summary collapse
- DEFAULT_MAPPINGS =
Default mappings for built-in types
{ Object => ->(o) { o }, String => ->(s) { s.to_s }, Integer => ->(i) { i.to_i }, BigDecimal => ->(d) { d.to_d }, Float => ->(f) { f.to_f }, Symbol => ->(s) { s.to_sym }, Hash => ->(h) { h.to_h }, Time => Time.method(:iso8601), Date => Date.method(:parse), URI => URI.method(:parse) }.freeze
- DEFAULT_VALUES =
Default values for primitive types
{ String => '', Integer => 0, Numeric => 0, Float => 0.0, BigDecimal => BigDecimal.new('0'), Array => [] }.freeze
- IVAR =
-> name { name_as_str = name.to_s if name_as_str[-1] == '?' name_as_str = name_as_str[0...-1] end ('@' + name_as_str).freeze }
- WRITER =
-> name { (name.to_s.gsub('?', '') + '=').to_sym }
- TO_BOOL =
Converts a value to true or false according to its truthyness
-> b { !!b }
Instance Attribute Summary collapse
- #mappers ⇒ Object readonly
Class Method Summary collapse
- .attributes ⇒ Object
- .default_value_for(type, value) ⇒ Object
- .default_values ⇒ Object
-
.from_json(json, mappers: {}) ⇒ Object
::from_json.
- .inherited(klass) ⇒ Object
-
.is(name, from: map_name(name), map: TO_BOOL, default: false) ⇒ Object
::is.
-
.many(name, type, from: map_name(name), **args) ⇒ Object
::many.
- .mapper_for(type, mapper) ⇒ Object
- .mappers ⇒ Object
-
.one(name, type, from: map_name(name), allow_nil: true, **args) ⇒ Object
::one.
Instance Method Summary collapse
- #add_mapper_for(type, &block) ⇒ Object
-
#initialize(values = {}) ⇒ LazyMapper
constructor
::new.
- #inspect ⇒ Object
Constructor Details
#initialize(values = {}) ⇒ LazyMapper
::new
Create a new instance by giving a Hash of attribues.
Example
Foo.new :id => 42,
:created_at => Time.parse("2015-07-29 14:07:35 +0200"),
:amount => Money.parse("$2.00"),
:users => [
User.new("id" => 23, "name" => "Adam"),
User.new("id" => 45, "name" => "Ole"),
User.new("id" => 66, "name" => "Anders"),
User.new("id" => 91, "name" => "Kristoffer)
]
101 102 103 104 105 106 107 |
# File 'lib/lazy_mapper.rb', line 101 def initialize(values = {}) @json = {} @mappers = {} values.each do |name, value| send(WRITER[name], value) end end |
Instance Attribute Details
#mappers ⇒ Object
70 71 72 |
# File 'lib/lazy_mapper.rb', line 70 def mappers @mappers ||= self.class.mappers end |
Class Method Details
.attributes ⇒ Object
136 137 138 |
# File 'lib/lazy_mapper.rb', line 136 def self.attributes @attributes ||= {} end |
.default_value_for(type, value) ⇒ Object
37 38 39 |
# File 'lib/lazy_mapper.rb', line 37 def self.default_value_for type, value default_values[type] = value end |
.default_values ⇒ Object
41 42 43 |
# File 'lib/lazy_mapper.rb', line 41 def self.default_values @default_values ||= DEFAULT_VALUES end |
.from_json(json, mappers: {}) ⇒ Object
::from_json
Create a new instance by giving a Hash of unmapped attributes.
The keys in the Hash are assumed to be camelCased strings.
Example
Foo.from_json "xmlId" => 42,
"createdAt" => "2015-07-29 14:07:35 +0200",
"amount" => "$2.00",
"users" => [
{ "id" => 23, "name" => "Adam" },
{ "id" => 45, "name" => "Ole" },
{ "id" => 66, "name" => "Anders" },
{ "id" => 91, "name" => "Kristoffer" }
]
127 128 129 130 131 132 133 134 |
# File 'lib/lazy_mapper.rb', line 127 def self.from_json json, mappers: {} return nil if json.nil? fail TypeError, "#{ json.inspect } is not a Hash" unless json.respond_to? :to_h instance = new instance.send :json=, json.to_h instance.send :mappers=, mappers instance end |
.inherited(klass) ⇒ Object
64 65 66 67 |
# File 'lib/lazy_mapper.rb', line 64 def self.inherited(klass) klass.instance_variable_set IVAR[:mappers], self.mappers.dup klass.instance_variable_set IVAR[:default_values], self.default_values.dup end |
.is(name, from: map_name(name), map: TO_BOOL, default: false) ⇒ Object
::is
Defines an boolean attribute
Arguments
name - The name of the attribue
from: - Specifies the name of the wrapped value in the JSON object.
Defaults to camelCased version of +name+.
map: - Specifies a custom mapper to apply to the wrapped value. Must be
a Callable.
Defaults to TO_BOOL if unspecified.
default: - The default value to use if the value is missing. False, if unspecified
Example
class Foo < LazyMapper
is :green?, from: "isGreen", map: ->(x) { !x.zero? }
# ...
end
223 224 225 |
# File 'lib/lazy_mapper.rb', line 223 def self.is name, from: map_name(name), map: TO_BOOL, default: false one name, [TrueClass, FalseClass], from: from, allow_nil: false, map: map, default: default end |
.many(name, type, from: map_name(name), **args) ⇒ Object
::many
Wraps a collection
Arguments
name - The name of the attribue
type - The type of the elemnts in the collection. If an element is
already of that type, the mapper is bypassed for that element.
from: - Specifies the name of the wrapped array in the JSON object.
Defaults to camelCased version of +name+.
map: - Specifies a custom mapper to apply to the elements in the wrapped
array. Must respond to +#call+. If unspecified, it defaults to the default
mapper for the specified +type+ or simply the identity mapper if no default
mapper exists.
default: - The default value to use, if the wrapped value is not present
in the wrapped JSON object.
Example
class Bar < LazyMapper
many :underlings, Person, from: "serfs", map: ->(p) { Person.new(p) }
# ...
end
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/lazy_mapper.rb', line 259 def self.many(name, type, from: map_name(name), **args) # Define setter define_method(WRITER[name]) { |val| check_type! val, Enumerable, allow_nil: false instance_variable_set(IVAR[name], val) } # Define getter define_method(name) { memoize(name) { unmapped_value = json[from] if unmapped_value.is_a? Array unmapped_value.map { |v| mapped_value(name, v, type, **args) } else mapped_value name, unmapped_value, Array, **args end } } end |
.mapper_for(type, mapper) ⇒ Object
56 57 58 |
# File 'lib/lazy_mapper.rb', line 56 def self.mapper_for(type, mapper) mappers[type] = mapper end |
.mappers ⇒ Object
60 61 62 |
# File 'lib/lazy_mapper.rb', line 60 def self.mappers @mappers ||= DEFAULT_MAPPINGS end |
.one(name, type, from: map_name(name), allow_nil: true, **args) ⇒ Object
::one
Defines an attribute and creates a reader and a writer for it. The writer verifies the type of it’s supplied value.
Arguments
name - The name of the attribue
type - The type of the attribute. If the wrapped value is already of
that type, the mapper is bypassed.
If the type is allowed be one of several, use an Array to
to specify which ones
from: - Specifies the name of the wrapped value in the JSON object.
Defaults to camelCased version of +name+.
map: - Specifies a custom mapper to apply to the wrapped value. Must be
a Callable. If unspecified, it defaults to the default mapper for the
specified +type+ or simply the identity mapper if no default mapper exists.
default: - The default value to use, if the wrapped value is not present
in the wrapped JSON object.
allow_nil: - If true, allows the mapped value to be nil. Defaults to true.
Example
class Foo < LazyMapper
one :boss, Person, from: "supervisor", map: ->(p) { Person.new(p) }
one :weapon, [BladedWeapon, Firearm], default: Sixshooter.new
# ...
end
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/lazy_mapper.rb', line 174 def self.one(name, type, from: map_name(name), allow_nil: true, **args) ivar = IVAR[name] # Define writer define_method(WRITER[name]) { |val| check_type! val, type, allow_nil: allow_nil instance_variable_set(ivar, val) } # Define reader define_method(name) { memoize(name, ivar) { unmapped_value = json[from] mapped_value(name, unmapped_value, type, **args) } } attributes[name] = type end |
Instance Method Details
#add_mapper_for(type, &block) ⇒ Object
280 281 282 |
# File 'lib/lazy_mapper.rb', line 280 def add_mapper_for(type, &block) mappers[type] = block end |
#inspect ⇒ Object
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'lib/lazy_mapper.rb', line 284 def inspect @__under_inspection__ ||= 0 return "<#{ self.class.name } ... >" if @__under_inspection__ > 0 @__under_inspection__ += 1 attributes = self.class.attributes if self.class.superclass.respond_to? :attributes attributes = self.class.superclass.attributes.merge attributes end present_attributes = attributes.keys.each_with_object({}) {|name, memo| value = self.send name memo[name] = value unless value.nil? } "<#{ self.class.name } #{ present_attributes.map {|k,v| k.to_s + ': ' + v.inspect }.join(', ') } >" res = "<#{ self.class.name } #{ present_attributes.map {|k,v| k.to_s + ': ' + v.inspect }.join(', ') } >" @__under_inspection__ -= 1 res end |