simple_mapper 0.0.1

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