HornsAndHooves-flat_map 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/.metrics +17 -0
- data/.rspec +4 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +20 -0
- data/HornsAndHooves-flat_map.gemspec +29 -0
- data/LICENSE +21 -0
- data/README.markdown +214 -0
- data/Rakefile +15 -0
- data/lib/flat_map.rb +14 -0
- data/lib/flat_map/errors.rb +57 -0
- data/lib/flat_map/mapping.rb +124 -0
- data/lib/flat_map/mapping/factory.rb +21 -0
- data/lib/flat_map/mapping/reader.rb +12 -0
- data/lib/flat_map/mapping/reader/basic.rb +28 -0
- data/lib/flat_map/mapping/reader/formatted.rb +45 -0
- data/lib/flat_map/mapping/reader/formatted/formats.rb +28 -0
- data/lib/flat_map/mapping/reader/method.rb +25 -0
- data/lib/flat_map/mapping/reader/proc.rb +15 -0
- data/lib/flat_map/mapping/writer.rb +11 -0
- data/lib/flat_map/mapping/writer/basic.rb +25 -0
- data/lib/flat_map/mapping/writer/method.rb +28 -0
- data/lib/flat_map/mapping/writer/proc.rb +18 -0
- data/lib/flat_map/model_mapper.rb +195 -0
- data/lib/flat_map/model_mapper/persistence.rb +108 -0
- data/lib/flat_map/model_mapper/skipping.rb +45 -0
- data/lib/flat_map/open_mapper.rb +113 -0
- data/lib/flat_map/open_mapper/attribute_methods.rb +55 -0
- data/lib/flat_map/open_mapper/factory.rb +244 -0
- data/lib/flat_map/open_mapper/mapping.rb +123 -0
- data/lib/flat_map/open_mapper/mounting.rb +168 -0
- data/lib/flat_map/open_mapper/persistence.rb +178 -0
- data/lib/flat_map/open_mapper/skipping.rb +66 -0
- data/lib/flat_map/open_mapper/traits.rb +95 -0
- data/lib/flat_map/version.rb +3 -0
- data/spec/flat_map/errors_spec.rb +23 -0
- data/spec/flat_map/mapper/attribute_methods_spec.rb +36 -0
- data/spec/flat_map/mapper/callbacks_spec.rb +76 -0
- data/spec/flat_map/mapper/factory_spec.rb +285 -0
- data/spec/flat_map/mapper/mapping_spec.rb +98 -0
- data/spec/flat_map/mapper/mounting_spec.rb +142 -0
- data/spec/flat_map/mapper/persistence_spec.rb +152 -0
- data/spec/flat_map/mapper/skipping_spec.rb +91 -0
- data/spec/flat_map/mapper/targeting_spec.rb +156 -0
- data/spec/flat_map/mapper/traits_spec.rb +172 -0
- data/spec/flat_map/mapper/validations_spec.rb +72 -0
- data/spec/flat_map/mapper_spec.rb +9 -0
- data/spec/flat_map/mapping/factory_spec.rb +12 -0
- data/spec/flat_map/mapping/reader/basic_spec.rb +15 -0
- data/spec/flat_map/mapping/reader/formatted_spec.rb +62 -0
- data/spec/flat_map/mapping/reader/method_spec.rb +13 -0
- data/spec/flat_map/mapping/reader/proc_spec.rb +13 -0
- data/spec/flat_map/mapping/writer/basic_spec.rb +15 -0
- data/spec/flat_map/mapping/writer/method_spec.rb +13 -0
- data/spec/flat_map/mapping/writer/proc_spec.rb +13 -0
- data/spec/flat_map/mapping_spec.rb +123 -0
- data/spec/flat_map/open_mapper_spec.rb +19 -0
- data/spec/spec_helper.rb +7 -0
- data/tmp/metric_fu/_data/20131218.yml +6902 -0
- data/tmp/metric_fu/_data/20131219.yml +6726 -0
- metadata +220 -0
@@ -0,0 +1,124 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# Encapsulates mapping concept used by mappers. Each mapping belongs to
|
3
|
+
# a particular mapper and has its own reader and writer objects.
|
4
|
+
class Mapping
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
autoload :Reader
|
8
|
+
autoload :Writer
|
9
|
+
autoload :Factory
|
10
|
+
|
11
|
+
attr_reader :mapper, :name, :full_name, :target_attribute
|
12
|
+
attr_reader :reader, :writer
|
13
|
+
attr_reader :multiparam
|
14
|
+
|
15
|
+
delegate :target, :to => :mapper
|
16
|
+
delegate :write, :to => :writer, :allow_nil => true
|
17
|
+
delegate :read, :to => :reader, :allow_nil => true
|
18
|
+
|
19
|
+
# Initialize a mapping, passing to it a +mapper+, which is
|
20
|
+
# a gateway to actual +target+, +name+, which is an external
|
21
|
+
# identifier, +target_attribute+, which is used to access
|
22
|
+
# actual information of the +target+, and +options+.
|
23
|
+
#
|
24
|
+
# @param [FlatMap::Mapper] mapper
|
25
|
+
# @param [Symbol] name
|
26
|
+
# @param [Symbol] target_attribute
|
27
|
+
# @param [Hash] options
|
28
|
+
# @option [Symbol, Proc] :reader specifies how value will
|
29
|
+
# be read from the +target+
|
30
|
+
# @option [Symbol] :format specifies additional processing
|
31
|
+
# of the value on reading
|
32
|
+
# @option [Symbol, Proc] :writer specifies how value will
|
33
|
+
# be written to the +target+
|
34
|
+
# @option [Class] :multiparam specifies multiparam Class,
|
35
|
+
# object of which will be instantiated on writing
|
36
|
+
# multiparam attribute passed from the Rails form
|
37
|
+
def initialize(mapper, name, target_attribute, options = {})
|
38
|
+
@mapper = mapper
|
39
|
+
@name = name
|
40
|
+
@target_attribute = target_attribute
|
41
|
+
|
42
|
+
@full_name = mapper.suffixed? ? :"#{@name}_#{mapper.suffix}" : name
|
43
|
+
|
44
|
+
@multiparam = options[:multiparam]
|
45
|
+
|
46
|
+
fetch_reader(options)
|
47
|
+
fetch_writer(options)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return +true+ if the mapping was created with the <tt>:multiparam</tt>
|
51
|
+
# option set.
|
52
|
+
#
|
53
|
+
# @return [Boolean]
|
54
|
+
def multiparam?
|
55
|
+
!!@multiparam
|
56
|
+
end
|
57
|
+
|
58
|
+
# Lookup the passed hash of params for the key that corresponds
|
59
|
+
# to the +full_name+ of +self+, and write it if it is present.
|
60
|
+
#
|
61
|
+
# @param [Hash] params
|
62
|
+
# @return [Object] value assigned
|
63
|
+
def write_from_params(params)
|
64
|
+
write(params[full_name]) if params.key?(full_name) && writer.present?
|
65
|
+
end
|
66
|
+
|
67
|
+
# Return a hash of a single key => value pair, where key
|
68
|
+
# corresponds to +full_name+ and +value+ to value read from
|
69
|
+
# +target+. If +reader+ is not set, return an empty hash.
|
70
|
+
#
|
71
|
+
# @return [Hash]
|
72
|
+
def read_as_params
|
73
|
+
reader ? {full_name => read} : {}
|
74
|
+
end
|
75
|
+
|
76
|
+
# Instantiate a +reader+ object based on the <tt>:reader</tt>
|
77
|
+
# and <tt>:format</tt> values of +options+.
|
78
|
+
#
|
79
|
+
# @param [Hash] options
|
80
|
+
# @return [FlatMap::Mapping::Reader::Basic]
|
81
|
+
def fetch_reader(options)
|
82
|
+
options_reader = options[:reader]
|
83
|
+
|
84
|
+
@reader =
|
85
|
+
case options_reader
|
86
|
+
when Symbol, String
|
87
|
+
Reader::Method.new(self, options_reader)
|
88
|
+
when Proc
|
89
|
+
Reader::Proc.new(self, options_reader)
|
90
|
+
when false
|
91
|
+
nil
|
92
|
+
else
|
93
|
+
if options.key?(:format) then
|
94
|
+
Reader::Formatted.new(self, options[:format])
|
95
|
+
else
|
96
|
+
Reader::Basic.new(self)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
private :fetch_reader
|
101
|
+
|
102
|
+
# Instantiate a +writer+ object based on the <tt>:writer</tt>
|
103
|
+
# value of +options+.
|
104
|
+
#
|
105
|
+
# @param [Hash] options
|
106
|
+
# @return [FlatMap::Mapping::Writer::Basic]
|
107
|
+
def fetch_writer(options)
|
108
|
+
options_writer = options[:writer]
|
109
|
+
|
110
|
+
@writer =
|
111
|
+
case options_writer
|
112
|
+
when Symbol, String
|
113
|
+
Writer::Method.new(self, options_writer)
|
114
|
+
when Proc
|
115
|
+
Writer::Proc.new(self, options_writer)
|
116
|
+
when false
|
117
|
+
nil
|
118
|
+
else
|
119
|
+
Writer::Basic.new(self)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
private :fetch_writer
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# Factory objects store mapping definitions within mapper class and are
|
3
|
+
# used eventually to generate mapping objects for a particular mapper.
|
4
|
+
class Mapping::Factory
|
5
|
+
# Simply store all arguments necessary to create a new mapping for
|
6
|
+
# a specific mapper.
|
7
|
+
#
|
8
|
+
# @param [*Object] args
|
9
|
+
def initialize(*args)
|
10
|
+
@args = args
|
11
|
+
end
|
12
|
+
|
13
|
+
# Return a new mapping, initialized by +mapper+ and <tt>@args</tt>.
|
14
|
+
#
|
15
|
+
# @param [FlatMap::Mapper] mapper
|
16
|
+
# @return [FlatMap::Mapping]
|
17
|
+
def create(mapper)
|
18
|
+
Mapping.new(mapper, *@args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# Reader module hosts various readers that are used by
|
3
|
+
# mappings for reading and returning values.
|
4
|
+
module Mapping::Reader
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
autoload :Basic
|
8
|
+
autoload :Method
|
9
|
+
autoload :Proc
|
10
|
+
autoload :Formatted
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module FlatMap
|
2
|
+
module Mapping::Reader
|
3
|
+
# Basic reader simply sends a mapped attribute to the target
|
4
|
+
# and returns the result value.
|
5
|
+
class Basic
|
6
|
+
attr_reader :mapping
|
7
|
+
|
8
|
+
delegate :target, :target_attribute, :to => :mapping
|
9
|
+
|
10
|
+
# Initialize the reader with a mapping.
|
11
|
+
#
|
12
|
+
# @param [FlatMap::Mapping] mapping
|
13
|
+
def initialize(mapping)
|
14
|
+
@mapping = mapping
|
15
|
+
end
|
16
|
+
|
17
|
+
# Send the attribute method to the target and return its value.
|
18
|
+
# As a base class for readers, it allows to pass additional
|
19
|
+
# arguments when reading value (for example, used by :enum
|
20
|
+
# format of {Formatted} reader)
|
21
|
+
#
|
22
|
+
# @return [Object] value returned by reading
|
23
|
+
def read(*)
|
24
|
+
target.send(target_attribute)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module FlatMap
|
2
|
+
module Mapping::Reader
|
3
|
+
# Formatted reader reads the value the same as Basic reader does, but
|
4
|
+
# additionally performs value postprocessing. All processing methods
|
5
|
+
# are defined within {Formatted::Formats} module. The method is chosen
|
6
|
+
# based on the :format option when the mapping is defined.
|
7
|
+
class Formatted < Basic
|
8
|
+
extend ActiveSupport::Autoload
|
9
|
+
autoload :Formats
|
10
|
+
|
11
|
+
include Formats
|
12
|
+
|
13
|
+
# Initialize the reader with a +mapping+ and a +format+.
|
14
|
+
#
|
15
|
+
# @param [FlatMap::Mapping] mapping
|
16
|
+
# @param [Symbol] format
|
17
|
+
def initialize(mapping, format)
|
18
|
+
@mapping, @format = mapping, format
|
19
|
+
end
|
20
|
+
|
21
|
+
# Read the value just like the {Basic} reader does, but
|
22
|
+
# additionally send the returned value to its format method.
|
23
|
+
#
|
24
|
+
# Additional arguments will be passed to formatting function
|
25
|
+
# of the mapping's format.
|
26
|
+
#
|
27
|
+
# @return [Object] formatted value
|
28
|
+
def read(*args)
|
29
|
+
format_value(super, *args)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Send the +value+ to the format method, defined in the {Format}
|
33
|
+
# module and specified upon reader initialization.
|
34
|
+
#
|
35
|
+
# Additional optional arguments are passed as well.
|
36
|
+
#
|
37
|
+
# @param [Object] value
|
38
|
+
# @return [Object] formatted value
|
39
|
+
def format_value(value, *args)
|
40
|
+
send(@format, value, *args)
|
41
|
+
end
|
42
|
+
private :format_value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module FlatMap
|
2
|
+
module Mapping::Reader
|
3
|
+
# Hosts various formats that can be applied to values read by mappings
|
4
|
+
# for post-processing.
|
5
|
+
module Formatted::Formats
|
6
|
+
if defined? I18n
|
7
|
+
# Pass +value+ to <tt>I18n::l</tt> method.
|
8
|
+
def i18n_l(value)
|
9
|
+
I18n::l(value) if value
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
if defined? PowerEnum
|
14
|
+
# Return the specified +property+ of a +value+ which
|
15
|
+
# is supposed to be an +enum+ record. By default,
|
16
|
+
# uses <tt>:name</tt>. However, <tt>:description</tt>
|
17
|
+
# might be also useful for UI purposes.
|
18
|
+
#
|
19
|
+
# @param [Object] value
|
20
|
+
# @param [Symbol] property
|
21
|
+
# @return [Object]
|
22
|
+
def enum(value, property = :name)
|
23
|
+
value.send(property) if value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module FlatMap
|
2
|
+
module Mapping::Reader
|
3
|
+
# Method mapper calls a method, defined by the mapper, sending
|
4
|
+
# the mapping object to it as an argument.
|
5
|
+
class Method < Basic
|
6
|
+
delegate :mapper, :to => :mapping
|
7
|
+
|
8
|
+
# Initialize the reader with a +mapping+ and a +method+.
|
9
|
+
#
|
10
|
+
# @param [FlatMap::Mapping] mapping
|
11
|
+
# @param [Symbol] method name
|
12
|
+
def initialize(mapping, method)
|
13
|
+
@mapping, @method = mapping, method
|
14
|
+
end
|
15
|
+
|
16
|
+
# Send the <tt>@method</tt> to the mapping's mapper, passing
|
17
|
+
# the mapping itself to it.
|
18
|
+
#
|
19
|
+
# @return [Object] value returned by reader
|
20
|
+
def read
|
21
|
+
mapper.send(@method, mapping)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module FlatMap
|
2
|
+
module Mapping::Reader
|
3
|
+
# Proc reader accepts a lambda and calls it with the target
|
4
|
+
# as an argument for reading.
|
5
|
+
class Proc < Method
|
6
|
+
# Call a <tt>@method</tt>, which is a {Proc} object,
|
7
|
+
# passing the +target+ object to it.
|
8
|
+
#
|
9
|
+
# @return [Object] value returned by reader's lambda
|
10
|
+
def read
|
11
|
+
@method.call(target)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# Writer module hosts various writer classes that are used
|
3
|
+
# by mappings to assign values to the target of an associated mapper.
|
4
|
+
module Mapping::Writer
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
autoload :Basic
|
8
|
+
autoload :Method
|
9
|
+
autoload :Proc
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module FlatMap
|
2
|
+
module Mapping::Writer
|
3
|
+
# Basic writer simply calls the target's attribute assignment method
|
4
|
+
# passing to it the value being written.
|
5
|
+
class Basic
|
6
|
+
attr_reader :mapping
|
7
|
+
|
8
|
+
delegate :target, :target_attribute, :to => :mapping
|
9
|
+
|
10
|
+
# Initialize writer by passing +mapping+ to it.
|
11
|
+
def initialize(mapping)
|
12
|
+
@mapping = mapping
|
13
|
+
end
|
14
|
+
|
15
|
+
# Call the assignment method of the target, passing
|
16
|
+
# the +value+ to it.
|
17
|
+
#
|
18
|
+
# @param [Object] value
|
19
|
+
# @return [Object] result of assignment
|
20
|
+
def write(value)
|
21
|
+
target.send("#{target_attribute}=", value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module FlatMap
|
2
|
+
module Mapping::Writer
|
3
|
+
# Method writer calls a method defined by mapper and sends mapping
|
4
|
+
# and value to it as arguments.
|
5
|
+
#
|
6
|
+
# Note that this doesn't set anything on the target itself.
|
7
|
+
class Method < Basic
|
8
|
+
delegate :mapper, :to => :mapping
|
9
|
+
|
10
|
+
# Initialize the writer with a +mapping+ and +method+ name
|
11
|
+
# that should be called on the mapping's mapper.
|
12
|
+
#
|
13
|
+
# @param [FlatMap::Mapping] mapping
|
14
|
+
# @param [Symbol] method
|
15
|
+
def initialize(mapping, method)
|
16
|
+
@mapping, @method = mapping, method
|
17
|
+
end
|
18
|
+
|
19
|
+
# Write a +value+ by sending it, along with the mapping itself.
|
20
|
+
#
|
21
|
+
# @param [Object] value
|
22
|
+
# @return [Object] result of writing
|
23
|
+
def write(value)
|
24
|
+
mapper.send(@method, mapping, value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module FlatMap
|
2
|
+
module Mapping::Writer
|
3
|
+
# Proc writer calls a lambda passed on the mapping definition and
|
4
|
+
# sends the mapper's target and value to it.
|
5
|
+
#
|
6
|
+
# Note that this doesn't set anything on the target itself.
|
7
|
+
class Proc < Method
|
8
|
+
# Call a <tt>@method</tt>, which is a +Proc+ object,
|
9
|
+
# passing it the mapping's +target+ and +value+.
|
10
|
+
#
|
11
|
+
# @param [Object] value
|
12
|
+
# @return [Object] result of writing
|
13
|
+
def write(value)
|
14
|
+
@method.call(target, value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# == Mapper
|
3
|
+
#
|
4
|
+
# FlatMap mappers are designed to provide complex set of data, distributed over
|
5
|
+
# associated AR models, in the simple form of a plain hash. They accept a plain
|
6
|
+
# hash of the same format and distribute its values over deeply nested AR models.
|
7
|
+
#
|
8
|
+
# To achieve this goal, Mapper uses three major concepts: Mappings, Mountings and
|
9
|
+
# Traits.
|
10
|
+
#
|
11
|
+
# === Mappings
|
12
|
+
#
|
13
|
+
# Mappings are defined view Mapper.map method. They represent a simple one-to-one
|
14
|
+
# relation between target attribute and a mapper, extended by additional features
|
15
|
+
# for convenience. The best way to show how they work is by example:
|
16
|
+
#
|
17
|
+
# class CustomerMapper < FlatMap::Mapper
|
18
|
+
# # When there is no need to rename attributes, they can be passed as array:
|
19
|
+
# map :first_name, :last_name
|
20
|
+
#
|
21
|
+
# # When hash is used, it will map field name to attribute name:
|
22
|
+
# map :dob => :date_of_birth
|
23
|
+
#
|
24
|
+
# # Also, additional options can be used:
|
25
|
+
# map :name_suffix, :format => :enum
|
26
|
+
# map :password, :reader => false, :writer => :assign_password
|
27
|
+
#
|
28
|
+
# # Or you can combine all definitions together if they all are common:
|
29
|
+
# map :first_name, :last_name,
|
30
|
+
# :dob => :date_of_birth,
|
31
|
+
# :suffix => :name_suffix,
|
32
|
+
# :reader => :my_custom_reader
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# When mappings are defined, one can read and write values using them:
|
36
|
+
#
|
37
|
+
# mapper = CustomerMapper.find(1)
|
38
|
+
# mapper.read # => {:first_name => 'John', :last_name => 'Smith', :dob => '02/01/1970'}
|
39
|
+
# mapper.write(params) # will assign same-looking hash of arguments
|
40
|
+
#
|
41
|
+
# Following options may be used when defining mappings:
|
42
|
+
# [<tt>:format</tt>] Allows to additionally process output value on reading it. All formats are
|
43
|
+
# defined within FlatMap::Mapping::Reader::Formatted::Formats and
|
44
|
+
# specify the actual output of the mapping
|
45
|
+
# [<tt>:reader</tt>] Allows you to manually control reader value of a mapping, or a group of
|
46
|
+
# mappings listed on definition. When String or Symbol is used, will call
|
47
|
+
# a method, defined by mapper class, and pass mapping object to it. When
|
48
|
+
# lambda is used, mapper's target (the model) will be passed to it.
|
49
|
+
# [<tt>:writer</tt>] Just like with the :reader option, allows to control how value is assigned
|
50
|
+
# (written). Works the same way as :reader does, but additionally value is
|
51
|
+
# sent to both mapper method and lambda.
|
52
|
+
# [<tt>:multiparam</tt>] If used, multiparam attributes will be extracted from params, when
|
53
|
+
# those are passed for writing. Class should be passed as a value for
|
54
|
+
# this option. Object of this class will be initialized with the arguments
|
55
|
+
# extracted from params hash.
|
56
|
+
#
|
57
|
+
# === Mountings
|
58
|
+
#
|
59
|
+
# Mappers may be mounted on top of each other. This ability allows host mapper to gain all the
|
60
|
+
# mappings of the mounted mapper, thus providing more information for external usage (both reading
|
61
|
+
# and writing). Usually, target for mounted mapper may be obtained from association of target of
|
62
|
+
# the host mapper itself, but may be defined manually.
|
63
|
+
#
|
64
|
+
# class CustomerMapper < FlatMap::Mapper
|
65
|
+
# map :first_name, :last_name
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# class CustomerAccountMapper < FlatMap::Mapper
|
69
|
+
# map :source, :brand, :format => :enum
|
70
|
+
#
|
71
|
+
# mount :customer
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# mapper = CustomerAccountMapper.find(1)
|
75
|
+
# mapper.read # => {:first_name => 'John', :last_name => 'Smith', :source => nil, :brand => 'FTW'}
|
76
|
+
# mapper.write(params) # Will assign params for both CustomerAccount and Customer records
|
77
|
+
#
|
78
|
+
# The following options may be used when mounting a mapper:
|
79
|
+
# [<tt>:mapper_class</tt>] Specifies mapper class if it cannot be determined from mounting itself
|
80
|
+
# [<tt>:mapper_class_name</tt>] Alternate string form of class name instead of mapper_class.
|
81
|
+
# [<tt>:target</tt>] Allows to manually specify target for the new mapper. May be an object or lambda
|
82
|
+
# with arity of one that accepts host mapper target as argument. Comes in handy
|
83
|
+
# when target cannot be obviously detected or requires additional setup:
|
84
|
+
# <tt>mount :title, :target => lambda{ |customer| customer.title_customers.build.build_title }</tt>
|
85
|
+
# [<tt>:traits</tt>] Specifies list of traits to be used by mounted mapper
|
86
|
+
# [<tt>:suffix</tt>] Specifies the suffix that will be appended to all mappings and mountings of mapper,
|
87
|
+
# as well as mapper name itself.
|
88
|
+
#
|
89
|
+
# === Traits
|
90
|
+
#
|
91
|
+
# Traits allow mappers to encapsulate named sets of additional definitions, and use them optionally
|
92
|
+
# on mapper initialization. Everything that can be defined within the mapper may be defined within
|
93
|
+
# the trait. In fact, from the implementation perspective traits are mappers themselves that are
|
94
|
+
# mounted on the host mapper.
|
95
|
+
#
|
96
|
+
# class CustomerAccountMapper < FlatMap::Mapper
|
97
|
+
# map :brand, :format => :enum
|
98
|
+
#
|
99
|
+
# trait :with_email do
|
100
|
+
# map :source, :format => :enum
|
101
|
+
#
|
102
|
+
# mount :email_address
|
103
|
+
#
|
104
|
+
# trait :with_email_phones_residence do
|
105
|
+
# mount :customer, :traits => [:with_phone_numbers, :with_residence]
|
106
|
+
# end
|
107
|
+
# end
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# CustomerAccountMapper.find(1).read # => {:brand => 'TLP'}
|
111
|
+
# CustomerAccountMapper.find(1, :with_email).read # => {:brand => 'TLP', :source => nil, :email_address => 'j.smith@gmail.com'}
|
112
|
+
# CustomerAccountMapper.find(1, :with_email_phone_residence).read # => :brand, :source, :email_address, phone numbers,
|
113
|
+
# #:residence attributes - all will be available for reading and writing in plain hash
|
114
|
+
#
|
115
|
+
# === Extensions
|
116
|
+
#
|
117
|
+
# When mounting a mapper, one can pass an optional block. This block is used as an extension for a mounted
|
118
|
+
# mapper and acts as an anonymous trait. For example:
|
119
|
+
#
|
120
|
+
# class CustomerAccountMapper < FlatMap::Mapper
|
121
|
+
# mount :customer do
|
122
|
+
# map :dob => :date_of_birth, :format => :i18n_l
|
123
|
+
# validates_presence_of :dob
|
124
|
+
#
|
125
|
+
# mount :unique_identifier
|
126
|
+
#
|
127
|
+
# validates_acceptance_of :mandatory_agreement, :message => "You must check this box to continue"
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# === Validation
|
132
|
+
#
|
133
|
+
# <tt>FlatMap::Mapper</tt> includes <tt>ActiveModel::Validations</tt> module, allowing each model to
|
134
|
+
# perform its own validation routines before trying to save its target (which is usually AR model). Mapper
|
135
|
+
# validation is very handy when mappers are used with Rails forms, since there no need to lookup for a
|
136
|
+
# deeply nested errors hash of the AR models to extract error messages. Mapper validations will attach
|
137
|
+
# messages to mapping names.
|
138
|
+
#
|
139
|
+
# Mapper validations become even more useful when used within traits, providing way of very flexible validation sets.
|
140
|
+
#
|
141
|
+
# === Callbacks
|
142
|
+
#
|
143
|
+
# Since mappers include <tt>ActiveModel::Validation</tt>, they already support ActiveSupport's callbacks.
|
144
|
+
# Additionally, <tt>:save</tt> callbacks have been defined (i.e. there have been define_callbacks <tt>:save</tt>
|
145
|
+
# call for <tt>FlatMap::Mapper</tt>). This allows you to control flow of mapper saving:
|
146
|
+
#
|
147
|
+
# set_callback :save, :before, :set_model_validation
|
148
|
+
#
|
149
|
+
# def set_model_validation
|
150
|
+
# target.use_validation :some_themis_validation
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# === Skipping
|
154
|
+
#
|
155
|
+
# In some cases, it is required to omit mapper processing after it has been created within mounting chain. If
|
156
|
+
# <tt>skip!</tt> method is called on mapper, it will return <tt>true</tt> for <tt>valid?</tt> and <tt>save</tt>
|
157
|
+
# method calls without performing any other operations. For example:
|
158
|
+
#
|
159
|
+
# class CustomerMapper < FlatMap::Mapper
|
160
|
+
# # some definitions
|
161
|
+
#
|
162
|
+
# trait :product_selection do
|
163
|
+
# attr_reader :selected_product_id
|
164
|
+
#
|
165
|
+
# mount :product
|
166
|
+
#
|
167
|
+
# set_callback :validate, :before, :ignore_new_product
|
168
|
+
#
|
169
|
+
# def ignore_new_bank_account
|
170
|
+
# mounting(:product).skip! if product_selected?
|
171
|
+
# end
|
172
|
+
#
|
173
|
+
# # some more definitions
|
174
|
+
# end
|
175
|
+
# end
|
176
|
+
#
|
177
|
+
# === Attribute Methods
|
178
|
+
#
|
179
|
+
# All mappers have the ability to read and write values via method calls:
|
180
|
+
#
|
181
|
+
# mapper.read[:first_name] # => John
|
182
|
+
# mapper.first_name # => 'John'
|
183
|
+
# mapper.last_name = 'Smith'
|
184
|
+
class ModelMapper < OpenMapper
|
185
|
+
extend ActiveSupport::Autoload
|
186
|
+
|
187
|
+
autoload :Persistence
|
188
|
+
autoload :Skipping
|
189
|
+
|
190
|
+
include Persistence
|
191
|
+
include Skipping
|
192
|
+
|
193
|
+
delegate :logger, :to => :target
|
194
|
+
end
|
195
|
+
end
|