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
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'delegate'
|
3
|
+
class CollectionArrayTest < Test::Unit::TestCase
|
4
|
+
context 'A SimpleMapper::Collection::Array' do
|
5
|
+
setup do
|
6
|
+
@class = SimpleMapper::Collection::Array
|
7
|
+
end
|
8
|
+
|
9
|
+
should 'be equivalent to a standard Array' do
|
10
|
+
array = [:a, :b, :c]
|
11
|
+
instance = @class.new(array)
|
12
|
+
assert_equal array, instance
|
13
|
+
assert_equal @class, instance.class
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'allow array-style assignments, lookups, size checks, etc' do
|
17
|
+
instance = @class.new
|
18
|
+
assert_equal 0, instance.size
|
19
|
+
instance << :a
|
20
|
+
assert_equal 1, instance.size
|
21
|
+
assert_equal :a, instance[0]
|
22
|
+
instance[4] = :b
|
23
|
+
assert_equal 5, instance.size
|
24
|
+
assert_equal [:a, nil, nil, nil, :b], instance
|
25
|
+
end
|
26
|
+
|
27
|
+
should 'disallow non-integer indexes' do
|
28
|
+
instance = @class.new
|
29
|
+
assert_raise(TypeError) { instance[:a] = 'a' }
|
30
|
+
end
|
31
|
+
|
32
|
+
should 'provide list of indexes via :keys' do
|
33
|
+
assert_equal (0..3).to_a, @class.new([:a, :b, :c, :d]).keys
|
34
|
+
end
|
35
|
+
|
36
|
+
should 'yield key/value pairs instead of just value for :inject' do
|
37
|
+
source = [:a, :b, :c, :d, :e]
|
38
|
+
expected = (0..(source.size - 1)).to_a.collect {|key| [key, source[key]]}
|
39
|
+
result = @class.new(source).inject([]) {|a, pair| a << pair; a}
|
40
|
+
assert_equal expected, result
|
41
|
+
end
|
42
|
+
|
43
|
+
should 'have an :attribute attribute' do
|
44
|
+
instance = @class.new
|
45
|
+
assert_equal nil, instance.attribute
|
46
|
+
attrib = stub('attribute')
|
47
|
+
assert_equal attrib, instance.attribute = attrib
|
48
|
+
assert_equal attrib, instance.attribute
|
49
|
+
end
|
50
|
+
|
51
|
+
should 'allow creation of new mapper instances through :build' do
|
52
|
+
mapper = stub('mapper')
|
53
|
+
attrib = stub('attrib', :mapper => mapper)
|
54
|
+
seq = sequence('build invocations')
|
55
|
+
mapper.expects(:new).with.in_sequence(seq).returns(:one)
|
56
|
+
mapper.expects(:new).with(:a => 'A').in_sequence(seq).returns(:two)
|
57
|
+
mapper.expects(:new).with(:a, :b, :c).in_sequence(seq).returns(:three)
|
58
|
+
instance = @class.new
|
59
|
+
instance.attribute = attrib
|
60
|
+
result = []
|
61
|
+
result << instance.build
|
62
|
+
result << instance.build(:a => 'A')
|
63
|
+
result << instance.build(:a, :b, :c)
|
64
|
+
assert_equal [:one, :two, :three], result
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with change_tracking true' do
|
68
|
+
context 'when appending to the array' do
|
69
|
+
setup do
|
70
|
+
@instance = @class.new
|
71
|
+
@instance.attribute = stub('attrib', :mapper => nil)
|
72
|
+
@instance.change_tracking = false
|
73
|
+
@values = [:a, :b, :c]
|
74
|
+
@instance.push(*@values)
|
75
|
+
@instance.change_tracking = true
|
76
|
+
end
|
77
|
+
|
78
|
+
should 'mark new item for :<< as changed' do
|
79
|
+
@instance << :zzz
|
80
|
+
assert_equal [@values.size], @instance.changed_members
|
81
|
+
end
|
82
|
+
|
83
|
+
should 'mark new items for :push as changed' do
|
84
|
+
@instance.push(:xxx, :yyy, :zzz)
|
85
|
+
assert_equal [@values.size, @values.size + 1, @values.size + 2], @instance.changed_members
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'when deleting from the array' do
|
91
|
+
setup do
|
92
|
+
@instance = @class.new
|
93
|
+
@instance.attribute = stub('attrib', :mapper => nil)
|
94
|
+
@instance.change_tracking = false
|
95
|
+
@values = ('a'..'z').to_a
|
96
|
+
@instance.push(*@values)
|
97
|
+
@instance.change_tracking = true
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'with slice! given a range' do
|
101
|
+
should 'identify all indexes from the range minimum to the original size as changed' do
|
102
|
+
range = 5..10
|
103
|
+
expected_list = @values.clone
|
104
|
+
expected_slice = expected_list.slice!(range)
|
105
|
+
expected_changes = (5..(@values.size - 1)).to_a
|
106
|
+
assert_equal expected_slice, @instance.slice!(range)
|
107
|
+
assert_equal expected_list, @instance
|
108
|
+
assert_equal expected_changes, @instance.changed_members
|
109
|
+
end
|
110
|
+
|
111
|
+
should 'not mark changes for indexes outside the original size' do
|
112
|
+
range = (@values.size - 2)..(@values.size + 1)
|
113
|
+
expected_list = @values.clone
|
114
|
+
expected_slice = expected_list.slice!(range)
|
115
|
+
assert_equal expected_slice, @instance.slice!(range)
|
116
|
+
assert_equal expected_list, @instance
|
117
|
+
assert_equal [@values.size - 2, @values.size - 1], @instance.changed_members
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'with slice! given a start index and length' do
|
122
|
+
should 'identify all indexes from start up to original size as changed' do
|
123
|
+
start = @values.size - 5
|
124
|
+
expected_list = @values.clone
|
125
|
+
expected_slice = expected_list.slice!(start, 3)
|
126
|
+
assert_equal expected_slice, @instance.slice!(start, 3)
|
127
|
+
assert_equal expected_list, @instance
|
128
|
+
assert_equal ((@values.size - 5)..(@values.size - 1)).to_a, @instance.changed_members
|
129
|
+
end
|
130
|
+
|
131
|
+
should 'not mark changes for indexes outside the original size' do
|
132
|
+
start = @values.size - 2
|
133
|
+
expected_list = @values.clone
|
134
|
+
expected_slice = expected_list.slice!(start, 1000)
|
135
|
+
assert_equal expected_slice, @instance.slice!(start, 1000)
|
136
|
+
assert_equal expected_list, @instance
|
137
|
+
assert_equal [@values.size - 2, @values.size - 1], @instance.changed_members
|
138
|
+
end
|
139
|
+
|
140
|
+
should 'handle negative start index properly' do
|
141
|
+
expected_list = @values.clone
|
142
|
+
expected_slice = expected_list.slice!(-5, 1)
|
143
|
+
assert_equal expected_slice, @instance.slice!(-5, 1)
|
144
|
+
assert_equal expected_list, @instance
|
145
|
+
assert_equal ((@values.size - 5)..(@values.size - 1)).to_a, @instance.changed_members
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'with delete_at' do
|
150
|
+
should 'return deleted item and marked index to end as changed if index is in range' do
|
151
|
+
index = @values.size - 5
|
152
|
+
assert_equal @values[index], @instance.delete_at(index)
|
153
|
+
assert_equal @values.slice(0, index) + @values.slice(index + 1, 4), @instance
|
154
|
+
assert_equal (index..@values.size - 1).to_a, @instance.changed_members
|
155
|
+
end
|
156
|
+
|
157
|
+
should 'do nothing but return nil if index out of range' do
|
158
|
+
assert_equal nil, @instance.delete_at(@values.size)
|
159
|
+
assert_equal @values, @instance
|
160
|
+
assert_equal [], @instance.changed_members
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'with reject!' do
|
165
|
+
should 'return the altered list and mark all items as changed from first removed to end' do
|
166
|
+
start_index = @values.size - 10
|
167
|
+
expected_values = @values.clone.reject! {|x| x >= @values[start_index]}
|
168
|
+
assert_equal(expected_values, @instance.reject! {|val| val >= @values[start_index]})
|
169
|
+
assert_equal (start_index..@values.size - 1).to_a, @instance.changed_members
|
170
|
+
end
|
171
|
+
|
172
|
+
should 'do nothing but return nil if nothing is deleted' do
|
173
|
+
assert_equal(nil, @instance.reject! {|v| false})
|
174
|
+
assert_equal [], @instance.changed_members
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'with delete_if' do
|
179
|
+
should 'return the altered list and mark all items as changed from first removed to end' do
|
180
|
+
start = @values.size - 10
|
181
|
+
key = @values[start]
|
182
|
+
expected_values = @values.find_all {|x| x < key}
|
183
|
+
assert_equal(expected_values, @instance.delete_if {|val| val >= key})
|
184
|
+
assert_equal (start..@values.size-1).to_a, @instance.changed_members
|
185
|
+
end
|
186
|
+
|
187
|
+
should 'do nothing but return unchanged list if nothing is deleted' do
|
188
|
+
assert_equal(@values, @instance.delete_if {|val| false})
|
189
|
+
assert_equal [], @instance.changed_members
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CollectionHashTest < Test::Unit::TestCase
|
4
|
+
context 'A SimpleMapper::Collection::Hash' do
|
5
|
+
setup do
|
6
|
+
@class = SimpleMapper::Collection::Hash
|
7
|
+
end
|
8
|
+
|
9
|
+
should 'be equivalent to a hash passed to the constructor' do
|
10
|
+
hash = {:a => 'A', :b => 'B'}
|
11
|
+
instance = @class.new(hash)
|
12
|
+
assert_equal hash, instance
|
13
|
+
assert_equal @class, instance.class
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'allow hash-style lookups, assignments, etc.' do
|
17
|
+
instance = @class.new
|
18
|
+
assert_equal nil, instance[:foo]
|
19
|
+
assert_equal :bar, instance[:foo] = :bar
|
20
|
+
assert_equal :bar, instance[:foo]
|
21
|
+
assert_equal true, instance.has_key?(:foo)
|
22
|
+
assert_equal false, instance.has_key?(:blah)
|
23
|
+
assert_equal :blee, instance[:boo] = :blee
|
24
|
+
assert_equal :blee, instance[:boo]
|
25
|
+
assert_equal([:boo, :foo], instance.keys.sort_by {|k| k.to_s})
|
26
|
+
end
|
27
|
+
|
28
|
+
should 'have an :attribute attribute' do
|
29
|
+
instance = @class.new
|
30
|
+
assert_equal nil, instance.attribute
|
31
|
+
attrib = stub('attribute_object')
|
32
|
+
assert_equal attrib, instance.attribute = attrib
|
33
|
+
assert_equal attrib, instance.attribute
|
34
|
+
end
|
35
|
+
|
36
|
+
should 'allow construction of new mapper instances through :build' do
|
37
|
+
mapper = stub('mapper')
|
38
|
+
attrib = stub('attribute_object', :mapper => mapper)
|
39
|
+
seq = sequence('build invocations')
|
40
|
+
mapper.expects(:new).with.in_sequence(seq).returns(:one)
|
41
|
+
mapper.expects(:new).with(:a => 'A').in_sequence(seq).returns(:two)
|
42
|
+
mapper.expects(:new).with(:foo, :bar, :a => 'A', :b => 'B').in_sequence(seq).returns(:three)
|
43
|
+
instance = @class.new
|
44
|
+
instance.attribute = attrib
|
45
|
+
result = []
|
46
|
+
result << instance.build
|
47
|
+
result << instance.build(:a => 'A')
|
48
|
+
result << instance.build(:foo, :bar, :a => 'A', :b => 'B')
|
49
|
+
assert_equal [:one, :two, :three], result
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'tracking changes' do
|
53
|
+
should 'be off by default' do
|
54
|
+
instance = @class.new
|
55
|
+
assert_equal false, (instance.change_tracking || false)
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'for assignment' do
|
59
|
+
should 'mark newly-assigned key as changed and mark entre value as changed if new value supports :all_changed' do
|
60
|
+
instance = @class.new
|
61
|
+
instance.change_tracking = true
|
62
|
+
assert_equal([], instance.changed_members)
|
63
|
+
instance[:a] = 'A'
|
64
|
+
assert_equal([:a], instance.changed_members)
|
65
|
+
|
66
|
+
change_me = stub('change_me')
|
67
|
+
change_me.expects(:all_changed!).with
|
68
|
+
instance[:b] = change_me
|
69
|
+
assert_equal([:a, :b], instance.changed_members)
|
70
|
+
end
|
71
|
+
|
72
|
+
should 'mark replaced key as changed' do
|
73
|
+
instance = @class.new({:a => 'A'})
|
74
|
+
instance.change_tracking = true
|
75
|
+
assert_equal([], instance.changed_members)
|
76
|
+
instance[:a] = 'Aprime'
|
77
|
+
assert_equal([:a], instance.changed_members)
|
78
|
+
end
|
79
|
+
|
80
|
+
should 'mark replaced key as changed and mark entire value as changed if new value supports :all_changed!' do
|
81
|
+
instance = @class.new({:a => 'A'})
|
82
|
+
instance.change_tracking = true
|
83
|
+
assert_equal([], instance.changed_members)
|
84
|
+
obj = stub('new value')
|
85
|
+
obj.expects(:all_changed!).with.returns(true)
|
86
|
+
instance[:a] = obj
|
87
|
+
assert_equal([:a], instance.changed_members)
|
88
|
+
end
|
89
|
+
|
90
|
+
should 'not track any changes on assignment if track_changes is false' do
|
91
|
+
instance = @class.new({:a => 'A', :b => 'B'})
|
92
|
+
instance.change_tracking = false
|
93
|
+
assert_equal([], instance.changed_members)
|
94
|
+
instance[:a] = 'A prime'
|
95
|
+
instance[:b] = nil
|
96
|
+
instance[:c] = 'C'
|
97
|
+
assert_equal([], instance.changed_members)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'for deletion' do
|
102
|
+
setup do
|
103
|
+
@key = :delete_me
|
104
|
+
@value = 'delete me!'
|
105
|
+
@keep_key = :keep_me
|
106
|
+
@keep_value = 'keep me!'
|
107
|
+
@instance = @class.new({@key => @value, @keep_key => @keep_value})
|
108
|
+
@instance.change_tracking = true
|
109
|
+
end
|
110
|
+
|
111
|
+
should 'mark key as changed when key is specified in a :delete' do
|
112
|
+
assert_equal @value, @instance.delete(@key)
|
113
|
+
assert_equal false, @instance.key?(@key)
|
114
|
+
assert_equal [@key], @instance.changed_members
|
115
|
+
end
|
116
|
+
|
117
|
+
should 'mark key as changed when key is removed via :delete_if' do
|
118
|
+
assert_equal({@keep_key => @keep_value}, @instance.delete_if {|k,v| k == @key})
|
119
|
+
assert_equal false, @instance.key?(@key)
|
120
|
+
assert_equal [@key], @instance.changed_members
|
121
|
+
end
|
122
|
+
|
123
|
+
should 'mark key as changed when key is removed via :reject!' do
|
124
|
+
assert_equal({@keep_key => @keep_value}, @instance.reject! {|k,v| k == @key})
|
125
|
+
assert_equal false, @instance.key?(@key)
|
126
|
+
assert_equal [@key], @instance.changed_members
|
127
|
+
end
|
128
|
+
|
129
|
+
should 'mark nothing as changed if change_tracking is false at delete time' do
|
130
|
+
@instance.change_tracking = false
|
131
|
+
@instance.delete(:a)
|
132
|
+
@instance.delete_if {|k, v| true}
|
133
|
+
assert_equal [], @instance.changed_members
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,314 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
class AttributesTypesTest < Test::Unit::TestCase
|
5
|
+
context 'The Float type' do
|
6
|
+
setup do
|
7
|
+
@type = SimpleMapper::Attributes::Types::Float
|
8
|
+
end
|
9
|
+
|
10
|
+
should 'decode the empty string to nil' do
|
11
|
+
assert_equal nil, @type.decode('')
|
12
|
+
end
|
13
|
+
|
14
|
+
should 'decode simple integer strings' do
|
15
|
+
[1, 2, 3, 5, 7, 11, 13, 17, 21129].each do |num|
|
16
|
+
result = @type.decode(num.to_s)
|
17
|
+
assert_equal Float, result.class
|
18
|
+
assert_equal num.to_f, result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'decode actual integers' do
|
23
|
+
[1, 13, 21000, 4600].each do |num|
|
24
|
+
result = @type.decode(num)
|
25
|
+
assert_equal Float, result.class
|
26
|
+
assert_equal num.to_f, result
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
should 'decode valid floats' do
|
31
|
+
[1.31, 46.723, 0.00290004, 16.161616].each do |num|
|
32
|
+
result = @type.decode(num.to_s)
|
33
|
+
assert_equal Float, result.class
|
34
|
+
assert_equal num, result
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
should 'handle float with decimal point but not fractional portion' do
|
39
|
+
assert_equal 21.0, @type.decode('21.')
|
40
|
+
end
|
41
|
+
|
42
|
+
should 'throw a TypeConversionException given invalid decode input' do
|
43
|
+
['total junk', :more_garbage, '12a32b23c', ' '].each do |value|
|
44
|
+
assert_raise(SimpleMapper::TypeConversionException) { @type.decode(value) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
should 'encode numeric inputs to a string' do
|
49
|
+
[1, 1.0, 0, 0.0, 21129, 46.723, 15.1515].each do |num|
|
50
|
+
assert_equal num.to_f.to_s, @type.encode(num)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
should 'encode nil as nil' do
|
55
|
+
assert_equal nil, @type.encode(nil)
|
56
|
+
end
|
57
|
+
|
58
|
+
should 'throw a TypeConversionException given non-numeric inputs for encode' do
|
59
|
+
['', :xyz, 'abc', ' 0 . 77asd '].each do |input|
|
60
|
+
assert_raise(SimpleMapper::TypeConversionException) { @type.encode(input) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
should 'provide a default of nil' do
|
65
|
+
assert_equal nil, @type.default
|
66
|
+
end
|
67
|
+
|
68
|
+
should 'be registered as :float' do
|
69
|
+
assert_equal({:name => :float,
|
70
|
+
:expected_type => Float,
|
71
|
+
:converter => @type,}, SimpleMapper::Attributes.type_for(:float))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'the String type' do
|
76
|
+
setup do
|
77
|
+
@type = SimpleMapper::Attributes::Types::String
|
78
|
+
end
|
79
|
+
|
80
|
+
should 'encode nil as nil' do
|
81
|
+
assert_equal nil, @type.encode(nil)
|
82
|
+
end
|
83
|
+
|
84
|
+
should 'decode nil as nil' do
|
85
|
+
assert_equal nil, @type.decode(nil)
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'given input with to_s' do
|
89
|
+
setup do
|
90
|
+
@value = 'to_s result'
|
91
|
+
@input = mock('input')
|
92
|
+
@input.expects(:to_s).with.returns(@value)
|
93
|
+
end
|
94
|
+
|
95
|
+
should 'return result of :to_s on input for :encode' do
|
96
|
+
assert_equal @value, @type.encode(@input)
|
97
|
+
end
|
98
|
+
|
99
|
+
should 'return result of :to_s on input for :decode' do
|
100
|
+
assert_equal @value, @type.decode(@input)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
should 'provide empty string for the default' do
|
105
|
+
assert_equal '', @type.default
|
106
|
+
end
|
107
|
+
|
108
|
+
should 'be registered as :string' do
|
109
|
+
assert_equal({:name => :string,
|
110
|
+
:expected_type => String,
|
111
|
+
:converter => @type,}, SimpleMapper::Attributes.type_for(:string))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'the SimpleUUID type' do
|
116
|
+
setup do
|
117
|
+
@type = SimpleMapper::Attributes::Types::SimpleUUID
|
118
|
+
@class = SimpleUUID::UUID
|
119
|
+
end
|
120
|
+
|
121
|
+
should 'decode and encode nil as nil' do
|
122
|
+
[:decode, :encode].each {|op| assert_equal nil, @type.send(op, nil)}
|
123
|
+
end
|
124
|
+
|
125
|
+
should 'return GUID string representation for :encode of actual UUID object' do
|
126
|
+
@uuid = @class.new
|
127
|
+
result = @type.encode(@uuid)
|
128
|
+
assert_equal String, result.class
|
129
|
+
assert_equal @uuid.to_guid, result
|
130
|
+
end
|
131
|
+
|
132
|
+
should 'return GUID string representation for :encode of a GUID-conforming string' do
|
133
|
+
@uuid = @class.new
|
134
|
+
result = @type.encode(@uuid.to_guid)
|
135
|
+
assert_equal String, result.class
|
136
|
+
assert_equal @uuid.to_guid, result
|
137
|
+
end
|
138
|
+
|
139
|
+
should 'parse string and return GUID-conforming string for :decode' do
|
140
|
+
@uuid = @class.new
|
141
|
+
assert_equal @uuid.to_guid, @type.decode(@uuid.to_guid)
|
142
|
+
end
|
143
|
+
|
144
|
+
should ':decode a SimpleUUID::UUID instance to a GUID string' do
|
145
|
+
@uuid = @class.new
|
146
|
+
assert_equal @uuid.to_guid, @type.decode(@uuid)
|
147
|
+
end
|
148
|
+
|
149
|
+
should 'a new SimpleUUID::UUID-based GUID string for the default' do
|
150
|
+
assert_equal String, (first = @type.default).class
|
151
|
+
assert_equal String, (second = @type.default).class
|
152
|
+
assert_not_equal first, second
|
153
|
+
assert_not_equal @class.new(first).to_guid, @class.new(second).to_guid
|
154
|
+
assert_equal first, @class.new(first).to_guid
|
155
|
+
assert_equal second, @class.new(second).to_guid
|
156
|
+
end
|
157
|
+
|
158
|
+
should 'be registered as :simple_uuid' do
|
159
|
+
assert_equal({:name => :simple_uuid,
|
160
|
+
:expected_type => nil,
|
161
|
+
:converter => @type}, SimpleMapper::Attributes.type_for(:simple_uuid))
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context 'the Timestamp type' do
|
166
|
+
setup do
|
167
|
+
@type = SimpleMapper::Attributes::Types::Timestamp
|
168
|
+
@class = DateTime
|
169
|
+
@format = '%Y-%m-%d %H:%M:%S%z'
|
170
|
+
# get time with appropriate resolution
|
171
|
+
@now = @class.strptime(@class.now.strftime(@format), @format)
|
172
|
+
end
|
173
|
+
|
174
|
+
should ':decode a timestamp string with %Y-%m-%d %H:%M:%S%z' do
|
175
|
+
result = @type.decode(@now.strftime(@format))
|
176
|
+
assert_equal @now, result
|
177
|
+
end
|
178
|
+
|
179
|
+
should ':encode a timestamp as string with %Y-%m-%d %H:%M:%S%z structure' do
|
180
|
+
assert_equal @now.strftime(@format), @type.encode(@now)
|
181
|
+
end
|
182
|
+
|
183
|
+
should 'pass nil through untouched for :encode and :decode' do
|
184
|
+
assert_equal nil, @type.encode(nil)
|
185
|
+
assert_equal nil, @type.decode(nil)
|
186
|
+
end
|
187
|
+
|
188
|
+
should 'provide current date/time as default' do
|
189
|
+
# yes, if another process/thread preempts this one on a heavily-loaded server,
|
190
|
+
# there's a possibility that these won't match. I'm willing to live with that.
|
191
|
+
assert_equal @now.strftime(@format), (default = @type.default) && default.strftime(@format)
|
192
|
+
end
|
193
|
+
|
194
|
+
should ':decode a DateTime instance as identity (pass through)' do
|
195
|
+
assert_equal @now, @type.decode(@now)
|
196
|
+
end
|
197
|
+
|
198
|
+
should 'be registered as :timestamp type' do
|
199
|
+
assert_equal({:name => :timestamp,
|
200
|
+
:expected_type => @class,
|
201
|
+
:converter => @type}, SimpleMapper::Attributes.type_for(:timestamp))
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
context 'the Integer type' do
|
206
|
+
setup do
|
207
|
+
@type = SimpleMapper::Attributes::Types::Integer
|
208
|
+
@class = Integer
|
209
|
+
@ints = [0, 54, 32, 65535, -12, -65535]
|
210
|
+
end
|
211
|
+
|
212
|
+
should 'convert a numeric string into something string-like' do
|
213
|
+
@ints.each do |int|
|
214
|
+
assert_equal int, @type.decode(int.to_s)
|
215
|
+
assert_equal int.to_s, @type.encode(int.to_s)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
should 'convert integer values as strings' do
|
220
|
+
@ints.each do |int|
|
221
|
+
assert_equal int, @type.decode(int)
|
222
|
+
assert_equal int.to_s, @type.encode(int)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
should 'handle non-integer objects gracefully if they have integer values' do
|
227
|
+
[1.0, '1.0', -12.0, '-12.0', 1245.0, '1245.0', Rational(1,1), Rational(4500,4500)].each do |val|
|
228
|
+
assert_equal val.to_i, @type.decode(val)
|
229
|
+
assert_equal val.to_i, @type.encode(val).to_i
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
should 'throw type conversion exception if integer value does not appear to match input' do
|
234
|
+
['abc', nil, 14.5, Rational(5,16)].each do |val|
|
235
|
+
assert_raise(SimpleMapper::TypeConversionException) { @type.encode(val) }
|
236
|
+
assert_raise(SimpleMapper::TypeConversionException) { @type.decode(val) }
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
should 'be registered as :integer type' do
|
241
|
+
assert_equal({:expected_type => @class,
|
242
|
+
:name => :integer,
|
243
|
+
:converter => @type}, SimpleMapper::Attributes.type_for(:integer))
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context 'the TimestampHighRes type' do
|
248
|
+
require 'bigdecimal'
|
249
|
+
setup do
|
250
|
+
@type = SimpleMapper::Attributes::Types::TimestampHighRes
|
251
|
+
@class = DateTime
|
252
|
+
@name = :timestamp_high_res
|
253
|
+
# put the time in UTC, since this loses time zone info
|
254
|
+
@time = DateTime.now
|
255
|
+
@format = '%Y-%m-%d %H:%M:%S.%N%z'
|
256
|
+
@time_string = @time.strftime(@format)
|
257
|
+
@offsets = [0, 1, 2, 3, -1, -2, -3]
|
258
|
+
end
|
259
|
+
|
260
|
+
should 'pass along DateTimes when given DateTime to decode' do
|
261
|
+
@offsets.each do |offset|
|
262
|
+
assert_equal @time.new_offset(offset), @type.decode(@time.new_offset(offset))
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
should 'decode conformant strings into DateTimes' do
|
267
|
+
@offsets.each do |offset|
|
268
|
+
assert_equal @time.new_offset(offset).strftime(@format),
|
269
|
+
@type.decode(@time.new_offset(offset).strftime(@format)).strftime(@format)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# addresses bug in decoding from strings when fractional portion is smaller than .1 sec.
|
274
|
+
should 'decode conformat strings with small fractional portions into DateTimes' do
|
275
|
+
# get timestamp with no fractional seconds
|
276
|
+
time = DateTime.parse(DateTime.now.to_s)
|
277
|
+
# cycle through fractions: 0.111111111, 0.011111111, 0.001111111, etc.
|
278
|
+
checks = (9..18).collect do |factor|
|
279
|
+
sec_fraction = Rational(111111111, 10 ** factor)
|
280
|
+
fractional_time = time + (sec_fraction * Rational(1, 24 * 60 * 60))
|
281
|
+
fractional_time.strftime(@format)
|
282
|
+
end
|
283
|
+
results = checks.collect {|input| @type.decode(input).strftime(@format)}
|
284
|
+
assert_equal checks, results
|
285
|
+
end
|
286
|
+
|
287
|
+
should 'encode nil as nil' do
|
288
|
+
assert_equal true, @type.encode(nil).nil?
|
289
|
+
end
|
290
|
+
|
291
|
+
should 'decode nil as nil' do
|
292
|
+
assert_equal true, @type.decode(nil).nil?
|
293
|
+
end
|
294
|
+
|
295
|
+
should 'throw type conversion exceptions if strings do not conform' do
|
296
|
+
['', 'abc', '7145.abc.234'].each do |string|
|
297
|
+
assert_raise(SimpleMapper::TypeConversionException) { @type.decode(string) }
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
should 'encode DateTime values into conformant strings' do
|
302
|
+
@offsets.each do |offset|
|
303
|
+
assert_equal @time.new_offset(offset).strftime(@format),
|
304
|
+
@type.encode(@time.new_offset(offset))
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
should 'be registered as the :timestamp_hi_res type' do
|
309
|
+
assert_equal({:expected_type => DateTime,
|
310
|
+
:name => @name,
|
311
|
+
:converter => @type}, SimpleMapper::Attributes.type_for(@name))
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|