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,11 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.setup
5
+
6
+ require 'simple_mapper'
7
+
8
+ require 'test/unit'
9
+ require 'shoulda'
10
+ require 'mocha'
11
+
@@ -0,0 +1,379 @@
1
+ require 'test_helper'
2
+ class AttributeCollectionTest < Test::Unit::TestCase
3
+ context 'A collection-type attribute' do
4
+ setup do
5
+ @class = Class.new(SimpleMapper::Attribute) do
6
+ include SimpleMapper::Attribute::Collection
7
+ end
8
+ @name = :me_collection_attribute
9
+ @instance = @class.new(@name)
10
+ @source = {}
11
+ @object = stub('object', :simple_mapper_source => @source)
12
+ end
13
+
14
+ context 'transforming a value from source' do
15
+ should 'prepare value marked for tracking changes' do
16
+ collection = @instance.transformed_source_value(@object)
17
+ assert_equal true, collection.change_tracking
18
+ end
19
+
20
+ should 'prepare value with no changes marked' do
21
+ @source.merge!({'a' => 'A', 'b' => 'B'})
22
+ collection = @instance.transformed_source_value(@object)
23
+ assert_equal [], collection.changed_members
24
+ end
25
+ end
26
+
27
+ context 'changed?' do
28
+ setup do
29
+ @collection = {}
30
+ @instance.stubs(:value).with(@object).returns(@collection)
31
+ end
32
+
33
+ context 'with empty changed_members' do
34
+ setup do
35
+ @collection.stubs(:changed_members).returns(SimpleMapper::ChangeHash.new)
36
+ end
37
+
38
+ should 'be false' do
39
+ assert_equal false, @instance.changed?(@object)
40
+ end
41
+
42
+ context 'and a mapper' do
43
+ setup do
44
+ @mapper = stub('mapper')
45
+ @instance.mapper = @mapper
46
+ @keys = [:a, :b, :c]
47
+ @keys.each do |key|
48
+ item = stub('item_' + key.to_s)
49
+ @collection[key] = item
50
+ end
51
+ end
52
+
53
+ should 'be false if no mapped values are changed' do
54
+ @collection.values.each {|item| item.stubs(:changed?).with.returns(false)}
55
+ assert_equal false, @instance.changed?(@object)
56
+ end
57
+
58
+ should 'be true if any mapped values are changed' do
59
+ item = @keys.pop
60
+ @collection[item].stubs(:changed?).with.returns(true)
61
+ @keys.each {|x| @collection[x].stubs(:changed?).with.returns(false)}
62
+ assert_equal true, @instance.changed?(@object)
63
+ end
64
+ end
65
+ end
66
+
67
+ should 'be true if changed_members is populated' do
68
+ hash = SimpleMapper::ChangeHash.new
69
+ hash[:foo] = true
70
+ @collection.stubs(:changed_members).returns(hash)
71
+ assert_equal true, @instance.changed?(@object)
72
+ end
73
+ end
74
+
75
+ should 'pass keys through unchanged for :to_simple_key' do
76
+ items = ['a string', :a_symbol, 666]
77
+ assert_equal(items, items.collect {|item| @instance.to_simple_key(item)})
78
+ end
79
+
80
+ should 'pass keys through unchanged for :from_simple_key' do
81
+ items = ['another string', :another_symbol, 444]
82
+ assert_equal(items, items.collect {|item| @instance.from_simple_key(item)})
83
+ end
84
+
85
+ context 'for source value' do
86
+ should 'get its container value from :new_collection' do
87
+ @instance.expects(:new_collection).returns(container = stub('container'))
88
+ assert_equal(container, @instance.source_value(@object))
89
+ end
90
+
91
+ should 'determine keys for the mapped structure via :from_simple_key' do
92
+ source_additional = {:a => 'A', :abc => 'ABC', :aarp => 'AARP'}
93
+ @instance.stubs(:member_key).returns(false)
94
+ expected = source_additional.inject({}) do |hash, keyval|
95
+ @instance.stubs(:member_key?).with(keyval[0]).returns(true)
96
+ key = keyval[0].to_s + ' transformed'
97
+ hash[key] = keyval[1]
98
+ @instance.expects(:from_simple_key).with(keyval[0]).returns(key)
99
+ hash
100
+ end
101
+ @source.merge! source_additional
102
+ result = @instance.source_value(@object)
103
+ assert_equal expected, result
104
+ end
105
+
106
+ should 'return empty hash if no keys in the source pass the member_key? test' do
107
+ # note that this depends on member_key? being a default-false method in the module
108
+ assert_equal({}, @instance.source_value(@object))
109
+ end
110
+
111
+ should 'return hash with key/value pairs for only the keys passing member_key? test' do
112
+ expected = {:a => 'A', :abc => 'ABC', :aarp => 'AARP'}
113
+ @source.merge! expected
114
+ @instance.stubs(:member_key).returns(false)
115
+ expected.keys.each do |key|
116
+ @instance.expects(:member_key?).with(key).returns(true)
117
+ end
118
+ assert_equal expected, @instance.source_value(@object)
119
+ end
120
+ end
121
+
122
+
123
+ context 'for transformed_source_value' do
124
+ setup do
125
+ @expected_source = {:a => 'A', :aa => 'AA'}
126
+ @expected_default = {:a => 'Default A', :aa => 'Default AA'}
127
+ @instance.stubs(:default_value).with(@object).returns(@expected_default)
128
+ end
129
+
130
+ should 'return the source value if there is no type and source is non-empty' do
131
+ @instance.stubs(:source_value).with(@object).returns(@expected_source)
132
+ assert_equal @expected_source, @instance.transformed_source_value(@object)
133
+ end
134
+
135
+ should 'return the default value if there is no type and source is empty' do
136
+ @instance.stubs(:source_value).with(@object).returns(nil)
137
+ assert_equal @expected_default, @instance.transformed_source_value(@object)
138
+ end
139
+
140
+ context 'with a type' do
141
+ setup do
142
+ @instance.type = stub('type')
143
+ end
144
+
145
+ should 'return the result of apply type given the source value' do
146
+ @instance.expects(:apply_type).with(@expected_source).returns( expected = {:a => 'Foo'})
147
+ @instance.stubs(:source_value).with(@object).returns(@expected_source)
148
+ result = @instance.transformed_source_value(@object)
149
+ assert_equal expected, result
150
+ end
151
+
152
+ should 'return the result of the apply type given the default value and empty source' do
153
+ @instance.expects(:apply_type).with(@expected_default).returns( expected = {:a => 'Default'} )
154
+ @instance.stubs(:source_value).with(@object).returns(nil)
155
+ result = @instance.transformed_source_value(@object)
156
+ assert_equal expected, result
157
+ end
158
+ end
159
+ end
160
+
161
+ context 'for freeze_for' do
162
+ setup do
163
+ @collection = [:a, :b, :c].inject({}) {|hash, name| hash[name] = stub(name.to_s); hash}
164
+ @object = stub('object')
165
+ @instance.stubs(:value).with(@object).returns(@collection)
166
+ end
167
+
168
+ context 'with simple values' do
169
+ should 'freeze the collection and leave members alone' do
170
+ @collection.values.each {|member| member.expects(:freeze).never}
171
+ @collection.expects(:freeze).with.once
172
+ @instance.freeze_for(@object)
173
+ end
174
+ end
175
+
176
+ context 'with mapped values' do
177
+ setup do
178
+ @instance.mapper = stub('mapper', :encode => 'foo')
179
+ @instance.type = @instance.mapper
180
+ end
181
+
182
+ should 'freeze the collection and each collection member' do
183
+ @collection.values.each {|member| member.expects(:freeze).with.once}
184
+ @collection.expects(:freeze).with.once
185
+ @instance.freeze_for(@object)
186
+ end
187
+ end
188
+ end
189
+
190
+ context 'for to_simple' do
191
+ context 'with simple values' do
192
+ setup do
193
+ @container_extra = {:foo => 'foo', :bar => 'bar'}
194
+ @container_added = {:a => 'A', :ab => 'AB'}
195
+ @value = @container_added.clone
196
+ @changed_members = SimpleMapper::ChangeHash.new
197
+ @value.stubs(:simple_mapper_changes).returns(@changed_members)
198
+ end
199
+
200
+ should 'add keys and values to container' do
201
+ @instance.expects(:value).with(@object).returns(@value)
202
+ result = @instance.to_simple(@object, @container_extra.clone)
203
+ assert_equal @container_extra.merge(@container_added), result
204
+ end
205
+
206
+ should 'filters keys through :to_simple_key when adding to container' do
207
+ @instance.expects(:value).with(@object).returns(@value)
208
+ expectation = {}
209
+ @container_added.each do |k, v|
210
+ key = k.to_s + ' transformed'
211
+ expectation[key] = v
212
+ @instance.expects(:to_simple_key).with(k).returns(key)
213
+ end
214
+ result = @instance.to_simple(@object, @container_extra.clone)
215
+ assert_equal @container_extra.merge(expectation), result
216
+ end
217
+
218
+ should 'encode typed values' do
219
+ @instance.type = mock('type')
220
+ @instance.expects(:value).with(@object).returns(@value)
221
+ expectation = {}
222
+ @container_added.each do |k, v|
223
+ expectation[k] = v + ' encoded'
224
+ @instance.type.expects(:encode).with(v).returns(expectation[k])
225
+ end
226
+ result = @instance.to_simple(@object, @container_extra.clone)
227
+ assert_equal @container_extra.merge(expectation), result
228
+ end
229
+
230
+ context 'with changes' do
231
+ setup do
232
+ @instance.expects(:value).with(@object).returns(@value)
233
+ @value.instance_eval do
234
+ def is_member?(key)
235
+ key? key
236
+ end
237
+ end
238
+ end
239
+
240
+ context 'and :changed option' do
241
+ should 'not include any unchanged members' do
242
+ result = @instance.to_simple(@object, @container_extra.clone, :changed => true)
243
+ assert_equal @container_extra, result
244
+ end
245
+
246
+ should 'include members that are changed' do
247
+ key = @container_added.keys.first
248
+ @changed_members[key] = true
249
+ result = @instance.to_simple(@object, @container_extra.clone, :changed => true)
250
+ assert_equal @container_extra.merge(key => @container_added[key]), result
251
+ end
252
+
253
+ should 'include members that are deleted' do
254
+ @changed_members[:removed_a] = true
255
+ @changed_members[:removed_b] = true
256
+ result = @instance.to_simple(@object, @container_extra.clone, :changed => true)
257
+ deletes = @changed_members.keys.inject({}) {|m,k| m[k] = nil; m}
258
+ assert_equal @container_extra.merge(deletes), result
259
+ end
260
+ end
261
+
262
+ context 'and :all option' do
263
+ setup do
264
+ @changed_members[:removed_a] = true
265
+ @changed_members[:removed_b] = true
266
+ end
267
+
268
+ should 'include all members included deleted ones' do
269
+ result = @instance.to_simple(@object, @container_extra.clone, :all => true)
270
+ deletes = @changed_members.keys.inject({}) {|m,k| m[k] = nil; m}
271
+ assert_equal @container_extra.merge(@container_added).merge(deletes), result
272
+ end
273
+
274
+ should 'not included deleted members if :defined option present' do
275
+ result = @instance.to_simple(@object, @container_extra.clone, :all => true, :defined => true)
276
+ assert_equal @container_extra.merge(@container_added), result
277
+ end
278
+ end
279
+ end
280
+ end
281
+
282
+ context 'with mapped values' do
283
+ setup do
284
+ @instance.mapper = stub('mapper', :encode => 'foo')
285
+ @instance.type = @instance.mapper
286
+ @base_value = {}
287
+ @changed_members = SimpleMapper::ChangeHash.new
288
+ @base_value.stubs(:simple_mapper_changes).returns(@changed_members)
289
+ @expected_value = {}
290
+ @container = {}
291
+ end
292
+
293
+ should 'apply to_simple on values' do
294
+ [:a, :ab, :abc].each do |sym|
295
+ val = sym.to_s.upcase
296
+ expect = val + ' simplified'
297
+ @expected_value[sym] = {sym => expect}
298
+ @base_value[sym] = mock(val)
299
+ @base_value[sym].expects(:to_simple).with({}).returns(@expected_value[sym].clone)
300
+ end
301
+ @instance.expects(:value).with(@object).returns(@base_value)
302
+ result = @instance.to_simple(@object, @container) || {}
303
+ assert_equal @expected_value, result
304
+ end
305
+
306
+ should 'apply to_simple on values and use string keys on collection when requested' do
307
+ [:a, :ab, :abc].each do |sym|
308
+ val = sym.to_s.upcase
309
+ expect = val + ' simplified'
310
+ @expected_value[sym.to_s] = {sym.to_s => expect}
311
+ @base_value[sym] = mock(val)
312
+ @base_value[sym].expects(:to_simple).with({:string_keys => true}).returns(@expected_value[sym.to_s].clone)
313
+ end
314
+ @instance.expects(:value).with(@object).returns(@base_value)
315
+ result = @instance.to_simple(@object, @container, :string_keys => true) || {}
316
+ assert_equal @expected_value, result
317
+ end
318
+
319
+ context 'with :changed' do
320
+ should 'include members marked as changed in the collection and members that identify themselves as changed' do
321
+ [:collection_changed, :item_changed, :not_changed].each do |sym|
322
+ val = sym.to_s.upcase
323
+ expect = val + ' simplified'
324
+ @expected_value[sym] = {sym => expect}
325
+ @base_value[sym] = mock(val)
326
+ @base_value[sym].stubs(:to_simple).with({:changed => true}).returns(@expected_value[sym].clone)
327
+ @base_value.instance_eval do
328
+ def is_member?(key)
329
+ key? key
330
+ end
331
+ end
332
+ end
333
+ @base_value[:item_changed].expects(:changed?).with.returns(true)
334
+ @base_value[:not_changed].expects(:changed?).with.returns(false)
335
+ @base_value[:collection_changed].expects(:changed?).never
336
+ @changed_members[:collection_changed] = true
337
+ @instance.expects(:value).with(@object).returns(@base_value)
338
+ result = @instance.to_simple(@object, @container, :changed => true)
339
+ @expected_value.delete(:not_changed)
340
+ assert_equal @expected_value, result
341
+ end
342
+ end
343
+ end
344
+ end
345
+
346
+ context 'when applying type to a source/default value' do
347
+ setup do
348
+ @input = {:a => 'A', :abc => 'ABC', :aabb => 'AABB'}
349
+ @expected = @input.inject({}) {|h, kv| h[kv[0]] = kv[1] + ' encoded'; h}
350
+ @type = stub('type')
351
+ @instance.stubs(:converter).returns(@type)
352
+ end
353
+
354
+ should 'decode all values in the input hash' do
355
+ @expected.each do |key, value|
356
+ @type.expects(:decode).with(@input[key]).returns(value)
357
+ end
358
+ @instance.apply_type(@input)
359
+ end
360
+
361
+ should 'return new collection hash of keys mapped to decoded values' do
362
+ @expected.each do |key, value|
363
+ @type.stubs(:decode).with(@input[key]).returns(value)
364
+ end
365
+ result = @instance.apply_type(@input)
366
+ assert_equal @expected, result
367
+ assert_equal SimpleMapper::Collection::Hash, result.class
368
+ end
369
+
370
+ should 'initialize collection hash with the attribute instance' do
371
+ @expected.each do |key, value|
372
+ @type.stubs(:decode).with(@input[key]).returns(value)
373
+ end
374
+ result = @instance.apply_type(@input)
375
+ assert_equal @instance, result.attribute
376
+ end
377
+ end
378
+ end
379
+ end
@@ -0,0 +1,55 @@
1
+ require 'test_helper'
2
+
3
+ class SimpleMapperAttributePatternTest < Test::Unit::TestCase
4
+ context 'A SimpleMapper::Attribute::Pattern instance' do
5
+ setup do
6
+ @class = SimpleMapper::Attribute::Pattern
7
+ @pattern = /^a+/
8
+ @instance = @class.new(@name = :pattern_collection, :pattern => @pattern)
9
+ end
10
+
11
+ should 'extend SimpleMapper::Attribute' do
12
+ assert_equal true, @instance.is_a?(SimpleMapper::Attribute)
13
+ end
14
+
15
+ should 'have a pattern attribute' do
16
+ assert_equal @pattern, @instance.pattern
17
+ new_pattern = /foo/
18
+ @instance.pattern = new_pattern
19
+ assert_equal new_pattern, @instance.pattern
20
+ end
21
+
22
+ should 'require a pattern value' do
23
+ # no problem if it has a :match method
24
+ item = stub('item', :match => true)
25
+ @instance.pattern = item
26
+ assert_equal item, @instance.pattern
27
+
28
+ # no rikey if no :match
29
+ item = stub('bad bad not-a-pattern')
30
+ assert_raise(SimpleMapper::InvalidPatternException) do
31
+ @instance.pattern = item
32
+ end
33
+ end
34
+
35
+ context 'with a SimpleMapper::Attributes-derived object' do
36
+ setup do
37
+ # initialize with non-matching keys
38
+ @source_values = {:b => 'Bad', :c => 'Cows', :d => 'Deathdog'}
39
+ @object = stub('object', :simple_mapper_source => @source_values)
40
+ end
41
+
42
+ context 'for source value' do
43
+ should 'return empty hash if no keys in the source match the pattern' do
44
+ assert_equal({}, @instance.source_value(@object))
45
+ end
46
+
47
+ should 'return hash with key/value pairs for only the keys matching pattern' do
48
+ expected = {:a => 'A', :abc => 'ABC', :aarp => 'AARP'}
49
+ @source_values.merge! expected
50
+ assert_equal expected, @instance.source_value(@object)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,419 @@
1
+ require 'test_helper'
2
+
3
+ class AttributeTest < Test::Unit::TestCase
4
+ context 'A SimpleMapper::Attribute instance' do
5
+ setup do
6
+ @name = :some_name
7
+ @instance = SimpleMapper::Attribute.new(@name)
8
+ end
9
+
10
+ should 'have a name attribute' do
11
+ assert_equal @name, @instance.name
12
+ assert_equal :foo, @instance.name = :foo
13
+ assert_equal :foo, @instance.name
14
+ end
15
+
16
+ should 'have a key attribute that defaults to the name' do
17
+ assert_equal @name, @instance.key
18
+ assert_equal :some_key, @instance.key = :some_key
19
+ assert_equal :some_key, @instance.key
20
+ end
21
+
22
+ should 'have a type attribute' do
23
+ assert_equal nil, @instance.type
24
+ assert_equal :some_type, @instance.type = :some_type
25
+ assert_equal :some_type, @instance.type
26
+ end
27
+
28
+ should 'have a default attribute' do
29
+ assert_equal nil, @instance.default
30
+ assert_equal :some_default, @instance.default = :some_default
31
+ assert_equal :some_default, @instance.default
32
+ end
33
+
34
+ should 'have a mapper attribute' do
35
+ assert_equal nil, @instance.mapper
36
+ assert_equal :some_mapper, @instance.mapper = :some_mapper
37
+ assert_equal :some_mapper, @instance.mapper
38
+ end
39
+
40
+ should 'get value from instance argument' do
41
+ target = mock('target')
42
+ target.stubs(@name).with.returns(:foo)
43
+ assert_equal :foo, @instance.value(target)
44
+ end
45
+
46
+ context 'freeze_for' do
47
+ should 'do nothing for regular attributes' do
48
+ target = mock('target')
49
+ target.expects(:freeze).never
50
+ @instance.freeze_for(target)
51
+ end
52
+
53
+ should "invoke freeze on instance's nested mapper" do
54
+ target = mock('mapper target')
55
+ target.stubs(@instance.name).returns(mapped_obj = mock('mapped instance'))
56
+ mapped_obj.expects(:freeze).once.with
57
+ @instance.stubs(:mapper).returns(:some_mapper)
58
+ @instance.freeze_for(target)
59
+ end
60
+ end
61
+
62
+ should 'use target object :simple_mapper_changes as state hash of :change_tracking_for' do
63
+ changes = {}
64
+ object = mock('object', :simple_mapper_changes => changes)
65
+ result = @instance.change_tracking_for(object)
66
+ assert_equal result.object_id, changes.object_id
67
+ end
68
+
69
+ context 'change tracking' do
70
+ setup do
71
+ @changes = {}
72
+ @object = stub('object')
73
+ @instance.stubs(:change_tracking_for).with(@object).returns(@changes)
74
+ end
75
+
76
+ should 'mark the attribute as changed within an instance.simple_mapper_changes hash when instance given to :changed!' do
77
+ @instance.changed!(@object)
78
+ assert_equal({@name => true}, @changes)
79
+ end
80
+
81
+ should 'remove the attribute from changes hash when instance given to :changed! along with false' do
82
+ @changes[@name] = true
83
+ @instance.changed!(@object, false)
84
+ assert_equal({}, @changes)
85
+ end
86
+
87
+ should 'return truth of changed state for attribute on instance provided to :changed? based on instance.simple_mapper_changes' do
88
+ assert_equal false, @instance.changed?(@object)
89
+ @changes[@name] = true
90
+ assert_equal true, @instance.changed?(@object)
91
+ end
92
+
93
+ should 'delegate :changed? call to mapped object if attribute has a mapper' do
94
+ @instance.stubs(:mapper).returns(:foo)
95
+ seq = sequence('value calls')
96
+ true_mapper = stub('mapped object with true')
97
+ false_mapper = stub('mapped object with false')
98
+ # value must be checked to retrieve mapped object, so this is a reasonable expectation
99
+ @instance.expects(:value).with(@object).in_sequence(seq).returns(true_mapper)
100
+ true_mapper.expects(:changed?).with.in_sequence(seq).returns(true)
101
+ @instance.expects(:value).with(@object).in_sequence(seq).returns(false_mapper)
102
+ false_mapper.expects(:changed?).with.in_sequence(seq).returns(false)
103
+ result = [@instance.changed?(@object), @instance.changed?(@object)]
104
+ assert_equal [true, false], result
105
+ end
106
+
107
+ should 'return truth of changed state for attribute if attribute has a mapper and value is nil' do
108
+ @instance.stubs(:mapper).returns(:foo)
109
+ @instance.stubs(:value).with(@object).returns(nil)
110
+ assert_equal false, @instance.changed?(@object)
111
+ @changes[@name] = true
112
+ assert_equal true, @instance.changed?(@object)
113
+ end
114
+ end
115
+
116
+ context 'when encoding' do
117
+ should 'pass through the value when type is undefined' do
118
+ assert_equal :foo, @instance.encode(:foo)
119
+ end
120
+
121
+ should 'convert the value with type.encode if type is a convertor' do
122
+ type = stub('type')
123
+ type.expects(:encode).with(:foo).returns(:foo_too)
124
+ @instance.type = type
125
+ assert_equal :foo_too, @instance.encode(:foo)
126
+ end
127
+
128
+ should 'convert the value with type from registry if type is registered' do
129
+ type = stub('type')
130
+ type.expects(:encode).with(:foo).returns(:foo_too)
131
+ SimpleMapper::Attributes.expects(:type_for).with(:some_type).returns({:converter => type})
132
+ @instance.type = :some_type
133
+ assert_equal :foo_too, @instance.encode(:foo)
134
+ end
135
+
136
+ should 'throw an InvalidTypeException if type is specified but is not a convertor' do
137
+ @instance.type = stub('no type')
138
+ assert_raise(SimpleMapper::InvalidTypeException) { @instance.encode(:foo) }
139
+ end
140
+ end
141
+
142
+ context 'using its default_value method' do
143
+ setup do
144
+ @default_name = :default_method
145
+ @default_value = 'some default'
146
+ @object = stub('object', @default_name => @default_value)
147
+ end
148
+
149
+ should 'return nil if there is no default' do
150
+ assert_equal nil, @instance.default_value(@object)
151
+ end
152
+
153
+ should 'return the default value if there is one by invoking the default on the target object' do
154
+ @instance.default = @default_name
155
+ assert_equal @default_value, @instance.default_value(@object)
156
+ end
157
+
158
+ context 'with a default of :from_type' do
159
+ setup do
160
+ @expected_value = 'the default value'
161
+ @type = stub('type', :name => :some_type, :encode => :blah, :decode => :blah)
162
+ @type.expects(:default).once.with.returns(@expected_value)
163
+ @instance.default = :from_type
164
+ end
165
+
166
+ should 'invoke :default on type if it is able' do
167
+ @instance.type = @type
168
+ result = @instance.default_value(@object)
169
+ assert_equal @expected_value, result
170
+ end
171
+
172
+ should 'invoke :default on registered type if type is registered' do
173
+ @instance.type = @type.name
174
+ SimpleMapper::Attributes.stubs(:types).with.returns({@type.name => {
175
+ :name => @type.name,
176
+ :expected_class => @type.class,
177
+ :converter => @type,
178
+ }})
179
+ result = @instance.default_value(@object)
180
+ assert_equal @expected_value, result
181
+ end
182
+ end
183
+ end
184
+ end
185
+
186
+ context 'the SimpleMapper::Attribute constructor' do
187
+ setup do
188
+ @class = SimpleMapper::Attribute
189
+ end
190
+
191
+ should 'allow specification of key in constructor' do
192
+ instance = @class.new(:name, :key => :some_key)
193
+ assert_equal :some_key, instance.key
194
+ end
195
+
196
+ should 'allow specification of type in constructor' do
197
+ instance = @class.new(:name, :type => :some_type)
198
+ assert_equal :some_type, instance.type
199
+ end
200
+
201
+ should 'allow specification of default in constructor' do
202
+ instance = @class.new(:name, :default => :some_default)
203
+ assert_equal :some_default, instance.default
204
+ end
205
+
206
+ should 'allow specification of mapper in constructor' do
207
+ instance = @class.new(:name, :mapper => :some_mapper)
208
+ assert_equal :some_mapper, instance.mapper
209
+ end
210
+ end
211
+
212
+ context 'the SimpleMapper::Attribute :to_simple method' do
213
+ setup do
214
+ @name = :some_attribute
215
+ @key = :some_attribute_key
216
+ @class = SimpleMapper::Attribute
217
+ @instance = @class.new(@name, :key => @key)
218
+ @value = 'some_attribute_value'
219
+
220
+ # the container is provided in each :to_simple call; it's where the
221
+ # simplified representation of the attribute/value should go.
222
+ # We want it to start as non-empty so the test can verify that the
223
+ # to_simple operation is additive rather than destructive
224
+ @container = {:preserve_me => :or_else}
225
+
226
+ @object = stub('object')
227
+ @object.stubs(@name).returns(@value)
228
+ end
229
+
230
+ context 'for an untyped attribute' do
231
+ should 'assign the attribute value as key/pair to the provided container' do
232
+ result = @container.clone
233
+ result[@key] = @value
234
+ @instance.to_simple @object, @container
235
+ assert_equal result, @container
236
+ end
237
+
238
+ should 'not assign key/value if :defined is true and value is nil' do
239
+ @object.stubs(@name).returns(nil)
240
+ result = @container.clone
241
+ @instance.to_simple @object, @container, :defined => true
242
+ assert_equal result, @container
243
+ end
244
+
245
+ should 'assign attr value as key/val pair if :defined is true and value is !nil' do
246
+ result = @container.clone
247
+ result[@key] = @value
248
+ @instance.to_simple @object, @container, :defined => true
249
+ assert_equal result, @container
250
+ end
251
+
252
+ should 'use string keys instead of symbols if :string_keys option is true' do
253
+ result = @container.clone
254
+ result[@key.to_s] = @value
255
+ @instance.to_simple @object, @container, :string_keys => true
256
+ assert_equal result, @container
257
+ end
258
+
259
+ should 'invoke to_simple on value rather than encoding if mapper is set' do
260
+ @instance.mapper = mapper = mock('mapper')
261
+ options = {:some_useless_option => :me}
262
+ @value.expects(:to_simple).with(options).returns(:something_simple)
263
+ result = @container.clone
264
+ result[@key] = :something_simple
265
+ @instance.to_simple @object, @container, options
266
+ assert_equal result, @container
267
+ end
268
+
269
+ should 'assign key with nil value for mapper if :defined is false and value is nil' do
270
+ @instance.mapper = mapper = mock('mapper')
271
+ @object.stubs(@name).returns(nil)
272
+ result = @container.clone
273
+ @instance.to_simple @object, @container
274
+ assert_equal result.merge({@key => nil}), @container
275
+ end
276
+
277
+ should 'use the mapper as type if a mapper is set' do
278
+ @instance.mapper = mapper = mock('mapper')
279
+ assert_equal mapper, @instance.type
280
+ end
281
+ end
282
+
283
+ context 'for a typed attribute' do
284
+ setup do
285
+ @type = stub('type')
286
+ @type.stubs(:encode).with(@value).returns(@encoded_value = :some_encoded_value)
287
+ @instance.type = @type
288
+ end
289
+
290
+ should 'assign the encoded attribute value as key/pair to the provided container' do
291
+ result = @container.clone
292
+ result[@key] = @encoded_value
293
+ @instance.to_simple @object, @container
294
+ assert_equal result, @container
295
+ end
296
+
297
+ should 'not assign key/value if :defined is true and encoded value is nil' do
298
+ @type.stubs(:encode).with(@value).returns(nil)
299
+ result = @container.clone
300
+ @instance.to_simple @object, @container, :defined => true
301
+ assert_equal result, @container
302
+ end
303
+
304
+ should 'assign encoded attr value as key/val pair if :defined is true and value is !nil' do
305
+ result = @container.clone
306
+ result[@key] = @encoded_value
307
+ @instance.to_simple @object, @container, :defined => true
308
+ assert_equal result, @container
309
+ end
310
+
311
+ should 'use string keys instead of symbols if :string_keys option is true' do
312
+ result = @container.clone
313
+ result[@key.to_s] = @encoded_value
314
+ @instance.to_simple @object, @container, :string_keys => true
315
+ assert_equal result, @container
316
+ end
317
+
318
+ should 'use specified type when set rather than using the mapper' do
319
+ @instance.mapper = mapper = mock('mapper')
320
+ assert_equal @type, @instance.type
321
+ end
322
+ end
323
+ end
324
+
325
+ context 'A SimpleMapper::Attribute working with a SimpleMapper::Attributes-based object' do
326
+ setup do
327
+ @class = SimpleMapper::Attribute
328
+ @key = :some_key
329
+ @value = :some_value
330
+ @name = :some_attribute
331
+ @instance = @class.new(@name, :key => @key)
332
+
333
+ @source = {}
334
+ @object.stubs(:simple_mapper_source).with.returns(@source)
335
+ end
336
+
337
+ context 'using the :source_value method' do
338
+ should 'return object source value by key symbol' do
339
+ @source[@key] = @value
340
+ assert_equal @value, @instance.source_value(@object)
341
+ end
342
+
343
+ should 'return object source value by key string if symbol does not exist' do
344
+ @source[@key.to_s] = @value
345
+ assert_equal @value, @instance.source_value(@object)
346
+ end
347
+ end
348
+
349
+ context 'using the :transformed_source_value method' do
350
+ should 'return the source value directly' do
351
+ @instance.expects(:source_value).with(@object).returns(@value)
352
+ assert_equal @value, @instance.transformed_source_value(@object)
353
+ end
354
+
355
+ should 'return the default value if the source value is undefined' do
356
+ @instance.stubs(:source_value).with(@object).returns(nil)
357
+ @instance.expects(:default_value).with(@object).returns(:my_default)
358
+ result = @instance.transformed_source_value(@object)
359
+ #assert_equal :my_default, result
360
+ end
361
+
362
+ context 'with a typed attribute' do
363
+ setup do
364
+ @type = stub('type', :name => :sooper_speshil_tipe)
365
+ @registry = {:name => @type.name, :converter => @type}
366
+ SimpleMapper::Attributes.stubs(:type_for).with(@type.name).returns(@registry)
367
+ end
368
+
369
+ context 'and a type that supports :decode' do
370
+ setup do
371
+ @instance.type = @type
372
+ @type.expects(:decode).with(@value).returns( @encoded = :encoded )
373
+ end
374
+
375
+ should 'return the decoded source value' do
376
+ @instance.stubs(:source_value).with(@object).returns(@value)
377
+ @instance.expects(:default_value).with(@object).never
378
+ assert_equal @encoded, @instance.transformed_source_value(@object)
379
+ end
380
+
381
+ should 'return the decoded default value if no source defined' do
382
+ @instance.stubs(:source_value).with(@object).returns(nil)
383
+ @instance.stubs(:default_value).with(@object).returns(@value)
384
+ result = @instance.transformed_source_value(@object)
385
+ # assert_equal @encoded, result
386
+ end
387
+ end
388
+
389
+ context 'and an expected type' do
390
+ setup do
391
+ @instance.type = @type.name
392
+ @registry[:expected_type] = @value.class
393
+ @type.expects(:decode).never
394
+ end
395
+
396
+ should 'return the source value if it matches' do
397
+ @instance.stubs(:source_value).with(@object).returns(@value)
398
+ assert_equal @value, @instance.transformed_source_value(@object)
399
+ end
400
+
401
+ should 'return the default value if no source defined and default matches' do
402
+ @instance.stubs(:source_value).with(@object).returns(nil)
403
+ @instance.expects(:default_value).with(@object).returns(@value)
404
+ result = @instance.transformed_source_value(@object)
405
+ # assert_equal @value, result
406
+ end
407
+ end
408
+
409
+ should 'return the decoded source value via the registered type' do
410
+ @instance.type = @type.name
411
+ @instance.stubs(:source_value).with(@object).returns(@value)
412
+ @type.expects(:decode).with(@value).returns( encoded = :encoded )
413
+ result = @instance.transformed_source_value(@object)
414
+ assert_equal encoded, result
415
+ end
416
+ end
417
+ end
418
+ end
419
+ end