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.
Files changed (64) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +31 -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/LICENSE +20 -0
  10. data/README.markdown +211 -0
  11. data/Rakefile +15 -0
  12. data/flat_map.gemspec +30 -0
  13. data/lib/flat_map.rb +9 -0
  14. data/lib/flat_map/base_mapper.rb +95 -0
  15. data/lib/flat_map/base_mapper/attribute_methods.rb +54 -0
  16. data/lib/flat_map/base_mapper/factory.rb +238 -0
  17. data/lib/flat_map/base_mapper/mapping.rb +123 -0
  18. data/lib/flat_map/base_mapper/mounting.rb +168 -0
  19. data/lib/flat_map/base_mapper/persistence.rb +145 -0
  20. data/lib/flat_map/base_mapper/skipping.rb +62 -0
  21. data/lib/flat_map/base_mapper/traits.rb +94 -0
  22. data/lib/flat_map/empty_mapper.rb +29 -0
  23. data/lib/flat_map/errors.rb +57 -0
  24. data/lib/flat_map/mapper.rb +213 -0
  25. data/lib/flat_map/mapper/skipping.rb +45 -0
  26. data/lib/flat_map/mapper/targeting.rb +130 -0
  27. data/lib/flat_map/mapping.rb +124 -0
  28. data/lib/flat_map/mapping/factory.rb +21 -0
  29. data/lib/flat_map/mapping/reader.rb +12 -0
  30. data/lib/flat_map/mapping/reader/basic.rb +28 -0
  31. data/lib/flat_map/mapping/reader/formatted.rb +45 -0
  32. data/lib/flat_map/mapping/reader/formatted/formats.rb +28 -0
  33. data/lib/flat_map/mapping/reader/method.rb +25 -0
  34. data/lib/flat_map/mapping/reader/proc.rb +15 -0
  35. data/lib/flat_map/mapping/writer.rb +11 -0
  36. data/lib/flat_map/mapping/writer/basic.rb +25 -0
  37. data/lib/flat_map/mapping/writer/method.rb +28 -0
  38. data/lib/flat_map/mapping/writer/proc.rb +18 -0
  39. data/lib/flat_map/version.rb +3 -0
  40. data/spec/flat_map/empty_mapper_spec.rb +36 -0
  41. data/spec/flat_map/errors_spec.rb +23 -0
  42. data/spec/flat_map/mapper/attribute_methods_spec.rb +36 -0
  43. data/spec/flat_map/mapper/callbacks_spec.rb +76 -0
  44. data/spec/flat_map/mapper/factory_spec.rb +258 -0
  45. data/spec/flat_map/mapper/mapping_spec.rb +98 -0
  46. data/spec/flat_map/mapper/mounting_spec.rb +142 -0
  47. data/spec/flat_map/mapper/skipping_spec.rb +91 -0
  48. data/spec/flat_map/mapper/targeting_spec.rb +156 -0
  49. data/spec/flat_map/mapper/traits_spec.rb +172 -0
  50. data/spec/flat_map/mapper/validations_spec.rb +72 -0
  51. data/spec/flat_map/mapper_spec.rb +9 -0
  52. data/spec/flat_map/mapping/factory_spec.rb +12 -0
  53. data/spec/flat_map/mapping/reader/basic_spec.rb +15 -0
  54. data/spec/flat_map/mapping/reader/formatted_spec.rb +62 -0
  55. data/spec/flat_map/mapping/reader/method_spec.rb +13 -0
  56. data/spec/flat_map/mapping/reader/proc_spec.rb +13 -0
  57. data/spec/flat_map/mapping/writer/basic_spec.rb +15 -0
  58. data/spec/flat_map/mapping/writer/method_spec.rb +13 -0
  59. data/spec/flat_map/mapping/writer/proc_spec.rb +13 -0
  60. data/spec/flat_map/mapping_spec.rb +123 -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 +184 -0
@@ -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,3 @@
1
+ module FlatMap # :nodoc:
2
+ VERSION = "0.0.3" # :nodoc:
3
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ module EmptyMapperSpec
4
+ class MountedMapper < ::FlatMap::Mapper
5
+ end
6
+
7
+ class Mapper < ::FlatMap::EmptyMapper
8
+ mount :mounted,
9
+ :mapper_class_name => 'EmptyMapperSpec::MountedMapper',
10
+ :target => Object.new
11
+
12
+ trait :some_trait do
13
+ end
14
+ end
15
+ end
16
+
17
+ module FlatMap
18
+ describe EmptyMapper do
19
+ let(:mapper){ EmptyMapperSpec::Mapper.new(:some_trait){} }
20
+
21
+ it 'should be normally initialized' do
22
+ mapper.mounting(:mounted).should be_present
23
+ mapper.trait(:some_trait).should be_present
24
+ mapper.extension.should be_present
25
+ end
26
+
27
+ it 'should raise error for malounted mapper when target is not specified' do
28
+ mapper_class = Class.new(::FlatMap::EmptyMapper) do
29
+ mount :mounted, :mapper_class_name => 'EmptyMapperSpec::MountedMapper'
30
+ end
31
+
32
+ expect{ mapper_class.new.mounting(:mounted) }.
33
+ to raise_error(::FlatMap::Mapper::Targeting::NoTargetError)
34
+ end
35
+ end
36
+ 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,258 @@
1
+ require 'spec_helper'
2
+
3
+ module FlatMap
4
+ describe BaseMapper::Factory do
5
+ let(:trait_class){ Class.new(Mapper) }
6
+ let(:target){ Object.new }
7
+ let(:other_target){ Object.new }
8
+ let(:mapper){ Class.new(Mapper).new(target) }
9
+ let(:mount_factory){ BaseMapper::Factory.new(:spec_mount, :traits => :used_traits) }
10
+ let(:trait_factory){ BaseMapper::Factory.new(trait_class, :trait_name => :a_trait) }
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
+ describe 'behavior' do
31
+ describe '#mapper_class for mounted mappers' do
32
+ class ::SpecMountMapper < Mapper; end
33
+ class BaseMapper::Factory::SpecMountMapper; end
34
+
35
+ it "should be able to fetch class name from name" do
36
+ mount_factory.mapper_class.should == ::SpecMountMapper
37
+ end
38
+
39
+ it "should use options if specified" do
40
+ factory = BaseMapper::Factory.new(
41
+ :spec_mount,
42
+ :mapper_class_name => 'FlatMap::BaseMapper::Factory::SpecMountMapper'
43
+ )
44
+ factory.mapper_class.should == ::FlatMap::BaseMapper::Factory::SpecMountMapper
45
+ end
46
+ end
47
+
48
+ describe '#fetch_target_from' do
49
+ it "should return owner's target for traited factory" do
50
+ trait_factory.fetch_target_from(mapper).should == target
51
+ end
52
+
53
+ context 'explicit target' do
54
+ it "should use explicitly specified if applicable" do
55
+ factory = BaseMapper::Factory.new(:mount, :target => other_target)
56
+ factory.fetch_target_from(mapper).should == other_target
57
+ end
58
+
59
+ it "should call Proc and pass owner target to it if Proc is specified as :target" do
60
+ factory = BaseMapper::Factory.new(:mount, :target => lambda{ |obj| obj.foo })
61
+ target.should_receive(:foo).and_return(other_target)
62
+ factory.fetch_target_from(mapper).should == other_target
63
+ end
64
+ end
65
+
66
+ context 'target from association' do
67
+ before{ target.stub(:kind_of?).with(ActiveRecord::Base).and_return(true) }
68
+
69
+ let(:has_one_current_reflection) {
70
+ double('reflection', :macro => :has_one, :options => {:is_current => true})
71
+ }
72
+ let(:has_one_reflection) {
73
+ double('reflection', :macro => :has_one, :options => {})
74
+ }
75
+ let(:belongs_to_reflection) {
76
+ double('reflection', :macro => :belongs_to)
77
+ }
78
+ let(:has_many_reflection) {
79
+ double('reflection', :macro => :has_many, :name => :spec_mounts)
80
+ }
81
+
82
+ it "should refer to effective name for has_one_current association" do
83
+ # Note: has_one_current is not part of Rails
84
+ mount_factory.should_receive(:reflection_from_target).
85
+ with(target).
86
+ and_return(has_one_current_reflection)
87
+ target.should_receive(:effective_spec_mount).and_return(other_target)
88
+ mount_factory.fetch_target_from(mapper).should == other_target
89
+ end
90
+
91
+ it "should refer to existing association object if possible, " \
92
+ "and build it if it is absent for :has_one" do
93
+ mount_factory.should_receive(:reflection_from_target).
94
+ with(target).
95
+ and_return(has_one_reflection)
96
+ target.should_receive(:spec_mount).and_return(nil)
97
+ target.should_receive(:build_spec_mount).and_return(other_target)
98
+ mount_factory.fetch_target_from(mapper).should == other_target
99
+ end
100
+
101
+ it "should refer to existing association object if possible, " \
102
+ "and build it if it is absent for :belongs_to" do
103
+ mount_factory.should_receive(:reflection_from_target).
104
+ with(target).
105
+ and_return(belongs_to_reflection)
106
+ target.should_receive(:spec_mount).and_return(nil)
107
+ target.should_receive(:build_spec_mount).and_return(other_target)
108
+ mount_factory.fetch_target_from(mapper).should == other_target
109
+ end
110
+
111
+ it "should always build a new record for :has_many association" do
112
+ mount_factory.should_receive(:reflection_from_target).
113
+ with(target).
114
+ and_return(has_many_reflection)
115
+ target.should_receive(:association).with(:spec_mounts)
116
+ target.stub_chain(:association, :build).and_return(other_target)
117
+ mount_factory.fetch_target_from(mapper).should == other_target
118
+ end
119
+
120
+ describe 'reflection_from_target' do
121
+ before{ target.stub(:is_a?).with(ActiveRecord::Base).and_return(true) }
122
+
123
+ it 'should first refer to singular association' do
124
+ target.stub_chain(:class, :reflect_on_association).
125
+ with(:spec_mount).
126
+ and_return(has_one_reflection)
127
+ mount_factory.reflection_from_target(target).should == has_one_reflection
128
+ end
129
+
130
+ it 'should use collection association if singular does not exist' do
131
+ target.stub_chain(:class, :reflect_on_association).
132
+ with(:spec_mount).
133
+ and_return(nil)
134
+ target.stub_chain(:class, :reflect_on_association).
135
+ with(:spec_mounts).
136
+ and_return(has_many_reflection)
137
+ mount_factory.reflection_from_target(target).should == has_many_reflection
138
+ end
139
+ end
140
+ end
141
+
142
+ context 'target from name' do
143
+ it 'should simply send method to owner target' do
144
+ target.should_receive(:spec_mount).and_return(other_target)
145
+ mount_factory.fetch_target_from(mapper).should == other_target
146
+ end
147
+ end
148
+ end
149
+
150
+ describe '#create' do
151
+ specify 'traited factory should create an owned mapper' do
152
+ new_one = trait_factory.create(mapper)
153
+ new_one.owner.should == mapper
154
+ end
155
+
156
+ context 'mounted empty mapper' do
157
+ class ::SpecEmptyMountMapper < EmptyMapper; end
158
+ let(:factory){ BaseMapper::Factory.new(:spec_empty_mount) }
159
+
160
+ it 'should not call fetch_target_from' do
161
+ factory.should_not_receive(:fetch_target_from)
162
+ factory.create(mapper)
163
+ end
164
+ end
165
+
166
+ context 'mounted mapper' do
167
+ let(:mount_class){ Class.new(Mapper) }
168
+ let(:factory){ mount_factory }
169
+
170
+ before do
171
+ factory.stub(:mapper_class).and_return(mount_class)
172
+ factory.stub(:fetch_target_from).and_return(other_target)
173
+ end
174
+
175
+ it 'should combine traits' do
176
+ mount_class.should_receive(:new).
177
+ with(other_target, :used_traits, :another_trait).
178
+ and_call_original
179
+ factory.create(mapper, :another_trait)
180
+ end
181
+
182
+ it 'should properly set properties' do
183
+ new_one = factory.create(mapper)
184
+ new_one.host .should == mapper
185
+ new_one.name .should == :spec_mount
186
+ new_one.save_order.should == :after
187
+ new_one.suffix.should be_nil
188
+ end
189
+
190
+ context 'when suffix is defined' do
191
+ let(:factory){ BaseMapper::Factory.new(:spec_mount, :suffix => :foo) }
192
+
193
+ it "should adjust properties with suffix" do
194
+ new_one = factory.create(mapper)
195
+ new_one.name .should == :spec_mount_foo
196
+ new_one.suffix.should == :foo
197
+ end
198
+ end
199
+
200
+ context 'when extension is present' do
201
+ let(:extension){ Proc.new{} }
202
+ let(:factory){ BaseMapper::Factory.new(:spec_mount, &extension) }
203
+
204
+ it "should pass it to mapper initialization" do
205
+ mount_class.should_receive(:new).
206
+ with(other_target, &extension).
207
+ and_call_original
208
+ new_one = factory.create(mapper)
209
+ end
210
+ end
211
+
212
+ describe 'save order' do
213
+ context 'when explicitly set' do
214
+ let(:factory){ BaseMapper::Factory.new(:spec_mount, :save => :before) }
215
+
216
+ it 'should fetch from options, if possible' do
217
+ new_one = factory.create(mapper)
218
+ new_one.save_order.should == :before
219
+ end
220
+ end
221
+
222
+ it 'should be :before for belongs_to association' do
223
+ mount_factory.stub(:reflection_from_target).
224
+ and_return(double('reflection', :macro => :belongs_to))
225
+ factory.fetch_save_order(mapper).should == :before
226
+ end
227
+
228
+ it 'should be :after for other cases' do
229
+ mount_factory.stub(:reflection_from_target).
230
+ and_return(double('reflection', :macro => :has_one))
231
+ factory.fetch_save_order(mapper).should == :after
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ describe '#required_for_any_trait?' do
238
+ let(:mapper_class) do
239
+ Class.new(FlatMap::Mapper) do
240
+ trait(:trait_a) {
241
+ trait(:trait_b) {
242
+ trait(:trait_c) {
243
+ } } }
244
+ end
245
+ end
246
+ let(:factory_for_b){ mapper_class.mountings.first.mapper_class.mountings.first }
247
+
248
+ it "should be required for nested trait" do
249
+ factory_for_b.required_for_any_trait?([:trait_c]).should be_true
250
+ end
251
+
252
+ it "should not be required for top trait" do
253
+ factory_for_b.required_for_any_trait?([:trait_a]).should be_false
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end