simple_mapper 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +165 -0
- data/Rakefile.rb +11 -0
- data/lib/simple_mapper.rb +8 -0
- data/lib/simple_mapper/attribute.rb +119 -0
- data/lib/simple_mapper/attribute/collection.rb +130 -0
- data/lib/simple_mapper/attribute/pattern.rb +19 -0
- data/lib/simple_mapper/attributes.rb +196 -0
- data/lib/simple_mapper/attributes/types.rb +224 -0
- data/lib/simple_mapper/change_hash.rb +14 -0
- data/lib/simple_mapper/collection.rb +169 -0
- data/lib/simple_mapper/exceptions.rb +10 -0
- data/test/integration/attribute_change_tracking_test.rb +181 -0
- data/test/integration/attribute_meta_interaction_test.rb +169 -0
- data/test/integration/attribute_pattern_test.rb +77 -0
- data/test/integration/to_simple_test.rb +128 -0
- data/test/test_helper.rb +11 -0
- data/test/unit/attribute_collection_test.rb +379 -0
- data/test/unit/attribute_pattern_test.rb +55 -0
- data/test/unit/attribute_test.rb +419 -0
- data/test/unit/attributes_test.rb +561 -0
- data/test/unit/collection_array_test.rb +194 -0
- data/test/unit/collection_hash_test.rb +139 -0
- data/test/unit/types_test.rb +314 -0
- metadata +140 -0
data/test/test_helper.rb
ADDED
@@ -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
|