flat_map 0.0.3

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