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