simple_mapper 0.0.1

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.
@@ -0,0 +1,561 @@
1
+ require 'test_helper'
2
+
3
+ class AttributesTest < Test::Unit::TestCase
4
+ def stub_out_attributes(klass, *attrs)
5
+ attrs.each do |attr|
6
+ attr_obj = klass.simple_mapper.attributes[attr]
7
+ attr_obj.stubs(:changed?)
8
+ attr_obj.stubs(:changed!)
9
+ end
10
+ end
11
+
12
+ context 'A SimpleMapper::Attributes::Manager' do
13
+ setup do
14
+ @instance = SimpleMapper::Attributes::Manager.new
15
+ end
16
+
17
+ should 'has an empty attributes hash' do
18
+ assert @instance.respond_to?(:attributes)
19
+ assert_equal({}, @instance.attributes)
20
+ end
21
+
22
+ should 'has an applies_to attribute' do
23
+ assert @instance.respond_to?(:applies_to)
24
+ assert_equal nil, @instance.applies_to
25
+ @instance.applies_to = self
26
+ assert_equal self, @instance.applies_to
27
+ end
28
+
29
+ should 'allows specification of applies_to through constructor' do
30
+ @instance = @instance.class.new(self.class)
31
+ assert_equal self.class, @instance.applies_to
32
+ end
33
+
34
+ context 'when creating an anonymous mapper' do
35
+ should 'return a class that is mapper-enabled' do
36
+ result = @instance.create_anonymous_mapper
37
+ assert_equal Class, result.class
38
+ assert result.respond_to?(:maps)
39
+ assert result.new.respond_to?(:to_simple)
40
+ assert result.respond_to?(:decode)
41
+ # decode should just wrap the constructor
42
+ assert_equal result, result.decode.class
43
+ end
44
+
45
+ should 'eval a block within the context of the anonymous class' do
46
+ result = @instance.create_anonymous_mapper do
47
+ def self.platypus; 'platypus?'; end
48
+ def walrus; 'walrus?'; end
49
+ end
50
+ assert_equal 'platypus?', result.platypus
51
+ assert_equal 'walrus?', result.new.walrus
52
+ end
53
+ end
54
+
55
+ context 'when installing attributes should' do
56
+ setup do
57
+ @class = Class.new
58
+ @instance.applies_to = @class
59
+ @object = @instance.create_attribute(:some_attribute)
60
+ @instance.install_attribute(:some_attribute, @object)
61
+ end
62
+
63
+ should 'allow addition of instance attribute to applies_to' do
64
+ assert @class.new.respond_to?(:some_attribute)
65
+ assert @class.new.respond_to?(:some_attribute=)
66
+ end
67
+
68
+ should 'install attribute reader wrapping the read_attribute method' do
69
+ @mapper_instance = @class.new
70
+ @mapper_instance.expects(:read_attribute).with(:some_attribute).once
71
+ @mapper_instance.some_attribute
72
+ end
73
+
74
+ should 'install attribute writer wrapping the write_attribute method' do
75
+ @mapper_instance = @class.new
76
+ @mapper_instance.expects(:write_attribute).with(:some_attribute, :some_value).once
77
+ @mapper_instance.some_attribute = :some_value
78
+ end
79
+
80
+ should 'add attribute object to attributes list' do
81
+ attribs = @instance.attributes.inject({}) do |a, kvp|
82
+ key, val = kvp
83
+ a[key] = [ val.key, val.class ]
84
+ a
85
+ end
86
+ assert_equal({
87
+ :some_attribute => [:some_attribute, SimpleMapper::Attribute],
88
+ },
89
+ attribs)
90
+ end
91
+ end
92
+
93
+ context 'when creating new attributes should' do
94
+ setup do
95
+ @class = Class.new
96
+ @instance.applies_to = @class
97
+ end
98
+
99
+ should 'pass :type option through to attribute object' do
100
+ type_a = mock('type_a')
101
+ attrib = @instance.create_attribute(:attrib, :type => type_a)
102
+ assert_equal type_a, attrib.type
103
+ end
104
+
105
+ should 'pass :key option through if provided' do
106
+ attrib = @instance.create_attribute(:attrib, :key => :not_attrib)
107
+ assert_equal :not_attrib, attrib.key
108
+ end
109
+
110
+ should 'default the attribute class to SimpleMapper::Attribute' do
111
+ assert_equal SimpleMapper::Attribute, @instance.create_attribute(:some_attr).class
112
+ end
113
+
114
+ should 'allow specification of attribute class via the :attribute_class option' do
115
+ attr_class = Class.new(SimpleMapper::Attribute)
116
+ attrib = @instance.create_attribute(:some_attr, :attribute_class => attr_class)
117
+ assert_equal attr_class, attrib.class
118
+ end
119
+ end
120
+ end
121
+
122
+ context 'SimpleMapper::Attributes module' do
123
+ setup do
124
+ @class = Class.new do
125
+ include SimpleMapper::Attributes
126
+ end
127
+ end
128
+
129
+ context 'types registry' do
130
+ setup do
131
+ @fake_type = stub('fake_type')
132
+ @fake_expected_type = Class.new { attr_accessor :value }
133
+ @fake_type_symbol = :fake_for_test
134
+ @fake_registry_entry = {:name => @fake_type_symbol,
135
+ :expected_type => @fake_expected_type,
136
+ :converter => @fake_type,}
137
+ end
138
+
139
+ should 'provide a types registry hash' do
140
+ assert_equal Hash, SimpleMapper::Attributes.types.class
141
+ end
142
+
143
+ should 'provide type registration through :register_type' do
144
+ SimpleMapper::Attributes.register_type(@fake_type_symbol,
145
+ @fake_expected_type,
146
+ @fake_type)
147
+
148
+ assert_equal(@fake_registry_entry,
149
+ SimpleMapper::Attributes.types[@fake_type_symbol])
150
+ end
151
+
152
+ should 'allow lookup of type info by name' do
153
+ SimpleMapper::Attributes.register_type(@fake_type_symbol,
154
+ @fake_expected_type,
155
+ @fake_type)
156
+ assert_equal(@fake_registry_entry,
157
+ SimpleMapper::Attributes.type_for(@fake_type_symbol))
158
+ end
159
+
160
+ should 'return nil on type lookup for unknown type' do
161
+ assert_equal nil, SimpleMapper::Attributes.type_for(:i_do_not_exist)
162
+ end
163
+
164
+ teardown do
165
+ SimpleMapper::Attributes.types.delete @fake_type_symbol
166
+ end
167
+ end
168
+
169
+ context 'inclusion should' do
170
+ should 'provide a maps class method' do
171
+ assert @class.respond_to?(:maps)
172
+ end
173
+
174
+ should 'provide a simple_mapper attribute manager' do
175
+ assert @class.respond_to?(:simple_mapper)
176
+ assert_equal SimpleMapper::Attributes::Manager, @class.simple_mapper.class
177
+ assert_equal @class, @class.simple_mapper.applies_to
178
+ end
179
+ end
180
+
181
+ should 'return an empty hash for to_simple on an instance with no attributes' do
182
+ assert_equal({}, @class.new.to_simple)
183
+ end
184
+
185
+ context 'to_simple instance method' do
186
+ setup do
187
+ @class.maps :a
188
+ @class.maps :b
189
+ @attrib_a = @class.simple_mapper.attributes[:a]
190
+ @attrib_b = @class.simple_mapper.attributes[:b]
191
+ # traditional mocking is not sufficiently
192
+ # expressive for this, so we'll just implement
193
+ # methods directly.
194
+ @options = {}
195
+ [@attrib_a, @attrib_b].each do |attr|
196
+ attr.instance_eval do
197
+ def to_simple(obj, container, options = {})
198
+ # like an expectation; verifies that
199
+ # options are passed along properly
200
+ raise Exception unless options == obj.expected_options
201
+ container[key] = obj.read_attribute(name)
202
+ container
203
+ end
204
+ end
205
+ end
206
+ @values = {:a => 'A', :b => 'B'}
207
+ @instance = @class.new(@values.clone)
208
+ @instance.stubs(:expected_options).returns(@options)
209
+ end
210
+
211
+ should 'accumulate results from :to_simple on each attribute object' do
212
+ @attrib_a.expects(:changed?).never
213
+ @attrib_b.expects(:changed?).never
214
+ assert_equal @values, @instance.to_simple(@options)
215
+ end
216
+
217
+ should 'accumulate results from :to_simple on changed attributes only when :changed => true is passed' do
218
+ expectation = @values.clone
219
+ expectation.delete :b
220
+ @attrib_a.expects(:changed?).with(@instance).returns(true)
221
+ @attrib_b.expects(:changed?).with(@instance).returns(false)
222
+ @options[:changed] = true
223
+ assert_equal expectation, @instance.to_simple(@options)
224
+ end
225
+
226
+ should 'return values of all attributes and propagate :all option from :to_simple when :all_changed? is true on receiver and :changed is true' do
227
+ @instance.stubs(:all_changed?).returns(true)
228
+ @attrib_a.stubs(:changed?).with(@instance).returns(true)
229
+ @sttrib_b.stubs(:changed?).with(@instance).returns(false)
230
+ opt = @options.clone
231
+ opt[:changed] = true
232
+ @options[:all] = true
233
+ assert_equal @values.clone, @instance.to_simple(opt)
234
+ end
235
+ end
236
+
237
+ context 'simpler_mapper_source' do
238
+ should 'provide an empty hash by default' do
239
+ @instance = @class.new
240
+ assert_equal({}, @instance.simple_mapper_source)
241
+ end
242
+
243
+ should 'provide the source hash with which the instance was created' do
244
+ values = {:foo => 'foo', :boo => 'boo'}
245
+ @instance = @class.new(values)
246
+ assert_equal values, @instance.simple_mapper_source
247
+ end
248
+ end
249
+
250
+ context 'instance method' do
251
+ setup do
252
+ @instance = @class.new
253
+ @class.maps :foo
254
+ stub_out_attributes @class, :foo
255
+ end
256
+
257
+ context 'reset_attribute should' do
258
+ should 'restore the specified attribute to its source value' do
259
+ @instance = @class.new
260
+ @class.simple_mapper.attributes[:foo].expects(:source_value).with(@instance).once.returns('Foo!')
261
+
262
+ @instance.write_attribute(:foo, 'new val')
263
+ assert_equal 'new val', @instance.read_attribute(:foo)
264
+ @instance.reset_attribute(:foo)
265
+ assert_equal 'Foo!', @instance.read_attribute(:foo)
266
+ end
267
+
268
+ should 'clear the changed? status of the specified attribute' do
269
+ @instance = @class.new
270
+ seq = sequence('change states')
271
+ attrib = @class.simple_mapper.attributes[:foo]
272
+ attrib.expects(:changed!).with(@instance, true).once.in_sequence(seq).returns(true)
273
+ attrib.expects(:changed!).with(@instance, false).once.in_sequence(seq).returns(false)
274
+ @instance.write_attribute(:foo, 'new val')
275
+ @instance.reset_attribute(:foo)
276
+ end
277
+ end
278
+
279
+ # keep me
280
+ context 'transform_source_attribute' do
281
+ setup do
282
+ @instance = @class.new(:foo => 'foo')
283
+ end
284
+
285
+ should 'delegate source value transformation to underlying attribute object' do
286
+ @class.simple_mapper.attributes[:foo].expects(:transformed_source_value).with(@instance).returns(:foopy)
287
+ result = @instance.transform_source_attribute(:foo)
288
+ assert_equal :foopy, result
289
+ end
290
+ end
291
+
292
+ # keep me
293
+ context 'read_source_attribute' do
294
+ setup do
295
+ @instance = @class.new(:foo => 'Foo!')
296
+ end
297
+
298
+ should 'delegate source value retrieval to the underlying attribute object' do
299
+ @class.simple_mapper.attributes[:foo].expects(:source_value).with(@instance).returns('foo')
300
+ result = @instance.read_source_attribute(:foo)
301
+ assert_equal 'foo', result
302
+ end
303
+ end
304
+
305
+ context 'write_attribute should' do
306
+ should 'set the attribute as an instance variable' do
307
+ @instance.write_attribute(:foo, 'Foo!')
308
+ assert_equal 'Foo!', @instance.instance_variable_get(:@foo)
309
+ end
310
+ end
311
+
312
+ context 'get_attribute_default' do
313
+ should 'delegate default value to attribute object' do
314
+ @class.maps :with_default, :default => :foo
315
+ @class.simple_mapper.attributes[:with_default].expects(:default_value).once.with(@instance).returns(:expected_default)
316
+ result = @instance.get_attribute_default(:with_default)
317
+ assert_equal :expected_default, result
318
+ end
319
+ end
320
+
321
+ context 'freeze' do
322
+ setup do
323
+ @instance = @class.new(:foo => @value = 'foo')
324
+ end
325
+
326
+ should 'prevent further attribute writes' do
327
+ @instance.freeze
328
+ assert_raises(RuntimeError) { @instance.foo = :x }
329
+ # verify that value is unchanged
330
+ assert_equal @value, @instance.foo
331
+ end
332
+
333
+ should 'allow attribute reads' do
334
+ @instance.freeze
335
+ assert_equal @value, @instance.foo
336
+ end
337
+
338
+ should 'support per-attribute handling via :freeze_for per attribute' do
339
+ @class.maps :foo2
340
+ @class.maps :foo3
341
+ [:foo, :foo2, :foo3].each do |attrib|
342
+ @instance.attribute_object_for(attrib).expects(:freeze_for).with(@instance)
343
+ end
344
+ @instance.freeze
345
+ end
346
+ end
347
+
348
+ context 'frozen?' do
349
+ setup do
350
+ @instance = @class.new(:foo => 'foo')
351
+ end
352
+
353
+ should 'default to false' do
354
+ assert_equal false, @instance.frozen?
355
+ end
356
+
357
+ should 'be true after a :freeze call' do
358
+ @instance.freeze
359
+ assert_equal true, @instance.frozen?
360
+ end
361
+ end
362
+ end
363
+
364
+ context 'change tracking' do
365
+ setup do
366
+ @class.maps :change_me
367
+ @class.maps :do_not_change_me
368
+ @instance = @class.new(:change_me => 'change me', :do_not_change_me => 'no changing')
369
+ end
370
+
371
+ should 'automatically instantiate an empty ChangeHash per instance' do
372
+ assert_equal(SimpleMapper::ChangeHash.new, @instance.simple_mapper_changes)
373
+ @other = @class.new
374
+ assert_equal(SimpleMapper::ChangeHash.new, @other.simple_mapper_changes)
375
+ assert_not_equal @instance.simple_mapper_changes.object_id, @other.simple_mapper_changes.object_id
376
+ end
377
+
378
+ should 'indicate an attribute is unchanged if it has not been assigned, based on attribute object' do
379
+ @instance.class.simple_mapper.attributes[:change_me].expects(:changed?).with(@instance).returns(false)
380
+ assert_equal false, @instance.attribute_changed?(:change_me)
381
+ end
382
+
383
+ should 'mark an attribute as changed once it has been assigned to' do
384
+ @instance.class.simple_mapper.attributes[:change_me].expects(:changed!).with(@instance, true)
385
+ @instance.change_me = 'Thou art changed'
386
+ end
387
+
388
+ should 'mark an attribute as changed via the :attribute_changed! method' do
389
+ @instance.class.simple_mapper.attributes[:change_me].expects(:changed!).with(@instance, true)
390
+ @instance.attribute_changed! :change_me
391
+ end
392
+
393
+ should 'flag all_changed on change tracking hash through :all_changed! method' do
394
+ @instance.simple_mapper_changes.expects(:all_changed!).with().returns(true)
395
+ @instance.all_changed!
396
+ end
397
+
398
+ should "return value of change-tracking hash's :all for :all_changed?" do
399
+ @instance.simple_mapper_changes.stubs(:all).returns(true)
400
+ assert_equal true, @instance.all_changed?
401
+ @instance.simple_mapper_changes.stubs(:all).returns(false)
402
+ assert_equal false, @instance.all_changed?
403
+ end
404
+
405
+ should 'return all attributes as changed when change-tracker marked with all' do
406
+ @instance.simple_mapper_changes.stubs(:all).returns(true)
407
+ attribs = @instance.changed_attributes.sort_by {|x| x.to_s}
408
+ expected = @instance.class.simple_mapper.attributes.keys.sort_by {|x| x.to_s}
409
+ assert_equal expected, attribs
410
+ end
411
+
412
+ context 'when nothing has been assigned' do
413
+ setup do
414
+ @instance.class.simple_mapper.attributes.each do |key, attr|
415
+ attr.expects(:changed?).with(@instance).returns(false)
416
+ end
417
+ end
418
+
419
+ should 'return empty list for :changed_attributes' do
420
+ assert_equal [], @instance.changed_attributes
421
+ end
422
+
423
+ should 'return false for :changed?' do
424
+ assert_equal false, @instance.changed?
425
+ end
426
+ end
427
+
428
+ context 'when one attribute is marked as changed' do
429
+ setup do
430
+ @instance.class.simple_mapper.attributes.each do |key, attr|
431
+ attr.expects(:changed?).with(@instance).returns(key == :change_me ? true : false)
432
+ end
433
+ end
434
+
435
+ should 'return single-item list for :changed_attributes when an attribute is marked as changed' do
436
+ assert_equal [:change_me], @instance.changed_attributes
437
+ end
438
+
439
+ should 'return true for :changed?' do
440
+ assert @instance.changed?
441
+ end
442
+ end
443
+
444
+ context 'when multiple attributes are marked as changed' do
445
+ setup do
446
+ @changes = [:change_me, :do_not_change_me]
447
+ @instance.class.simple_mapper.attributes.each do |key, attr|
448
+ attr.expects(:changed?).with(@instance).returns(@changes.include?(key) ? true : false)
449
+ end
450
+ end
451
+
452
+ should 'return two-item list for :changed_attributes when both attrs were assigned' do
453
+ assert_equal(@changes, @instance.changed_attributes.sort_by {|sym| sym.to_s})
454
+ end
455
+
456
+ should 'return true for :changed' do
457
+ assert @instance.changed?
458
+ end
459
+ end
460
+ end
461
+
462
+ context 'maps method should' do
463
+ should 'install an attribute reader on the class' do
464
+ instance = @class.new
465
+ assert ! instance.respond_to?(:some_attr)
466
+ @class.maps :some_attr
467
+ assert instance.respond_to?(:some_attr)
468
+ end
469
+
470
+ should 'install an attribute writer on the class' do
471
+ instance = @class.new
472
+ assert ! instance.respond_to?(:some_attr=)
473
+ @class.maps :some_attr
474
+ assert instance.respond_to?(:some_attr=)
475
+ end
476
+
477
+ should 'place an Attribute instance in the class manager attributes hash' do
478
+ @class.maps :some_attr
479
+ @class.maps :some_other_attr
480
+ attr_class = SimpleMapper::Attribute
481
+ attribs = @class.simple_mapper.attributes.inject({}) do |a, kvp|
482
+ key, val = kvp
483
+ a[key] = [ val.key, val.class ]
484
+ a
485
+ end
486
+ assert_equal({
487
+ :some_attr => [:some_attr, attr_class],
488
+ :some_other_attr => [:some_other_attr, attr_class],
489
+ },
490
+ attribs)
491
+ end
492
+
493
+ should 'accept a :type option that carries over to the Attribute instance' do
494
+ type_a = stub('type_a')
495
+ @class.maps :foo, :type => type_a
496
+ type_b = stub('type_b')
497
+ @class.maps :bar, :type => type_b
498
+
499
+ assert_equal type_a, @class.simple_mapper.attributes[:foo].type
500
+ assert_equal type_b, @class.simple_mapper.attributes[:bar].type
501
+ end
502
+
503
+ should 'create an anonymous mapper as the type when given a block' do
504
+ @class.maps :outer do; end
505
+ assert @class.simple_mapper.attributes[:outer].type.respond_to?(:maps)
506
+ assert @class.simple_mapper.attributes[:outer].type.respond_to?(:new)
507
+ assert @class.simple_mapper.attributes[:outer].type.respond_to?(:decode)
508
+ end
509
+
510
+ should 'assign anonymous mapper as the mapper of the attribute object when given a block' do
511
+ mapper = stub('mapper')
512
+ @class.simple_mapper.expects(:create_anonymous_mapper).returns(mapper)
513
+ @class.maps :outer do; end
514
+ assert_equal mapper, @class.simple_mapper.attributes[:outer].mapper
515
+ end
516
+ end
517
+ end
518
+
519
+ context 'with full nested mappers' do
520
+ setup do
521
+ @class = Class.new do
522
+ include SimpleMapper::Attributes
523
+ maps :id
524
+ maps :email
525
+ maps :home_address do
526
+ maps :address
527
+ maps :city
528
+ maps :state
529
+ maps :zip
530
+ end
531
+ end
532
+ @source = {:id => 'foo',
533
+ :email => 'aardvark@pyongyang.yumm.com',
534
+ :home_address => {
535
+ :address => '54 Gorgeous Gorge Parkway',
536
+ :city => 'Here',
537
+ :state => 'TX',
538
+ :zip => '04435'}}
539
+ @instance = @class.new(@source)
540
+ end
541
+
542
+ should 'map to instance appropriately' do
543
+ assert_equal @class, @instance.class
544
+ assert_equal @source[:id], @instance.id
545
+ assert_equal @source[:email], @instance.email
546
+ assert_equal @source[:home_address][:address], @instance.home_address.address
547
+ assert_equal @source[:home_address][:city], @instance.home_address.city
548
+ assert_equal @source[:home_address][:state], @instance.home_address.state
549
+ assert_equal @source[:home_address][:zip], @instance.home_address.zip
550
+ end
551
+
552
+ should 'map back to simple structure' do
553
+ assert_equal @source, @instance.to_simple
554
+ end
555
+
556
+ should 'freeze nested mappers when frozen from containing object' do
557
+ @instance.freeze
558
+ assert_equal true, @instance.home_address.frozen?
559
+ end
560
+ end
561
+ end