perpetuity 0.7.3 → 1.0.0.beta

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/Gemfile +3 -0
  4. data/README.md +18 -8
  5. data/lib/perpetuity.rb +4 -4
  6. data/lib/perpetuity/attribute.rb +9 -0
  7. data/lib/perpetuity/attribute_set.rb +4 -6
  8. data/lib/perpetuity/data_injectable.rb +1 -1
  9. data/lib/perpetuity/duplicator.rb +27 -0
  10. data/lib/perpetuity/mapper.rb +9 -14
  11. data/lib/perpetuity/version.rb +1 -1
  12. data/perpetuity.gemspec +0 -1
  13. data/spec/integration/associations_spec.rb +2 -6
  14. data/spec/integration/indexing_spec.rb +1 -1
  15. data/spec/integration/persistence_spec.rb +3 -2
  16. data/spec/integration/retrieval_spec.rb +5 -13
  17. data/spec/perpetuity/attribute_set_spec.rb +1 -1
  18. data/spec/perpetuity/attribute_spec.rb +6 -1
  19. data/spec/perpetuity/duplicator_spec.rb +35 -0
  20. data/spec/perpetuity/mapper_spec.rb +21 -2
  21. data/spec/perpetuity_spec.rb +2 -2
  22. data/spec/spec_helper.rb +8 -2
  23. data/spec/support/test_classes.rb +22 -26
  24. data/spec/support/test_classes/topic.rb +5 -0
  25. data/spec/support/test_classes/user.rb +1 -1
  26. metadata +7 -55
  27. data/lib/perpetuity/mongodb.rb +0 -230
  28. data/lib/perpetuity/mongodb/index.rb +0 -52
  29. data/lib/perpetuity/mongodb/nil_query.rb +0 -11
  30. data/lib/perpetuity/mongodb/query.rb +0 -33
  31. data/lib/perpetuity/mongodb/query_attribute.rb +0 -66
  32. data/lib/perpetuity/mongodb/query_expression.rb +0 -94
  33. data/lib/perpetuity/mongodb/query_intersection.rb +0 -16
  34. data/lib/perpetuity/mongodb/query_union.rb +0 -16
  35. data/lib/perpetuity/mongodb/serializer.rb +0 -174
  36. data/lib/perpetuity/validations.rb +0 -1
  37. data/lib/perpetuity/validations/length.rb +0 -36
  38. data/lib/perpetuity/validations/presence.rb +0 -14
  39. data/lib/perpetuity/validations/validation_set.rb +0 -28
  40. data/spec/integration/mongodb_spec.rb +0 -218
  41. data/spec/integration/validations_spec.rb +0 -17
  42. data/spec/perpetuity/mongodb/index_spec.rb +0 -44
  43. data/spec/perpetuity/mongodb/query_attribute_spec.rb +0 -58
  44. data/spec/perpetuity/mongodb/query_expression_spec.rb +0 -67
  45. data/spec/perpetuity/mongodb/query_intersection_spec.rb +0 -16
  46. data/spec/perpetuity/mongodb/query_spec.rb +0 -79
  47. data/spec/perpetuity/mongodb/query_union_spec.rb +0 -16
  48. data/spec/perpetuity/mongodb/serializer_spec.rb +0 -212
  49. data/spec/perpetuity/validations/length_spec.rb +0 -53
  50. data/spec/perpetuity/validations/presence_spec.rb +0 -30
  51. data/spec/perpetuity/validations_spec.rb +0 -87
@@ -1,16 +0,0 @@
1
- require 'perpetuity/mongodb/query_intersection'
2
- require 'perpetuity/mongodb/query_expression'
3
-
4
- module Perpetuity
5
- class MongoDB
6
- describe QueryIntersection do
7
- let(:lhs) { QueryExpression.new :first, :equals, 'one' }
8
- let(:rhs) { QueryExpression.new :second, :equals, 'two' }
9
- let(:intersection) { QueryIntersection.new lhs, rhs }
10
-
11
- it 'returns a Mongo representation of the union of 2 expressions' do
12
- intersection.to_db.should be == { '$and' => [{first: 'one'}, {second: 'two'}] }
13
- end
14
- end
15
- end
16
- end
@@ -1,79 +0,0 @@
1
- require 'perpetuity/mongodb/query'
2
-
3
- module Perpetuity
4
- describe MongoDB::Query do
5
- let(:query) { MongoDB::Query }
6
-
7
- it 'generates Mongo equality expressions' do
8
- query.new{ |user| user.name == 'Jamie' }.to_db.should == {name: 'Jamie'}
9
- end
10
-
11
- it 'generates Mongo less-than expressions' do
12
- query.new{ |v| v.quantity < 10 }.to_db.should == {quantity: { '$lt' => 10}}
13
- end
14
-
15
- it 'generates Mongo less-than-or-equal expressions' do
16
- query.new{ |v| v.quantity <= 10 }.to_db.should == {quantity: { '$lte' => 10}}
17
- end
18
-
19
- it 'generates Mongo greater-than expressions' do
20
- query.new{ |v| v.quantity > 10 }.to_db.should == {quantity: { '$gt' => 10}}
21
- end
22
-
23
- it 'generates Mongo greater-than-or-equal expressions' do
24
- query.new{ |v| v.quantity >= 10 }.to_db.should == {quantity: { '$gte' => 10}}
25
- end
26
-
27
- it 'generates Mongo inequality expressions' do
28
- query.new{ |user| user.name.not_equal? 'Jamie' }.to_db.should == {
29
- name: {'$ne' => 'Jamie'}
30
- }
31
- end
32
-
33
- it 'generates Mongo regexp expressions' do
34
- query.new{ |user| user.name =~ /Jamie/ }.to_db.should == {name: /Jamie/}
35
- end
36
-
37
- describe 'negated queries' do
38
- it 'negates an equality query' do
39
- q = query.new { |user| user.name == 'Jamie' }
40
- q.negate.to_db.should == { name: { '$ne' => 'Jamie' } }
41
- end
42
-
43
- it 'negates a not-equal query' do
44
- q = query.new { |account| account.balance != 10 }
45
- q.negate.to_db.should == { balance: { '$not' => { '$ne' => 10 } } }
46
- end
47
-
48
- it 'negates a less-than query' do
49
- q = query.new { |account| account.balance < 10 }
50
- q.negate.to_db.should == { balance: { '$not' => { '$lt' => 10 } } }
51
- end
52
-
53
- it 'negates a less-than-or-equal query' do
54
- q = query.new { |account| account.balance <= 10 }
55
- q.negate.to_db.should == { balance: { '$not' => { '$lte' => 10 } } }
56
- end
57
-
58
- it 'negates a greater-than query' do
59
- q = query.new { |account| account.balance > 10 }
60
- q.negate.to_db.should == { balance: { '$not' => { '$gt' => 10 } } }
61
- end
62
-
63
- it 'negates a greater-than-or-equal query' do
64
- q = query.new { |account| account.balance >= 10 }
65
- q.negate.to_db.should == { balance: { '$not' => { '$gte' => 10 } } }
66
- end
67
-
68
- it 'negates a regex query' do
69
- q = query.new { |account| account.name =~ /Jamie/ }
70
- q.negate.to_db.should == { name: { '$not' => /Jamie/ } }
71
- end
72
-
73
- it 'negates a inclusion query' do
74
- q = query.new { |article| article.tags.in ['tag1', 'tag2'] }
75
- q.negate.to_db.should == { tags: { '$not' => { '$in' => ['tag1', 'tag2'] } } }
76
- end
77
- end
78
- end
79
- end
@@ -1,16 +0,0 @@
1
- require 'perpetuity/mongodb/query_union'
2
- require 'perpetuity/mongodb/query_expression'
3
-
4
- module Perpetuity
5
- class MongoDB
6
- describe QueryUnion do
7
- let(:lhs) { QueryExpression.new :first, :equals, 'one' }
8
- let(:rhs) { QueryExpression.new :second, :equals, 'two' }
9
- let(:union) { QueryUnion.new lhs, rhs }
10
-
11
- it 'returns the proper union of two expressions' do
12
- union.to_db.should be == { '$or' => [{first: 'one'}, {second: 'two'}] }
13
- end
14
- end
15
- end
16
- end
@@ -1,212 +0,0 @@
1
- require 'perpetuity/mongodb/serializer'
2
- require 'perpetuity/mapper'
3
- require 'perpetuity/mapper_registry'
4
- require 'support/test_classes/book'
5
- require 'support/test_classes/user'
6
- require 'support/test_classes/car'
7
-
8
- module Perpetuity
9
- class MongoDB
10
- describe Serializer do
11
- let(:dave) { User.new('Dave') }
12
- let(:andy) { User.new('Andy') }
13
- let(:authors) { [dave, andy] }
14
- let(:book) { Book.new('The Pragmatic Programmer', authors) }
15
- let(:mapper_registry) { MapperRegistry.new }
16
- let(:book_mapper) do
17
- registry = mapper_registry
18
- Class.new(Perpetuity::Mapper) do
19
- map Book, registry
20
- attribute :title
21
- attribute :authors
22
- end.new(registry)
23
- end
24
- let(:user_mapper) do
25
- registry = mapper_registry
26
- Class.new(Perpetuity::Mapper) do
27
- map User, registry
28
- attribute :name
29
- end.new(registry)
30
- end
31
- let(:data_source) { double('Data Source') }
32
- let(:serializer) { Serializer.new(book_mapper) }
33
-
34
- before do
35
- serializer.give_id_to dave, 1
36
- serializer.give_id_to andy, 2
37
- end
38
-
39
- it 'serializes an array of non-embedded attributes as references' do
40
- user_mapper.stub(data_source: data_source)
41
- book_mapper.stub(data_source: data_source)
42
- data_source.should_receive(:can_serialize?).with(book.title).and_return true
43
- data_source.should_receive(:can_serialize?).with(dave).and_return false
44
- data_source.should_receive(:can_serialize?).with(andy).and_return false
45
- serializer.serialize(book).should be == {
46
- 'title' => book.title,
47
- 'authors' => [
48
- {
49
- '__metadata__' => {
50
- 'class' => 'User',
51
- 'id' => user_mapper.id_for(dave)
52
- }
53
- },
54
- {
55
- '__metadata__' => {
56
- 'class' => 'User',
57
- 'id' => user_mapper.id_for(andy)
58
- }
59
- }
60
- ]
61
- }
62
- end
63
-
64
- it 'can serialize only changed attributes' do
65
- book = Book.new('Original Title')
66
- updated_book = book.dup
67
- updated_book.title = 'New Title'
68
- book_mapper.stub(data_source: data_source)
69
- data_source.stub(:can_serialize?).with('New Title') { true }
70
- data_source.stub(:can_serialize?).with('Original Title') { true }
71
- serializer.serialize_changes(updated_book, book).should == {
72
- 'title' => 'New Title'
73
- }
74
- end
75
-
76
- context 'with objects that have hashes as attributes' do
77
- let(:name_data) { {first_name: 'Jamie', last_name: 'Gaskins'} }
78
- let(:serialized_data) { { 'name' => name_data } }
79
- let(:user) { User.new(name_data) }
80
- let(:user_serializer) { Serializer.new(user_mapper) }
81
-
82
- before do
83
- user_mapper.stub(data_source: data_source)
84
- book_mapper.stub(data_source: data_source)
85
- data_source.stub(:can_serialize?).with(name_data) { true }
86
- end
87
-
88
- it 'serializes' do
89
- user_serializer.serialize(user).should be == serialized_data
90
- end
91
-
92
- it 'unserializes' do
93
- user_serializer.unserialize(serialized_data).name.should be == user.name
94
- end
95
- end
96
-
97
- describe 'with an array of references' do
98
- let(:author) { Reference.new(User, 1) }
99
- let(:title) { 'title' }
100
- let(:book) { Book.new(title, [author]) }
101
-
102
- before do
103
- user_mapper.stub(data_source: data_source)
104
- book_mapper.stub(data_source: data_source)
105
- end
106
-
107
- it 'passes the reference unserialized' do
108
- data_source.should_receive(:can_serialize?).with('title') { true }
109
- serializer.serialize(book).should == {
110
- 'title' => title,
111
- 'authors' => [{
112
- '__metadata__' => {
113
- 'class' => author.klass.to_s,
114
- 'id' => author.id
115
- }
116
- }]
117
- }
118
- end
119
- end
120
-
121
- context 'with uninitialized attributes' do
122
- let(:car_model) { 'Corvette' }
123
- let(:car) { Car.new(model: car_model) }
124
- let(:mapper) do
125
- registry = mapper_registry
126
- Class.new(Mapper) do
127
- map Car, registry
128
-
129
- attribute :make
130
- attribute :model
131
- end.new(registry)
132
- end
133
- let(:serializer) { Serializer.new(mapper) }
134
-
135
-
136
- it 'does not persist uninitialized attributes' do
137
- mapper.stub data_source: data_source
138
- data_source.should_receive(:can_serialize?).with(car_model) { true }
139
-
140
- serializer.serialize(car).should == { 'model' => car_model }
141
- end
142
- end
143
-
144
- context 'with marshaled data' do
145
- let(:unserializable_value) { 1..10 }
146
-
147
- it 'stores metadata with marshal information' do
148
- book = Book.new(unserializable_value)
149
-
150
- book_mapper.stub(data_source: data_source)
151
- data_source.stub(:can_serialize?).with(book.title) { false }
152
-
153
- serializer.serialize(book).should == {
154
- 'title' => {
155
- '__marshaled__' => true,
156
- 'value' => Marshal.dump(unserializable_value)
157
- },
158
- 'authors' => []
159
- }
160
- end
161
-
162
- it 'stores marshaled attributes within arrays' do
163
- book = Book.new([unserializable_value])
164
- book_mapper.stub(data_source: data_source)
165
- data_source.stub(:can_serialize?).with(book.title.first) { false }
166
-
167
- serializer.serialize(book).should == {
168
- 'title' => [{
169
- '__marshaled__' => true,
170
- 'value' => Marshal.dump(unserializable_value)
171
- }],
172
- 'authors' => []
173
- }
174
- end
175
-
176
- it 'unmarshals data that has been marshaled by the serializer' do
177
- data = {
178
- 'title' => {
179
- '__marshaled__' => true,
180
- 'value' => Marshal.dump(unserializable_value),
181
- }
182
- }
183
- serializer.unserialize(data).title.should be_a unserializable_value.class
184
- end
185
-
186
- it 'does not unmarshal data not marshaled by the serializer' do
187
- data = { 'title' => Marshal.dump(unserializable_value) }
188
-
189
- serializer.unserialize(data).title.should be_a String
190
- end
191
- end
192
-
193
- it 'unserializes a hash of primitives' do
194
- time = Time.now
195
- serialized_data = {
196
- 'number' => 1,
197
- 'string' => 'hello',
198
- 'boolean' => true,
199
- 'float' => 7.5,
200
- 'time' => time
201
- }
202
-
203
- object = serializer.unserialize(serialized_data)
204
- object.instance_variable_get(:@number).should == 1
205
- object.instance_variable_get(:@string).should == 'hello'
206
- object.instance_variable_get(:@boolean).should == true
207
- object.instance_variable_get(:@float).should == 7.5
208
- object.instance_variable_get(:@time).should == time
209
- end
210
- end
211
- end
212
- end
@@ -1,53 +0,0 @@
1
- require 'perpetuity/validations/length'
2
-
3
- module Perpetuity
4
- module Validations
5
- describe Length do
6
- let(:length) { Length.new :to_s, {}}
7
-
8
- describe 'minimum length' do
9
- before { length.at_least 4 }
10
-
11
- it 'invalidates' do
12
- length.pass?('abc').should be false
13
- end
14
-
15
- it 'validates' do
16
- length.pass?('abcd').should be true
17
- end
18
- end
19
-
20
- describe 'maximum length' do
21
- before { length.at_most 4 }
22
-
23
- it 'validates' do
24
- length.pass?('abcd').should be true
25
- end
26
-
27
- it 'invalidates' do
28
- length.pass?('abcde').should be false
29
- end
30
- end
31
-
32
- describe 'ranges' do
33
- before { length.between 4..5 }
34
-
35
- it 'invalidates values too short' do
36
- length.pass?('abc').should be false
37
- end
38
-
39
- it 'validates lengths at the low end' do
40
- length.pass?('abcd').should be true
41
- end
42
-
43
- it 'validates lengths at the high end' do
44
- length.pass?('abcde').should be true
45
- end
46
-
47
- it 'invalidates values too long' do
48
- length.pass?('abcdef').should be false
49
- end
50
- end
51
- end
52
- end
53
- end
@@ -1,30 +0,0 @@
1
- require 'perpetuity/validations/presence'
2
-
3
- module Perpetuity
4
- module Validations
5
- describe Presence do
6
- let(:presence) { Presence.new :to_s }
7
- let(:valid) { double('valid object') }
8
- let(:invalid) { double('invalid object') }
9
-
10
- it 'validates values that are present' do
11
- presence.pass?(valid).should be_true
12
- end
13
-
14
- it 'invalidates nil values' do
15
- invalid.stub(to_s: nil)
16
- presence.pass?(invalid).should be_false
17
- end
18
-
19
- it 'invalidates empty strings' do
20
- invalid.stub(to_s: '')
21
- presence.pass?(invalid).should be_false
22
- end
23
-
24
- it 'invalidates strings with only whitespace' do
25
- invalid.stub(to_s: ' ')
26
- presence.pass?(invalid).should be_false
27
- end
28
- end
29
- end
30
- end
@@ -1,87 +0,0 @@
1
- require 'perpetuity/validations'
2
-
3
- module Perpetuity
4
- describe ValidationSet do
5
- let(:validation_set) { ValidationSet.new }
6
- it 'is empty when created' do
7
- validation_set.should be_empty
8
- end
9
-
10
- it 'can add validations' do
11
- v = double('validation')
12
- validation_set << v
13
-
14
- validation_set.first.should == v
15
- end
16
-
17
- it 'validates an object' do
18
- v = double('validation')
19
- v.stub(pass?: true)
20
- validation_set << v
21
-
22
- validation_set.should be_valid(double('object'))
23
- end
24
-
25
- it 'invalidates an object' do
26
- v = double('validation')
27
- v.stub(pass?: false)
28
- validation_set << v
29
-
30
- validation_set.should_not be_valid(double('object'))
31
- end
32
-
33
- describe 'validation types' do
34
- let(:valid_object) { double('valid object') }
35
- let(:invalid_object) { double('invalid object') }
36
-
37
- it 'validates presence of an attribute' do
38
- valid_object.stub(email: 'me@example.com')
39
- validation_set.present :email
40
-
41
- validation_set.count.should eq 1
42
- validation_set.should be_valid(valid_object)
43
- end
44
-
45
- it 'validates length of an attribute' do
46
- valid_object.stub(email: 'me@example.com')
47
- validation_set.length :email, at_most: 14
48
- validation_set.length :email, at_least: 14
49
-
50
- validation_set.count.should eq 2
51
- validation_set.should be_valid(valid_object)
52
- end
53
-
54
- it 'validates length within a range' do
55
- valid_object.stub(email: 'me@example.com')
56
- validation_set.length :email, between: (14..14)
57
-
58
- validation_set.should have(1).items
59
- validation_set.should be_valid(valid_object)
60
- end
61
-
62
- it 'invalidates length within a range' do
63
- invalid_object.stub(email: 'me@example.com')
64
- validation_set.length :email, between: (0..13)
65
-
66
- validation_set.should have(1).items
67
- validation_set.should be_invalid(invalid_object)
68
- end
69
-
70
- it 'invalidates when attribute is too short' do
71
- invalid_object.stub(email: 'foo')
72
- validation_set.length :email, at_least: 4
73
-
74
- validation_set.count.should eq 1
75
- validation_set.should be_invalid(invalid_object)
76
- end
77
-
78
- it 'invalidates when attribute is too long' do
79
- invalid_object.stub(email: 'me@example.com')
80
- subject.length :email, at_most: 4
81
-
82
- subject.count.should eq 1
83
- subject.should be_invalid(invalid_object)
84
- end
85
- end
86
- end
87
- end