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,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