flat_map 0.0.3
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 +15 -0
- data/.gitignore +31 -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/LICENSE +20 -0
- data/README.markdown +211 -0
- data/Rakefile +15 -0
- data/flat_map.gemspec +30 -0
- data/lib/flat_map.rb +9 -0
- data/lib/flat_map/base_mapper.rb +95 -0
- data/lib/flat_map/base_mapper/attribute_methods.rb +54 -0
- data/lib/flat_map/base_mapper/factory.rb +238 -0
- data/lib/flat_map/base_mapper/mapping.rb +123 -0
- data/lib/flat_map/base_mapper/mounting.rb +168 -0
- data/lib/flat_map/base_mapper/persistence.rb +145 -0
- data/lib/flat_map/base_mapper/skipping.rb +62 -0
- data/lib/flat_map/base_mapper/traits.rb +94 -0
- data/lib/flat_map/empty_mapper.rb +29 -0
- data/lib/flat_map/errors.rb +57 -0
- data/lib/flat_map/mapper.rb +213 -0
- data/lib/flat_map/mapper/skipping.rb +45 -0
- data/lib/flat_map/mapper/targeting.rb +130 -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/version.rb +3 -0
- data/spec/flat_map/empty_mapper_spec.rb +36 -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 +258 -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/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/spec_helper.rb +7 -0
- data/tmp/metric_fu/_data/20131218.yml +6902 -0
- data/tmp/metric_fu/_data/20131219.yml +6726 -0
- metadata +184 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# This module allows mappers to return and assign values via method calls
|
3
|
+
# which names correspond to names of mappings defined within the mapper.
|
4
|
+
#
|
5
|
+
# This methods are defined within anonymous module that will extend
|
6
|
+
# mapper on first usage of this methods.
|
7
|
+
#
|
8
|
+
# NOTE: :to_ary method is called internally by Ruby 1.9.3 when we call
|
9
|
+
# something like [mapper].flatten. And we DO want default behavior
|
10
|
+
# for handling this missing method.
|
11
|
+
module BaseMapper::AttributeMethods
|
12
|
+
# Lazily define reader and writer methods for all mappings available
|
13
|
+
# to the mapper, and extend +self+ with it.
|
14
|
+
def method_missing(name, *args, &block)
|
15
|
+
if name == :to_ary ||
|
16
|
+
@attribute_methods_defined ||
|
17
|
+
FlatMap::BaseMapper.protected_instance_methods.include?(name)
|
18
|
+
return super
|
19
|
+
end
|
20
|
+
|
21
|
+
mappings = all_mappings
|
22
|
+
valid_names = mappings.map{ |_mapping|
|
23
|
+
mapping_full_name = _mapping.full_name
|
24
|
+
[ mapping_full_name,
|
25
|
+
"#{mapping_full_name}=".to_sym ]
|
26
|
+
}.flatten
|
27
|
+
|
28
|
+
return super unless valid_names.include?(name)
|
29
|
+
|
30
|
+
extend attribute_methods(mappings)
|
31
|
+
@attribute_methods_defined = true
|
32
|
+
send(name, *args, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Define anonymous module with reader and writer methods for
|
36
|
+
# all the +mappings+ being passed.
|
37
|
+
#
|
38
|
+
# @param [Array<FlatMap::Mapping>] mappings list of mappings
|
39
|
+
# @return [Module] module with method definitions
|
40
|
+
def attribute_methods(mappings)
|
41
|
+
Module.new do
|
42
|
+
mappings.each do |_mapping|
|
43
|
+
mapping_full_name = _mapping.full_name
|
44
|
+
define_method(mapping_full_name){ |*args| _mapping.read(*args) }
|
45
|
+
|
46
|
+
define_method("#{mapping_full_name}=") do |value|
|
47
|
+
_mapping.write(value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
private :attribute_methods
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# Mapper factory objects are used to store mounting and trait definitions
|
3
|
+
# and to instantiate and setup corresponding mapper objects thereafter.
|
4
|
+
# Factory objects are stored by mapper classes in opposite to actual
|
5
|
+
# mounted mappers that are stored by mapper objects themselves.
|
6
|
+
class BaseMapper::Factory
|
7
|
+
# Initializes factory with an identifier (name of a mounted mapper,
|
8
|
+
# or the actual class for a trait) and a set of options. Those args
|
9
|
+
# are used to create actual mapper object for the host mapper.
|
10
|
+
#
|
11
|
+
# @param [Symbol, Class] identifier name of a mapper or mapper class
|
12
|
+
# itself
|
13
|
+
# @param [Hash] options
|
14
|
+
def initialize(identifier, options = {}, &block)
|
15
|
+
@identifier, @options, @extension = identifier, options, block
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return +true+ if factory defines a trait.
|
19
|
+
#
|
20
|
+
# @return [Boolean]
|
21
|
+
def traited?
|
22
|
+
@identifier.is_a?(Class)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return the name of the mapper being defined by the factory.
|
26
|
+
# Return +nil+ for the traited factory.
|
27
|
+
#
|
28
|
+
# @return [Symbol, nil]
|
29
|
+
def name
|
30
|
+
traited? ? nil : @identifier
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return the trait name if the factory defines a trait.
|
34
|
+
def trait_name
|
35
|
+
@options[:trait_name] if traited?
|
36
|
+
end
|
37
|
+
|
38
|
+
# Return the list of traits that should be applied for a mapper being
|
39
|
+
# mounted on a host mapper.
|
40
|
+
#
|
41
|
+
# @return [Array<Symbol>] list of traits
|
42
|
+
def traits
|
43
|
+
Array(@options[:traits]).compact
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return the anonymous trait class if the factory defines a trait.
|
47
|
+
# Fetch and return the class of a mapper defined by a symbol.
|
48
|
+
#
|
49
|
+
# @return [Class] ancestor of {FlatMap::BaseMapper}
|
50
|
+
def mapper_class
|
51
|
+
return @identifier if traited?
|
52
|
+
|
53
|
+
class_name = @options[:mapper_class_name] || "#{name.to_s.camelize}Mapper"
|
54
|
+
class_name.constantize
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return +true+ if factory should create targeted mapper.
|
58
|
+
#
|
59
|
+
# @return [Boolean]
|
60
|
+
def targeted_mount?
|
61
|
+
mapper_class < Mapper
|
62
|
+
end
|
63
|
+
|
64
|
+
# Fetch the target for the mapper being created based on target of a host mapper.
|
65
|
+
#
|
66
|
+
# @param [FlatMap::BaseMapper] mapper Host mapper
|
67
|
+
# @return [Object] target for new mapper
|
68
|
+
def fetch_target_from(mapper)
|
69
|
+
return explicit_target!(mapper) if mapper.is_a?(EmptyMapper) && targeted_mount?
|
70
|
+
|
71
|
+
owner_target = mapper.target
|
72
|
+
|
73
|
+
return owner_target if traited?
|
74
|
+
|
75
|
+
explicit_target(owner_target) ||
|
76
|
+
target_from_association(owner_target) ||
|
77
|
+
target_from_name(owner_target)
|
78
|
+
end
|
79
|
+
|
80
|
+
# When creating mappers mounted on top of EmptyMapper, target cannot be implicitly
|
81
|
+
# fetched from it and should be specified explicitly.
|
82
|
+
#
|
83
|
+
# @param [FlatMap::EmptyMapper] mapper Host empty mapper
|
84
|
+
# @return [Object] target for new mapper
|
85
|
+
def explicit_target!(mapper)
|
86
|
+
target = @options[:target]
|
87
|
+
|
88
|
+
if target.present?
|
89
|
+
case target
|
90
|
+
when Proc then target.call
|
91
|
+
when Symbol then mapper.send(target)
|
92
|
+
else target
|
93
|
+
end
|
94
|
+
else
|
95
|
+
raise Mapper::Targeting::NoTargetError.new(mapper_class)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Try to use explicit target definition passed in options to fetch a
|
100
|
+
# target. If this value is a +Proc+, will call it with owner target as
|
101
|
+
# argument.
|
102
|
+
#
|
103
|
+
# @param [Object] owner_target
|
104
|
+
# @return [Object, nil] target for new mapper.
|
105
|
+
def explicit_target(owner_target)
|
106
|
+
if @options.key?(:target)
|
107
|
+
target = @options[:target]
|
108
|
+
if target.is_a? Proc
|
109
|
+
target.call(owner_target)
|
110
|
+
else
|
111
|
+
target
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Try to fetch the target for a new mapper being mounted, based on
|
117
|
+
# correspondence of the mounting name and presence of the association
|
118
|
+
# with a similar name in the host mapper.
|
119
|
+
#
|
120
|
+
# For example:
|
121
|
+
# class Foo < ActiveRecord::Base
|
122
|
+
# has_one :baz
|
123
|
+
# has_many :bars
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# class FooMapper < FlatMap::Mapper
|
127
|
+
# # target of this mapper is the instance of Foo. Lets reference it as 'foo'
|
128
|
+
# mount :baz # This will look for BazMapper, and will try to fetch a target for
|
129
|
+
# # it based on :has_one association, i.e. foo.baz || foo.build_baz
|
130
|
+
#
|
131
|
+
# mount :bar # This will look for BarMapper, and will try to fetch a target for
|
132
|
+
# # it based on :has_many association, i.e. foo.bars.build
|
133
|
+
# end
|
134
|
+
def target_from_association(owner_target)
|
135
|
+
return unless owner_target.kind_of?(ActiveRecord::Base)
|
136
|
+
|
137
|
+
reflection = reflection_from_target(owner_target)
|
138
|
+
return unless reflection.present?
|
139
|
+
|
140
|
+
reflection_macro = reflection.macro
|
141
|
+
case
|
142
|
+
when reflection_macro == :has_one && reflection.options[:is_current]
|
143
|
+
owner_target.send("effective_#{name}")
|
144
|
+
when reflection_macro == :has_one || reflection_macro == :belongs_to
|
145
|
+
owner_target.send(name) || owner_target.send("build_#{name}")
|
146
|
+
when reflection_macro == :has_many
|
147
|
+
owner_target.association(reflection.name).build
|
148
|
+
else # nil
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Try to retreive an association reflection that has a name corresponding
|
153
|
+
# to the one of +self+
|
154
|
+
#
|
155
|
+
# @param [ActiveRecord::Base] target
|
156
|
+
# @return [ActiveRecord::Reflection::AssociationReflection, nil]
|
157
|
+
def reflection_from_target(target)
|
158
|
+
return unless name.present? && target.is_a?(ActiveRecord::Base)
|
159
|
+
target_class = target.class
|
160
|
+
reflection = target_class.reflect_on_association(name)
|
161
|
+
reflection || target_class.reflect_on_association(name.to_s.pluralize.to_sym)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Send the name of the mounting to the target of the host mapper, and use
|
165
|
+
# return value as a target for a mapper being created.
|
166
|
+
#
|
167
|
+
# @return [Object]
|
168
|
+
def target_from_name(target)
|
169
|
+
target.send(name)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Return order relative to target of the passed +mapper+ in which mapper to
|
173
|
+
# be created should be saved. In particular, targets of <tt>:belongs_to</tt>
|
174
|
+
# associations should be saved before target of +mapper+ is saved.
|
175
|
+
#
|
176
|
+
# @param [FlatMap::BaseMapper] mapper
|
177
|
+
# @return [Symbol]
|
178
|
+
def fetch_save_order(mapper)
|
179
|
+
return :after if mapper.is_a?(EmptyMapper)
|
180
|
+
|
181
|
+
reflection = reflection_from_target(mapper.target)
|
182
|
+
return unless reflection.present?
|
183
|
+
reflection.macro == :belongs_to ? :before : :after
|
184
|
+
end
|
185
|
+
|
186
|
+
# Create a new mapper object for mounting. If the factory is traited,
|
187
|
+
# the new mapper is a part of a host mapper, and is 'owned' by it.
|
188
|
+
# Otherwise, assign the name of the factory to it to be able to find it
|
189
|
+
# later on.
|
190
|
+
#
|
191
|
+
# @param [FlatMap::BaseMapper] mapper Host mapper
|
192
|
+
# @param [*Symbol] owner_traits List of traits to be applied to a newly created mapper
|
193
|
+
def create(mapper, *owner_traits)
|
194
|
+
save_order = @options[:save] || fetch_save_order(mapper) || :after
|
195
|
+
all_traits = (traits + owner_traits).uniq
|
196
|
+
|
197
|
+
new_one =
|
198
|
+
if targeted_mount? then
|
199
|
+
mapper_class.new(fetch_target_from(mapper), *all_traits, &@extension)
|
200
|
+
else
|
201
|
+
mapper_class.new(*all_traits, &@extension)
|
202
|
+
end
|
203
|
+
|
204
|
+
if traited?
|
205
|
+
new_one.owner = mapper
|
206
|
+
else
|
207
|
+
new_one.host = mapper
|
208
|
+
new_one.name = @identifier
|
209
|
+
new_one.save_order = save_order
|
210
|
+
|
211
|
+
if (suffix = @options[:suffix] || mapper.suffix).present?
|
212
|
+
new_one.suffix = suffix
|
213
|
+
new_one.name = :"#{@identifier}_#{suffix}"
|
214
|
+
else
|
215
|
+
new_one.name = @identifier
|
216
|
+
end
|
217
|
+
end
|
218
|
+
new_one
|
219
|
+
end
|
220
|
+
|
221
|
+
# Return +true+ if the factory is required to be able to apply a trait
|
222
|
+
# for the host mapper.
|
223
|
+
# For example, it is required if its name is listed in +traits+.
|
224
|
+
# It is also required if it has nested traits with names listed in +traits+.
|
225
|
+
#
|
226
|
+
# @param [Array<Symbol>] traits list of traits
|
227
|
+
# @return [Boolean]
|
228
|
+
def required_for_any_trait?(traits)
|
229
|
+
return true unless traited?
|
230
|
+
|
231
|
+
traits.include?(trait_name) ||
|
232
|
+
mapper_class.mountings.any?{ |factory|
|
233
|
+
factory.traited? &&
|
234
|
+
factory.required_for_any_trait?(traits)
|
235
|
+
}
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# This module hosts all definitions required to define and use mapping
|
3
|
+
# functionality within mapper classes. This includes mapping definition
|
4
|
+
# methods and basic reading and writing methods.
|
5
|
+
module BaseMapper::Mapping
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Mapping class macros
|
9
|
+
module ClassMethods
|
10
|
+
# Mapping-modifier options to distinguish options from mappings
|
11
|
+
# themselves:
|
12
|
+
MAPPING_OPTIONS = [:reader, :writer, :format, :multiparam].freeze
|
13
|
+
|
14
|
+
# Define single or multiple mappings at a time. Usually, a Hash
|
15
|
+
# is passed in a form !{mapping_name => target_attribute}. All keys
|
16
|
+
# that are listed under {MAPPING_OPTIONS} will be extracted and used
|
17
|
+
# as modifiers for new mappings.
|
18
|
+
#
|
19
|
+
# Also, mapping names may be listed as an array preceding the hash.
|
20
|
+
# In that case, its elements are treated as !{mapping_name => mapping_name}
|
21
|
+
# mapping elements.
|
22
|
+
#
|
23
|
+
# Example:
|
24
|
+
# map :brand, :account_source => :source, :format => :enum
|
25
|
+
# # is equivalent to:
|
26
|
+
# map :brand => :brand, :format => :enum
|
27
|
+
# map :account_source => :source, :format => :enum
|
28
|
+
def map(*args)
|
29
|
+
mapping_options = args.extract_options!
|
30
|
+
mappings = mapping_options.slice!(*MAPPING_OPTIONS)
|
31
|
+
mappings_from_array = args.zip(args).flatten
|
32
|
+
mappings.merge!(Hash[*mappings_from_array]) unless mappings_from_array.empty?
|
33
|
+
|
34
|
+
define_mappings(mappings, mapping_options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Define a set of +mappings+, passed as a {Hash} with +options+ as modifiers.
|
38
|
+
# Eventually, adds a mapping factories to list of class mappings. Those
|
39
|
+
# factory objects are used to create actual mappings for specific mapper
|
40
|
+
# object.
|
41
|
+
#
|
42
|
+
# @param [Hash] mappings
|
43
|
+
# @param [Hash] options
|
44
|
+
# @return [Array<FlatMap::Mapping::Factory>] list of mappings
|
45
|
+
def define_mappings(mappings, options)
|
46
|
+
mappings.each do |name, target_attribute|
|
47
|
+
self.mappings << FlatMap::Mapping::Factory.new(name, target_attribute, options)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
private :define_mappings
|
51
|
+
|
52
|
+
# List of class mappings (mapping factories).
|
53
|
+
#
|
54
|
+
# @return [Array<FlatMap::Mapping::Factory>]
|
55
|
+
def mappings
|
56
|
+
@mappings ||= []
|
57
|
+
end
|
58
|
+
|
59
|
+
# Writer for mappings.
|
60
|
+
def mappings=(val)
|
61
|
+
@mappings = val
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Send passed +params+ +write_from_params+ method of each
|
66
|
+
# of the mappings of +self+.
|
67
|
+
#
|
68
|
+
# Overloaded in {BaseMapper::Mounting}.
|
69
|
+
#
|
70
|
+
# @param [Hash] params
|
71
|
+
# @return [Hash] params
|
72
|
+
def write(params)
|
73
|
+
mappings.each do |mapping|
|
74
|
+
mapping.write_from_params(params)
|
75
|
+
end
|
76
|
+
params
|
77
|
+
end
|
78
|
+
|
79
|
+
# Send +read_as_params+ method to all mappings associated with
|
80
|
+
# self. And consolidate results in a single hash.
|
81
|
+
#
|
82
|
+
# @return [Hash] set of read values
|
83
|
+
def read
|
84
|
+
mappings.inject({}) do |params, mapping|
|
85
|
+
params.merge(mapping.read_as_params)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Retrieve mapping value via its name, which might differ from its
|
90
|
+
# full_name, if suffix was used.
|
91
|
+
#
|
92
|
+
# @param [Symbol] name
|
93
|
+
def [](name)
|
94
|
+
mapping(name).try(:read)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Write value to mapping specified by name, which might differ from its
|
98
|
+
# full_name, if suffix was used.
|
99
|
+
#
|
100
|
+
# @param [Symbol] name
|
101
|
+
# @param [Object] value
|
102
|
+
def []=(name, value)
|
103
|
+
mapping(name).try(:write, value)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Lookup mapping by its name, which might differ from its
|
107
|
+
# full_name, if suffix was used.
|
108
|
+
#
|
109
|
+
# @param [Symbol] name
|
110
|
+
# @return [FlatMap::Mapping]
|
111
|
+
def mapping(name)
|
112
|
+
mappings.find{ |_mapping| _mapping.name == name }
|
113
|
+
end
|
114
|
+
|
115
|
+
# Return a list of mappings associated to +self+.
|
116
|
+
#
|
117
|
+
# @return [FlatMap::Mapping]
|
118
|
+
def mappings
|
119
|
+
@mappings ||= self.class.mappings.map{ |factory| factory.create(self) }
|
120
|
+
end
|
121
|
+
private :mappings
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# This module hosts definitions required for mounting functionality
|
3
|
+
# of the mappers. This includes mounting definition methods, overloaded
|
4
|
+
# +read+ and +write+ methods to make them aware of mounted mappers and
|
5
|
+
# other methods.
|
6
|
+
#
|
7
|
+
# Also, the +method_missing+ method is defined here to delegate the missing
|
8
|
+
# method to the very first mounted mapper that responds to it.
|
9
|
+
module BaseMapper::Mounting
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
included do
|
13
|
+
attr_accessor :save_order
|
14
|
+
end
|
15
|
+
|
16
|
+
# Mounting class macros.
|
17
|
+
module ClassMethods
|
18
|
+
# Add a mounting factory to a list of factories of a class
|
19
|
+
# These factories are used to create actual mounted objects,
|
20
|
+
# which are mappers themselves, associated to a particular
|
21
|
+
# mapper.
|
22
|
+
#
|
23
|
+
# @param [*Object] args
|
24
|
+
# @return [Array<FlatMap::BaseMapper::Factory>]
|
25
|
+
def mount(*args, &block)
|
26
|
+
mountings << FlatMap::BaseMapper::Factory.new(*args, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
# List of mountings (factories) of a class.
|
30
|
+
#
|
31
|
+
# @return [Array<FlatMap::BaseMapper>]
|
32
|
+
def mountings
|
33
|
+
@mountings ||= []
|
34
|
+
end
|
35
|
+
|
36
|
+
# Writer for @mountings.
|
37
|
+
def mountings=(val)
|
38
|
+
@mountings = val
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Extend original {Mapping#read} method to take
|
43
|
+
# into account mountings of mounted mappers.
|
44
|
+
#
|
45
|
+
# @return [Hash] read values
|
46
|
+
def read
|
47
|
+
mountings.inject(super) do |result, mapper|
|
48
|
+
result.merge(mapper.read)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Extend original {Mapping#write} method to pass
|
53
|
+
# +params+ to mounted mappers.
|
54
|
+
#
|
55
|
+
# Overridden in {Persistence}. Left here for consistency.
|
56
|
+
#
|
57
|
+
# @param [Hash] params
|
58
|
+
# @return [Hash] params
|
59
|
+
def write(params)
|
60
|
+
super
|
61
|
+
|
62
|
+
mountings.each do |mapper|
|
63
|
+
mapper.write(params)
|
64
|
+
end
|
65
|
+
|
66
|
+
params
|
67
|
+
end
|
68
|
+
|
69
|
+
# Return list of mappings to be saved before saving target of +self+
|
70
|
+
#
|
71
|
+
# @return [Array<FlatMap::BaseMapper>]
|
72
|
+
def before_save_mountings
|
73
|
+
nearest_mountings.select{ |mount| mount.save_order == :before }
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return list of mappings to be saved after target of +self+ was saved
|
77
|
+
#
|
78
|
+
# @return [Array<FlatMap::BaseMapper>]
|
79
|
+
def after_save_mountings
|
80
|
+
nearest_mountings.reject{ |mount| mount.save_order == :before }
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return all mountings that are mouted on +self+ directly or through
|
84
|
+
# traits.
|
85
|
+
#
|
86
|
+
# @return [Array<FlatMap::BaseMapper>]
|
87
|
+
def nearest_mountings
|
88
|
+
mountings.map{ |mount| mount.owned? ? mount.nearest_mountings : mount }.flatten
|
89
|
+
end
|
90
|
+
|
91
|
+
# Return a list of all mountings (mapper objects) associated with +self+.
|
92
|
+
#
|
93
|
+
# Overridden in {Traits}.
|
94
|
+
#
|
95
|
+
# @return [Array<FlatMap::BaseMapper>]
|
96
|
+
def mountings
|
97
|
+
@mountings ||= self.class.mountings.map{ |factory| factory.create(self) }
|
98
|
+
end
|
99
|
+
|
100
|
+
# Return a mapping with the name that corresponds to passed +mounting_name+,
|
101
|
+
# if it exists.
|
102
|
+
#
|
103
|
+
# @return [FlatMap::Mapping, nil]
|
104
|
+
def mounting(mounting_name, is_deep = true)
|
105
|
+
list = is_deep ? all_mountings : mountings
|
106
|
+
list.find{ |mount| mount.name == mounting_name }
|
107
|
+
end
|
108
|
+
|
109
|
+
# Return a list of all mounted mappers. If +self+ is a trait, return a
|
110
|
+
# list of all mountings of the owner. This will allow separate traits
|
111
|
+
# to share methods via method_missing pattern.
|
112
|
+
#
|
113
|
+
# @return [Array<FlatMap::BaseMapper>] mounted mappers (including traits)
|
114
|
+
def all_mountings
|
115
|
+
return all_nested_mountings.unshift(self) unless owned?
|
116
|
+
owner.all_mountings
|
117
|
+
end
|
118
|
+
protected :all_mountings
|
119
|
+
|
120
|
+
# Return a list of mountings that are accessible by a named mapper.
|
121
|
+
#
|
122
|
+
# @return [Array<FlatMap::BaseMapper>]
|
123
|
+
def all_nested_mountings
|
124
|
+
mountings.dup.concat(mountings.map{ |mount| mount.send(:all_nested_mountings) }).flatten
|
125
|
+
end
|
126
|
+
protected :all_nested_mountings
|
127
|
+
|
128
|
+
# Return a list of all mappings, i.e. mappings associated to +self+,
|
129
|
+
# plus mappings of all deeply mounted mappers. If +self+ is the owner,
|
130
|
+
# that means it is a part (a trait) of a host mapper. That means that all
|
131
|
+
# mappings of it actually correspond to all mappings of a host mapper.
|
132
|
+
# This allows to define things like validation in a trait where access
|
133
|
+
# to top-level mappings is required.
|
134
|
+
#
|
135
|
+
# @return [Array<FlatMap::Mapping>]
|
136
|
+
def all_mappings
|
137
|
+
return all_nested_mappings unless owned?
|
138
|
+
owner.all_mappings
|
139
|
+
end
|
140
|
+
protected :all_mappings
|
141
|
+
|
142
|
+
# Return a list of all mappings, i.e. mappings that associated to +self+
|
143
|
+
# plus mappings of all deeply mounted mappers.
|
144
|
+
#
|
145
|
+
# @return [Array<FlatMap::Mapping>]
|
146
|
+
def all_nested_mappings
|
147
|
+
(mappings + mountings.map{ |mount| mount.send(:all_nested_mappings) }).flatten
|
148
|
+
end
|
149
|
+
protected :all_nested_mappings
|
150
|
+
|
151
|
+
# Delegate missing method to any mounted mapping that respond to it,
|
152
|
+
# unless those methods are protected methods of FlatMap::BaseMapper.
|
153
|
+
#
|
154
|
+
# NOTE: :to_ary method is called internally by Ruby 1.9.3 when we call
|
155
|
+
# something like [mapper].flatten. And we DO want default behavior
|
156
|
+
# for handling this missing method.
|
157
|
+
def method_missing(name, *args, &block)
|
158
|
+
return super if name == :to_ary ||
|
159
|
+
FlatMap::BaseMapper.protected_instance_methods.include?(name)
|
160
|
+
|
161
|
+
return self[name] if mapping(name).present?
|
162
|
+
|
163
|
+
mount = all_mountings.find{ |_mount| _mount.respond_to?(name) }
|
164
|
+
return super if mount.nil?
|
165
|
+
mount.send(name, *args, &block)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|