lib/lazy_mapper.rb in lazy_mapper-0.2.0 vs lib/lazy_mapper.rb in lazy_mapper-0.2.1
- old
+ new
@@ -1,10 +1,10 @@
require 'bigdecimal'
require 'bigdecimal/util'
require 'time'
-##
+#
# 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.
@@ -18,43 +18,52 @@
# end
#
class LazyMapper
+ #
# Default mappings for built-in types
+ #
DEFAULT_MAPPINGS = {
- 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 },
+ Object => :itself.to_proc,
+ String => :to_s.to_proc,
+ Integer => :to_i.to_proc,
+ BigDecimal => :to_d.to_proc,
+ Float => :to_f.to_proc,
+ Symbol => :to_sym.to_proc,
+ Hash => :to_h.to_proc,
Time => Time.method(:iso8601),
Date => Date.method(:parse),
URI => URI.method(:parse)
}.freeze
+ #
+ # Adds (or overrides) a default type for a given type
+ #
def self.default_value_for type, value
default_values[type] = value
end
def self.default_values
@default_values ||= DEFAULT_VALUES
end
-
- # Default values for primitive types
+ #
+ # Default values for built-in value types
+ #
DEFAULT_VALUES = {
String => '',
Integer => 0,
Numeric => 0,
Float => 0.0,
BigDecimal => BigDecimal.new('0'),
Array => []
}.freeze
+ #
+ # Adds a mapper for a give type
+ #
def self.mapper_for(type, mapper)
mappers[type] = mapper
end
def self.mappers
@@ -80,14 +89,16 @@
('@' + name_as_str).freeze
}
WRITER = -> name { (name.to_s.gsub('?', '') + '=').to_sym }
- # = ::new
#
- # Create a new instance by giving a Hash of attribues.
+ # Creates a new instance by giving a Hash of attribues.
#
+ # Attribute values are type checked according to how they were defined.
+ # If a value has the wrong type, a `TypeError` is raised.
+ #
# == Example
#
# Foo.new :id => 42,
# :created_at => Time.parse("2015-07-29 14:07:35 +0200"),
# :amount => Money.parse("$2.00"),
@@ -104,27 +115,38 @@
values.each do |name, value|
send(WRITER[name], value)
end
end
- # = ::from_json
#
# Create a new instance by giving a Hash of unmapped attributes.
#
# The keys in the Hash are assumed to be camelCased strings.
#
+ # == Arguments
+ #
+ # +json+ - The unmapped data as a Hash(-like object). Must respond to #to_h.
+ # Keys are assumed to be camelCased string
+ #
+ # +mappers:+ - Optional instance-level mappers.
+ # Keys can either be classes or symbols corresponding to named attributes.
+ #
+ #
# == Example
#
- # Foo.from_json "xmlId" => 42,
+ # 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" }
- # ]
+ # { "id" => 91, "name" => "Kristoffer" } ]},
+ # mappers: {
+ # :amount => -> x { Money.new(x) },
+ # User => User.method(:new) })
#
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
@@ -135,33 +157,28 @@
def self.attributes
@attributes ||= {}
end
- # = ::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
+ # +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
+ # +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+.
+ # +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.
+ # +map:+ - Specifies a custom mapper to apply to the wrapped value.
+ # 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.
+ # +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
#
@@ -190,30 +207,29 @@
}
attributes[name] = type
end
- ##
+ #
# Converts a value to true or false according to its truthyness
+ #
TO_BOOL = -> b { !!b }
- # = ::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+.
+ # 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.
+ # +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
+ # +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? }
@@ -224,32 +240,28 @@
one name, [TrueClass, FalseClass], from: from, allow_nil: false, map: map, default: default
end
singleton_class.send(:alias_method, :has, :is)
-
- # = ::many
#
- # Wraps a collection
+ # Defines a collection attribute
#
# == Arguments
#
- # +name+ - The name of the attribue
+ # +name+ - The name of the attribute
#
- # +type+ - The type of the elemnts in the collection. If an element is
- # already of that type, the mapper is bypassed for that element.
+ # +type+ - The type of the elements in the collection.
#
# +from:+ - Specifies the name of the wrapped array in the JSON object.
- # Defaults to camelCased version of +name+.
+ # 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.
+ # +map:+ - Specifies a custom mapper to apply to each elements in the wrapped collection.
+ # 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.
+ # in the wrapped JSON object.
#
# == Example
#
# class Bar < LazyMapper
# many :underlings, Person, from: "serfs", map: ->(p) { Person.new(p) }
@@ -275,10 +287,13 @@
end
}
}
end
+ #
+ # Adds an instance-level type mapper
+ #
def add_mapper_for(type, &block)
mappers[type] = block
end
def inspect
@@ -303,11 +318,11 @@
def json
@json ||= {}
end
- ##
+ #
# Defines how to map an attribute name
# to the corresponding name in the unmapped
# JSON object.
#
# Defaults to CAMELIZE
@@ -358,8 +373,8 @@
def memoize name, ivar = IVAR[name]
send WRITER[name], yield unless instance_variable_defined?(ivar)
instance_variable_get(ivar)
end
- SNAKE_CASE_PATTERN = /(_[a-z])/
+ SNAKE_CASE_PATTERN = /(_[a-z])/ # :nodoc:
CAMELIZE = -> name { name.to_s.gsub(SNAKE_CASE_PATTERN) { |x| x[1].upcase }.gsub('?', '') }
end