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