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