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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +34 -0
  3. data/.metrics +17 -0
  4. data/.rspec +4 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +9 -0
  8. data/Gemfile +20 -0
  9. data/HornsAndHooves-flat_map.gemspec +29 -0
  10. data/LICENSE +21 -0
  11. data/README.markdown +214 -0
  12. data/Rakefile +15 -0
  13. data/lib/flat_map.rb +14 -0
  14. data/lib/flat_map/errors.rb +57 -0
  15. data/lib/flat_map/mapping.rb +124 -0
  16. data/lib/flat_map/mapping/factory.rb +21 -0
  17. data/lib/flat_map/mapping/reader.rb +12 -0
  18. data/lib/flat_map/mapping/reader/basic.rb +28 -0
  19. data/lib/flat_map/mapping/reader/formatted.rb +45 -0
  20. data/lib/flat_map/mapping/reader/formatted/formats.rb +28 -0
  21. data/lib/flat_map/mapping/reader/method.rb +25 -0
  22. data/lib/flat_map/mapping/reader/proc.rb +15 -0
  23. data/lib/flat_map/mapping/writer.rb +11 -0
  24. data/lib/flat_map/mapping/writer/basic.rb +25 -0
  25. data/lib/flat_map/mapping/writer/method.rb +28 -0
  26. data/lib/flat_map/mapping/writer/proc.rb +18 -0
  27. data/lib/flat_map/model_mapper.rb +195 -0
  28. data/lib/flat_map/model_mapper/persistence.rb +108 -0
  29. data/lib/flat_map/model_mapper/skipping.rb +45 -0
  30. data/lib/flat_map/open_mapper.rb +113 -0
  31. data/lib/flat_map/open_mapper/attribute_methods.rb +55 -0
  32. data/lib/flat_map/open_mapper/factory.rb +244 -0
  33. data/lib/flat_map/open_mapper/mapping.rb +123 -0
  34. data/lib/flat_map/open_mapper/mounting.rb +168 -0
  35. data/lib/flat_map/open_mapper/persistence.rb +178 -0
  36. data/lib/flat_map/open_mapper/skipping.rb +66 -0
  37. data/lib/flat_map/open_mapper/traits.rb +95 -0
  38. data/lib/flat_map/version.rb +3 -0
  39. data/spec/flat_map/errors_spec.rb +23 -0
  40. data/spec/flat_map/mapper/attribute_methods_spec.rb +36 -0
  41. data/spec/flat_map/mapper/callbacks_spec.rb +76 -0
  42. data/spec/flat_map/mapper/factory_spec.rb +285 -0
  43. data/spec/flat_map/mapper/mapping_spec.rb +98 -0
  44. data/spec/flat_map/mapper/mounting_spec.rb +142 -0
  45. data/spec/flat_map/mapper/persistence_spec.rb +152 -0
  46. data/spec/flat_map/mapper/skipping_spec.rb +91 -0
  47. data/spec/flat_map/mapper/targeting_spec.rb +156 -0
  48. data/spec/flat_map/mapper/traits_spec.rb +172 -0
  49. data/spec/flat_map/mapper/validations_spec.rb +72 -0
  50. data/spec/flat_map/mapper_spec.rb +9 -0
  51. data/spec/flat_map/mapping/factory_spec.rb +12 -0
  52. data/spec/flat_map/mapping/reader/basic_spec.rb +15 -0
  53. data/spec/flat_map/mapping/reader/formatted_spec.rb +62 -0
  54. data/spec/flat_map/mapping/reader/method_spec.rb +13 -0
  55. data/spec/flat_map/mapping/reader/proc_spec.rb +13 -0
  56. data/spec/flat_map/mapping/writer/basic_spec.rb +15 -0
  57. data/spec/flat_map/mapping/writer/method_spec.rb +13 -0
  58. data/spec/flat_map/mapping/writer/proc_spec.rb +13 -0
  59. data/spec/flat_map/mapping_spec.rb +123 -0
  60. data/spec/flat_map/open_mapper_spec.rb +19 -0
  61. data/spec/spec_helper.rb +7 -0
  62. data/tmp/metric_fu/_data/20131218.yml +6902 -0
  63. data/tmp/metric_fu/_data/20131219.yml +6726 -0
  64. metadata +220 -0
@@ -0,0 +1,66 @@
1
+ module FlatMap
2
+ # This helper module provides helper functionality that allow to
3
+ # exclude specific mapper from a processing chain.
4
+ module OpenMapper::Skipping
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :ActiveRecord
8
+
9
+ # Mark self as skipped, i.e. it will not be subject of
10
+ # validation and saving chain.
11
+ #
12
+ # @return [Object]
13
+ def skip!
14
+ @_skip_processing = true
15
+ end
16
+
17
+ # Remove "skip" mark from +self+ and "destroyed" flag from
18
+ # the target.
19
+ #
20
+ # @return [Object]
21
+ def use!
22
+ @_skip_processing = nil
23
+ end
24
+
25
+ # Return +true+ if +self+ was marked for skipping.
26
+ #
27
+ # @return [Boolean]
28
+ def skipped?
29
+ !!@_skip_processing
30
+ end
31
+
32
+ # Override {FlatMap::OpenMapper::Persistence#valid?} to
33
+ # force it to return +true+ if +self+ is marked for skipping.
34
+ #
35
+ # @param [Symbol] context useless context parameter to make it compatible with
36
+ # ActiveRecord models.
37
+ #
38
+ # @return [Boolean]
39
+ def valid?(context = nil)
40
+ skipped? || super
41
+ end
42
+
43
+ # Override {FlatMap::OpenMapper::Persistence#save} method to
44
+ # force it to return +true+ if +self+ is marked for skipping.
45
+ #
46
+ # @return [Boolean]
47
+ def save
48
+ skipped? || super
49
+ end
50
+
51
+ # Override {FlatMap::OpenMapper::Persistence#shallow_save} method
52
+ # to make it possible to skip traits.
53
+ #
54
+ # @return [Boolean]
55
+ def shallow_save
56
+ skipped? || super
57
+ end
58
+
59
+ # Mark self as used and then delegated to original
60
+ # {FlatMap::OpenMapper::Persistence#write}.
61
+ def write(*)
62
+ use!
63
+ super
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,95 @@
1
+ module FlatMap
2
+ # This small module allows mappers to define traits, which technically
3
+ # means mounting anonymous mappers, attached to host one.
4
+ #
5
+ # Also, FlatMap::OpenMapper::Mounting#mountings completely overridden
6
+ # here to support special trait behavior.
7
+ module OpenMapper::Traits
8
+ extend ActiveSupport::Concern
9
+
10
+ # Traits class macros
11
+ module ClassMethods
12
+ # Define a trait for a mapper class. In implementation terms, a trait
13
+ # is nothing more than a mounted mapper, owned by a host mapper.
14
+ # It shares all mappings with it.
15
+ # The block is passed as a body of the anonymous mapper class.
16
+ #
17
+ # @param [Symbol] name
18
+ def trait(name, &block)
19
+ base_class = self < FlatMap::Mapper ? FlatMap::Mapper : FlatMap::OpenMapper
20
+ mapper_class = Class.new(base_class, &block)
21
+ mapper_class_name = "#{ancestors.first.name}#{name.to_s.camelize}Trait"
22
+ mapper_class.singleton_class.send(:define_method, :name){ mapper_class_name }
23
+ mount mapper_class, :trait_name => name
24
+ end
25
+ end
26
+
27
+ # Override the original {FlatMap::OpenMapper::Mounting#mountings}
28
+ # method to filter out those traited mappers that are not required for
29
+ # trait setup of +self+. Also, handle any inline extension that may be
30
+ # defined on the mounting mapper, which is attached as a singleton trait.
31
+ #
32
+ # @return [Array<FlatMap::OpenMapper>]
33
+ def mountings
34
+ @mountings ||= begin
35
+ mountings = self.class.mountings.reject do |factory|
36
+ factory.traited? && !factory.required_for_any_trait?(traits)
37
+ end
38
+ mountings.concat(singleton_class.mountings)
39
+ mountings.map{ |factory| factory.create(self, *traits) }
40
+ end
41
+ end
42
+
43
+ # Return a list of all mountings that represent full picture of +self+, i.e.
44
+ # +self+ and all traits, including deeply nested, that are mounted on self
45
+ #
46
+ # @return [Array<FlatMap::OpenMapper>]
47
+ def self_mountings
48
+ mountings.select(&:owned?).map{ |mount| mount.self_mountings }.flatten.concat [self]
49
+ end
50
+
51
+ # Try to find trait mapper with name that corresponds to +trait_name+
52
+ # Used internally to manipulate such mappers (for example, skip some traits)
53
+ # in some scenarios.
54
+ #
55
+ # @param [Symbol] trait_name
56
+ # @return [FlatMap::OpenMapper, nil]
57
+ def trait(trait_name)
58
+ self_mountings.find{ |mount| mount.class.name.underscore =~ /#{trait_name}_trait$/ }
59
+ end
60
+
61
+ # Return :extension trait, if present
62
+ #
63
+ # @return [FlatMap::OpenMapper]
64
+ def extension
65
+ trait(:extension)
66
+ end
67
+
68
+ # Return only mountings that are actually traits for host mapper.
69
+ #
70
+ # @return [Array<FlatMap::OpenMapper>]
71
+ def trait_mountings
72
+ result = mountings.select{ |mount| mount.owned? }
73
+ # mapper extension has more priority then traits, and
74
+ # has to be processed first.
75
+ result.unshift(result.pop) if result.length > 1 && result[-1].extension?
76
+ result
77
+ end
78
+ protected :trait_mountings
79
+
80
+ # Return only mountings that correspond to external mappers.
81
+ #
82
+ # @return [Array<FlatMap::OpenMapper>]
83
+ def mapper_mountings
84
+ mountings.select{ |mount| !mount.owned? }
85
+ end
86
+ protected :mapper_mountings
87
+
88
+ # Return +true+ if +self+ is extension of host mapper.
89
+ #
90
+ # @return [Boolean]
91
+ def extension?
92
+ owned? && self.class.name =~ /ExtensionTrait$/
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,3 @@
1
+ module FlatMap # :nodoc:
2
+ VERSION = "0.2.0" # :nodoc:
3
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe FlatMap::Errors do
4
+ let(:errors) {
5
+ described_class.new(
6
+ double( 'mapper', :suffixed? => true,
7
+ :suffix => 'foo',
8
+ 'attr_foo' => 2)
9
+ )
10
+ }
11
+
12
+ it "preserved errors should appear on #empty? call exactly once" do
13
+ errors.preserve :base, 'an error'
14
+ errors.should_not be_empty
15
+ errors[:base].should == ['an error']
16
+ expect{ errors.empty? }.not_to change{ errors[:base].length }
17
+ end
18
+
19
+ it "should add error to mapper with suffix" do
20
+ errors.add(:attr, 'an error')
21
+ errors[:attr_foo].should == ['an error']
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ module FlatMap
4
+ module AttributeMethodsSpec
5
+ class SpecMapper < Mapper
6
+ map :attr_a, :attr_b
7
+ end
8
+ end
9
+
10
+ describe 'Attribute Methods' do
11
+ let(:target){ OpenStruct.new }
12
+ let(:mapper){ AttributeMethodsSpec::SpecMapper.new(target) }
13
+
14
+ before do
15
+ target.attr_a = 'a'
16
+ target.attr_b = 'b'
17
+ end
18
+
19
+ it 'should be able to read values via method calls' do
20
+ mapper.attr_a.should == 'a'
21
+ mapper.attr_b.should == 'b'
22
+ end
23
+
24
+ it 'should be able to write values via method calls' do
25
+ mapper.attr_a = 'A'
26
+ mapper.attr_b = 'B'
27
+ target.attr_a.should == 'A'
28
+ target.attr_b.should == 'B'
29
+ end
30
+
31
+ it 'should still raise for unknown or private method calls' do
32
+ expect{ mapper.undefined_method }.to raise_error(NoMethodError)
33
+ expect{ mapper.attribute_methods }.to raise_error(NoMethodError)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ module FlatMap
4
+ module CallbacksSpec
5
+ class MountMapper < Mapper
6
+ map :attr_c
7
+
8
+ set_callback :validate, :before, :set_c
9
+
10
+ def set_c
11
+ self.attr_c = 'mounted before validate'
12
+ end
13
+ end
14
+
15
+ class SpecMapper < Mapper
16
+ map :attr_a
17
+
18
+ set_callback :save, :before, :set_a
19
+
20
+ trait :with_b do
21
+ map :attr_b
22
+
23
+ set_callback :validate, :before, :set_b
24
+
25
+ def set_b
26
+ self.attr_b = 'before validate'
27
+ end
28
+ end
29
+
30
+ def set_a
31
+ self.attr_a = 'before save'
32
+ end
33
+
34
+ mount :mount,
35
+ :mapper_class_name => 'FlatMap::CallbacksSpec::MountMapper',
36
+ :target => lambda{ |_| OpenStruct.new }
37
+ end
38
+ end
39
+
40
+ describe 'Callbacks' do
41
+ let(:mapper) do
42
+ CallbacksSpec::SpecMapper.new(OpenStruct.new, :with_b) do
43
+ set_callback :validate, :before, :extension_set_b
44
+
45
+ def extension_set_b
46
+ self.attr_b = 'extension value'
47
+ end
48
+ end
49
+ end
50
+
51
+ it 'should call callbacks once' do
52
+ mapper.should_receive(:set_a).once
53
+ mapper.save
54
+ end
55
+
56
+ specify 'validation callbacks' do
57
+ mapper.valid?
58
+ mapper.attr_a.should be_nil
59
+ mapper.attr_b.should == 'before validate'
60
+ mapper.attr_c.should == 'mounted before validate'
61
+ end
62
+
63
+ specify 'save callbacks' do
64
+ mapper.save
65
+ mapper.attr_a.should == 'before save'
66
+ end
67
+
68
+ context 'extension trait and named traits' do
69
+ it 'should process extension first' do
70
+ mapper.extension.should_receive(:extension_set_b).once.and_call_original
71
+ mapper.valid?
72
+ mapper.attr_b.should == 'before validate'
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,285 @@
1
+ require 'spec_helper'
2
+
3
+ module FlatMap
4
+ describe OpenMapper::Factory do
5
+ let(:trait_class){ Class.new(FlatMap::OpenMapper) }
6
+ let(:mapper){ OpenMapper.build }
7
+
8
+ let(:mount_factory){ OpenMapper::Factory.new(:spec_mount, :traits => :used_traits) }
9
+ let(:trait_factory){ OpenMapper::Factory.new(trait_class, :trait_name => :a_trait) }
10
+ let(:open_factory){ OpenMapper::Factory.new(:some_mount, :open => true) }
11
+
12
+ context 'when used for a trait' do
13
+ subject{ trait_factory }
14
+
15
+ it{ should be_traited }
16
+ its(:name){ should be_nil }
17
+ its(:trait_name){ should == :a_trait }
18
+ its(:mapper_class){ should == trait_class }
19
+ end
20
+
21
+ context 'when used for a mounted mapper' do
22
+ subject{ mount_factory }
23
+
24
+ it{ should_not be_traited }
25
+ its(:name){ should == :spec_mount }
26
+ its(:trait_name){ should be_nil }
27
+ its(:traits){ should == [:used_traits] }
28
+ end
29
+
30
+ context 'when used for an open mapper' do
31
+ it "should have descendant of OpenMapper as mapper_class" do
32
+ open_factory.mapper_class.should < OpenMapper
33
+ open_factory.mapper_class.name.should == 'SomeMountMapper'
34
+ end
35
+
36
+ it "should create and instance of OpenMapper with open struct as a target" do
37
+ mounted = open_factory.create(mapper)
38
+ mounted.target.should be_a(OpenStruct)
39
+ end
40
+ end
41
+
42
+ describe 'behavior' do
43
+ let(:target) { mapper.target }
44
+ let(:other_target) { Object.new }
45
+
46
+ describe '#mapper_class for mounted mappers' do
47
+ class ::SpecMountMapper < FlatMap::ModelMapper; end
48
+ class OpenMapper::Factory::SpecMountMapper < FlatMap::OpenMapper; end
49
+
50
+ it "should be able to fetch class name from name" do
51
+ mount_factory.mapper_class.should == ::SpecMountMapper
52
+ end
53
+
54
+ it "should be able to fetch class from mapper_class" do
55
+ factory = OpenMapper::Factory.new(
56
+ :spec_mount,
57
+ :mapper_class => FlatMap::OpenMapper::Factory::SpecMountMapper
58
+ )
59
+ factory.mapper_class.should == ::FlatMap::OpenMapper::Factory::SpecMountMapper
60
+ end
61
+
62
+ it "should use options if specified" do
63
+ factory = OpenMapper::Factory.new(
64
+ :spec_mount,
65
+ :mapper_class_name => 'FlatMap::OpenMapper::Factory::SpecMountMapper'
66
+ )
67
+ factory.mapper_class.should == ::FlatMap::OpenMapper::Factory::SpecMountMapper
68
+ end
69
+ end
70
+
71
+ describe '#fetch_target_from' do
72
+ it "should return owner's target for traited factory" do
73
+ trait_factory.fetch_target_from(mapper).should == target
74
+ end
75
+
76
+ context 'explicit target' do
77
+ it "should use explicitly specified if applicable" do
78
+ factory = OpenMapper::Factory.new(:spec_mount, :target => other_target)
79
+ factory.fetch_target_from(mapper).should == other_target
80
+ end
81
+
82
+ it "should call Proc and pass owner target to it if Proc is specified as :target" do
83
+ factory = OpenMapper::Factory.new(:spec_mount, :target => lambda{ |obj| obj.foo })
84
+ target.should_receive(:foo).and_return(other_target)
85
+ factory.fetch_target_from(mapper).should == other_target
86
+ end
87
+
88
+ it "should call a method if Symbol is used" do
89
+ factory = OpenMapper::Factory.new(:spec_mount, :target => :foo)
90
+ mapper.should_receive(:foo).and_return(other_target)
91
+ factory.fetch_target_from(mapper).should == other_target
92
+ end
93
+ end
94
+
95
+ context 'target from association' do
96
+ before do
97
+ target.stub(:is_a?).and_call_original
98
+ target.stub(:is_a?).with(ActiveRecord::Base).and_return(true)
99
+ end
100
+
101
+ let(:has_one_current_reflection) {
102
+ double('reflection', :macro => :has_one, :options => {:is_current => true})
103
+ }
104
+ let(:has_one_reflection) {
105
+ double('reflection', :macro => :has_one, :options => {})
106
+ }
107
+ let(:belongs_to_reflection) {
108
+ double('reflection', :macro => :belongs_to)
109
+ }
110
+ let(:has_many_reflection) {
111
+ double('reflection', :macro => :has_many, :name => :spec_mounts)
112
+ }
113
+
114
+ it "should refer to effective name for has_one_current association" do
115
+ # Note: has_one_current is not part of Rails
116
+ mount_factory.should_receive(:reflection_from_target).
117
+ with(target).
118
+ and_return(has_one_current_reflection)
119
+ target.should_receive(:effective_spec_mount).and_return(other_target)
120
+ mount_factory.fetch_target_from(mapper).should == other_target
121
+ end
122
+
123
+ it "should refer to existing association object if possible, " \
124
+ "and build it if it is absent for :has_one" do
125
+ mount_factory.should_receive(:reflection_from_target).
126
+ with(target).
127
+ and_return(has_one_reflection)
128
+ target.should_receive(:spec_mount).and_return(nil)
129
+ target.should_receive(:build_spec_mount).and_return(other_target)
130
+ mount_factory.fetch_target_from(mapper).should == other_target
131
+ end
132
+
133
+ it "should refer to existing association object if possible, " \
134
+ "and build it if it is absent for :belongs_to" do
135
+ mount_factory.should_receive(:reflection_from_target).
136
+ with(target).
137
+ and_return(belongs_to_reflection)
138
+ target.should_receive(:spec_mount).and_return(nil)
139
+ target.should_receive(:build_spec_mount).and_return(other_target)
140
+ mount_factory.fetch_target_from(mapper).should == other_target
141
+ end
142
+
143
+ it "should always build a new record for :has_many association" do
144
+ mount_factory.should_receive(:reflection_from_target).
145
+ with(target).
146
+ and_return(has_many_reflection)
147
+ target.should_receive(:association).with(:spec_mounts)
148
+ target.stub_chain(:association, :build).and_return(other_target)
149
+ mount_factory.fetch_target_from(mapper).should == other_target
150
+ end
151
+
152
+ describe 'reflection_from_target' do
153
+ before{ target.stub(:is_a?).with(ActiveRecord::Base).and_return(true) }
154
+
155
+ it 'should first refer to singular association' do
156
+ target.stub_chain(:class, :reflect_on_association).
157
+ with(:spec_mount).
158
+ and_return(has_one_reflection)
159
+ mount_factory.reflection_from_target(target).should == has_one_reflection
160
+ end
161
+
162
+ it 'should use collection association if singular does not exist' do
163
+ target.stub_chain(:class, :reflect_on_association).
164
+ with(:spec_mount).
165
+ and_return(nil)
166
+ target.stub_chain(:class, :reflect_on_association).
167
+ with(:spec_mounts).
168
+ and_return(has_many_reflection)
169
+ mount_factory.reflection_from_target(target).should == has_many_reflection
170
+ end
171
+ end
172
+ end
173
+
174
+ context 'target from name' do
175
+ it 'should simply send method to owner target' do
176
+ target.should_receive(:spec_mount).and_return(other_target)
177
+ mount_factory.fetch_target_from(mapper).should == other_target
178
+ end
179
+ end
180
+ end
181
+
182
+ describe '#create' do
183
+ specify 'traited factory should create an owned mapper' do
184
+ new_one = trait_factory.create(mapper)
185
+ new_one.owner.should == mapper
186
+ end
187
+
188
+ context 'mounted mapper' do
189
+ let(:mount_class){ Class.new(Mapper) }
190
+ let(:factory){ mount_factory }
191
+
192
+ before do
193
+ factory.stub(:mapper_class).and_return(mount_class)
194
+ factory.stub(:fetch_target_from).and_return(other_target)
195
+ end
196
+
197
+ it 'should combine traits' do
198
+ mount_class.should_receive(:new).
199
+ with(other_target, :used_traits, :another_trait).
200
+ and_call_original
201
+ factory.create(mapper, :another_trait)
202
+ end
203
+
204
+ it 'should properly set properties' do
205
+ new_one = factory.create(mapper)
206
+ new_one.host .should == mapper
207
+ new_one.name .should == :spec_mount
208
+ new_one.save_order.should == :after
209
+ new_one.suffix.should be_nil
210
+ end
211
+
212
+ context 'when suffix is defined' do
213
+ let(:factory){ OpenMapper::Factory.new(:spec_mount, :suffix => :foo) }
214
+
215
+ it "should adjust properties with suffix" do
216
+ new_one = factory.create(mapper)
217
+ new_one.name .should == :spec_mount_foo
218
+ new_one.suffix.should == :foo
219
+ end
220
+ end
221
+
222
+ context 'when extension is present' do
223
+ let(:extension){ Proc.new{} }
224
+ let(:factory){ OpenMapper::Factory.new(:spec_mount, &extension) }
225
+
226
+ it "should pass it to mapper initialization" do
227
+ mount_class.should_receive(:new).
228
+ with(other_target, &extension).
229
+ and_call_original
230
+ new_one = factory.create(mapper)
231
+ end
232
+ end
233
+
234
+ describe 'save order' do
235
+ before do
236
+ mapper.stub(:is_a?).and_call_original
237
+ mapper.stub(:is_a?).with(ModelMapper).and_return(true)
238
+ end
239
+
240
+ it 'should be :before for belongs_to association' do
241
+ factory.stub(:reflection_from_target).
242
+ and_return(double('reflection', :macro => :belongs_to))
243
+ factory.fetch_save_order(mapper).should == :before
244
+ end
245
+
246
+ it 'should be :after for other cases' do
247
+ factory.stub(:reflection_from_target).
248
+ and_return(double('reflection', :macro => :has_one))
249
+ factory.fetch_save_order(mapper).should == :after
250
+ end
251
+
252
+ context 'when explicitly set' do
253
+ let(:factory){ OpenMapper::Factory.new(:spec_mount, :save => :before) }
254
+
255
+ it 'should fetch from options, if possible' do
256
+ new_one = factory.create(mapper)
257
+ new_one.save_order.should == :before
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
263
+
264
+ describe '#required_for_any_trait?' do
265
+ let(:mapper_class) do
266
+ Class.new(FlatMap::Mapper) do
267
+ trait(:trait_a) {
268
+ trait(:trait_b) {
269
+ trait(:trait_c) {
270
+ } } }
271
+ end
272
+ end
273
+ let(:factory_for_b){ mapper_class.mountings.first.mapper_class.mountings.first }
274
+
275
+ it "should be required for nested trait" do
276
+ factory_for_b.required_for_any_trait?([:trait_c]).should be true
277
+ end
278
+
279
+ it "should not be required for top trait" do
280
+ factory_for_b.required_for_any_trait?([:trait_a]).should be false
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end