HornsAndHooves-flat_map 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|