perpetuity-mongodb 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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +36 -0
- data/Rakefile +1 -0
- data/lib/perpetuity/mongodb.rb +231 -0
- data/lib/perpetuity/mongodb/index.rb +52 -0
- data/lib/perpetuity/mongodb/nil_query.rb +11 -0
- data/lib/perpetuity/mongodb/query.rb +33 -0
- data/lib/perpetuity/mongodb/query_attribute.rb +66 -0
- data/lib/perpetuity/mongodb/query_expression.rb +94 -0
- data/lib/perpetuity/mongodb/query_intersection.rb +16 -0
- data/lib/perpetuity/mongodb/query_union.rb +16 -0
- data/lib/perpetuity/mongodb/serializer.rb +174 -0
- data/lib/perpetuity/mongodb/version.rb +5 -0
- data/perpetuity-mongodb.gemspec +26 -0
- data/spec/perpetuity/mongodb/index_spec.rb +44 -0
- data/spec/perpetuity/mongodb/query_attribute_spec.rb +58 -0
- data/spec/perpetuity/mongodb/query_expression_spec.rb +67 -0
- data/spec/perpetuity/mongodb/query_intersection_spec.rb +16 -0
- data/spec/perpetuity/mongodb/query_spec.rb +79 -0
- data/spec/perpetuity/mongodb/query_union_spec.rb +16 -0
- data/spec/perpetuity/mongodb/serializer_spec.rb +212 -0
- data/spec/perpetuity/mongodb_spec.rb +218 -0
- data/spec/support/test_classes/book.rb +8 -0
- data/spec/support/test_classes/car.rb +5 -0
- data/spec/support/test_classes/user.rb +7 -0
- metadata +152 -0
@@ -0,0 +1,16 @@
|
|
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
|
@@ -0,0 +1,79 @@
|
|
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
|
@@ -0,0 +1,16 @@
|
|
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
|
@@ -0,0 +1,212 @@
|
|
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
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'perpetuity/mongodb'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module Perpetuity
|
5
|
+
describe MongoDB do
|
6
|
+
let(:mongo) { MongoDB.new db: 'perpetuity_gem_test' }
|
7
|
+
let(:klass) { String }
|
8
|
+
subject { mongo }
|
9
|
+
|
10
|
+
it 'is not connected when instantiated' do
|
11
|
+
mongo.should_not be_connected
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'connects to its host' do
|
15
|
+
mongo.connect
|
16
|
+
mongo.should be_connected
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'connects automatically when accessing the database' do
|
20
|
+
mongo.database
|
21
|
+
mongo.should be_connected
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'initialization params' do
|
25
|
+
let(:host) { double('host') }
|
26
|
+
let(:port) { double('port') }
|
27
|
+
let(:db) { double('db') }
|
28
|
+
let(:pool_size) { double('pool size') }
|
29
|
+
let(:username) { double('username') }
|
30
|
+
let(:password) { double('password') }
|
31
|
+
let(:mongo) do
|
32
|
+
MongoDB.new(
|
33
|
+
host: host,
|
34
|
+
port: port,
|
35
|
+
db: db,
|
36
|
+
pool_size: pool_size,
|
37
|
+
username: username,
|
38
|
+
password: password
|
39
|
+
)
|
40
|
+
end
|
41
|
+
subject { mongo }
|
42
|
+
|
43
|
+
its(:host) { should == host }
|
44
|
+
its(:port) { should == port }
|
45
|
+
its(:db) { should == db }
|
46
|
+
its(:pool_size) { should == pool_size }
|
47
|
+
its(:username) { should == username }
|
48
|
+
its(:password) { should == password }
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'inserts documents into a collection' do
|
52
|
+
expect { mongo.insert klass, { name: 'foo' }, [] }.to change { mongo.count klass }.by 1
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'inserts multiple documents into a collection' do
|
56
|
+
expect { mongo.insert klass, [{name: 'foo'}, {name: 'bar'}], [] }
|
57
|
+
.to change { mongo.count klass }.by 2
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'removes all documents from a collection' do
|
61
|
+
mongo.insert klass, {}, []
|
62
|
+
mongo.delete_all klass
|
63
|
+
mongo.count(klass).should == 0
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'counts the documents in a collection' do
|
67
|
+
mongo.delete_all klass
|
68
|
+
3.times do
|
69
|
+
mongo.insert klass, {}, []
|
70
|
+
end
|
71
|
+
mongo.count(klass).should == 3
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'counts the documents matching a query' do
|
75
|
+
mongo.delete_all klass
|
76
|
+
1.times { mongo.insert klass, { name: 'bar' }, [] }
|
77
|
+
3.times { mongo.insert klass, { name: 'foo' }, [] }
|
78
|
+
mongo.count(klass) { |o| o.name == 'foo' }.should == 3
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'gets the first document in a collection' do
|
82
|
+
value = {value: 1}
|
83
|
+
mongo.insert klass, value, []
|
84
|
+
mongo.first(klass)[:hypothetical_value].should == value['value']
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'gets all of the documents in a collection' do
|
88
|
+
values = [{value: 1}, {value: 2}]
|
89
|
+
mongo.should_receive(:retrieve).with(Object, mongo.nil_query, {})
|
90
|
+
.and_return(values)
|
91
|
+
mongo.all(Object).should == values
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'retrieves by id if the id is a string' do
|
95
|
+
time = Time.now.utc
|
96
|
+
id = mongo.insert Object, {inserted: time}, []
|
97
|
+
|
98
|
+
object = mongo.retrieve(Object, mongo.query{|o| o.id == id.to_s }).first
|
99
|
+
retrieved_time = object["inserted"]
|
100
|
+
retrieved_time.to_f.should be_within(0.001).of time.to_f
|
101
|
+
end
|
102
|
+
|
103
|
+
describe 'serialization' do
|
104
|
+
let(:object) { Object.new }
|
105
|
+
let(:foo_attribute) { double('Attribute', name: :foo) }
|
106
|
+
let(:baz_attribute) { double('Attribute', name: :baz) }
|
107
|
+
let(:mapper) { double('Mapper',
|
108
|
+
mapped_class: Object,
|
109
|
+
mapper_registry: {},
|
110
|
+
attribute_set: Set[foo_attribute, baz_attribute],
|
111
|
+
data_source: mongo,
|
112
|
+
) }
|
113
|
+
|
114
|
+
before do
|
115
|
+
object.instance_variable_set :@foo, 'bar'
|
116
|
+
object.instance_variable_set :@baz, 'quux'
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'serializes objects' do
|
120
|
+
mongo.serialize(object, mapper).should == {
|
121
|
+
'foo' => 'bar',
|
122
|
+
'baz' => 'quux'
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'can serialize only modified attributes of objects' do
|
127
|
+
updated = object.dup
|
128
|
+
updated.instance_variable_set :@foo, 'foo'
|
129
|
+
|
130
|
+
serialized = mongo.serialize_changed_attributes(updated, object, mapper)
|
131
|
+
serialized.should == { 'foo' => 'foo' }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe 'serializable objects' do
|
136
|
+
let(:serializable_values) { [nil, true, false, 1, 1.2, '', [], {}, Time.now] }
|
137
|
+
|
138
|
+
it 'can insert serializable values' do
|
139
|
+
serializable_values.each do |value|
|
140
|
+
mongo.insert(Object, {value: value}, []).should be_a Moped::BSON::ObjectId
|
141
|
+
mongo.can_serialize?(value).should be_true
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'generates a new query DSL object' do
|
147
|
+
mongo.query { |object| object.whatever == 1 }.should respond_to :to_db
|
148
|
+
end
|
149
|
+
|
150
|
+
describe 'indexing' do
|
151
|
+
let(:collection) { Object }
|
152
|
+
let(:key) { 'object_id' }
|
153
|
+
|
154
|
+
before { mongo.index collection, key }
|
155
|
+
after { mongo.drop_collection collection }
|
156
|
+
|
157
|
+
it 'adds indexes for the specified key on the specified collection' do
|
158
|
+
indexes = mongo.indexes(collection).select{ |index| index.attribute == 'object_id' }
|
159
|
+
indexes.should_not be_empty
|
160
|
+
indexes.first.order.should be :ascending
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'adds descending-order indexes' do
|
164
|
+
index = mongo.index collection, 'hash', order: :descending
|
165
|
+
index.order.should be :descending
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'creates indexes on the database collection' do
|
169
|
+
mongo.delete_all collection
|
170
|
+
index = mongo.index collection, 'real_index', order: :descending, unique: true
|
171
|
+
mongo.activate_index! index
|
172
|
+
|
173
|
+
mongo.active_indexes(collection).should include index
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'removes indexes' do
|
177
|
+
mongo.drop_collection collection
|
178
|
+
index = mongo.index collection, 'real_index', order: :descending, unique: true
|
179
|
+
mongo.activate_index! index
|
180
|
+
mongo.remove_index index
|
181
|
+
mongo.active_indexes(collection).should_not include index
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe 'atomic operations' do
|
186
|
+
after(:all) { mongo.delete_all klass }
|
187
|
+
|
188
|
+
it 'increments the value of an attribute' do
|
189
|
+
id = mongo.insert klass, { count: 1 }, []
|
190
|
+
mongo.increment klass, id, :count
|
191
|
+
mongo.increment klass, id, :count, 10
|
192
|
+
query = mongo.query { |o| o.id == id }
|
193
|
+
mongo.retrieve(klass, query).first['count'].should be == 12
|
194
|
+
mongo.increment klass, id, :count, -1
|
195
|
+
mongo.retrieve(klass, query).first['count'].should be == 11
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
describe 'operation errors' do
|
200
|
+
let(:data) { { foo: 'bar' } }
|
201
|
+
let(:index) { mongo.index Object, :foo, unique: true }
|
202
|
+
|
203
|
+
before do
|
204
|
+
mongo.delete_all Object
|
205
|
+
mongo.activate_index! index
|
206
|
+
end
|
207
|
+
|
208
|
+
after { mongo.drop_collection Object }
|
209
|
+
|
210
|
+
it 'raises an exception when insertion fails' do
|
211
|
+
mongo.insert Object, data, []
|
212
|
+
|
213
|
+
expect { mongo.insert Object, data, [] }.to raise_error DuplicateKeyError,
|
214
|
+
'Tried to insert Object with duplicate unique index: foo => "bar"'
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|