dm-mongo-adapter 0.2.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/LICENSE +20 -0
- data/README.rdoc +130 -0
- data/Rakefile +36 -0
- data/TODO +33 -0
- data/VERSION.yml +5 -0
- data/bin/console +31 -0
- data/dm-mongo-adapter.gemspec +154 -0
- data/lib/mongo_adapter.rb +71 -0
- data/lib/mongo_adapter/adapter.rb +255 -0
- data/lib/mongo_adapter/aggregates.rb +21 -0
- data/lib/mongo_adapter/conditions.rb +100 -0
- data/lib/mongo_adapter/embedded_model.rb +187 -0
- data/lib/mongo_adapter/embedded_resource.rb +134 -0
- data/lib/mongo_adapter/embedments/one_to_many.rb +139 -0
- data/lib/mongo_adapter/embedments/one_to_one.rb +53 -0
- data/lib/mongo_adapter/embedments/relationship.rb +258 -0
- data/lib/mongo_adapter/migrations.rb +45 -0
- data/lib/mongo_adapter/model.rb +89 -0
- data/lib/mongo_adapter/model/embedment.rb +215 -0
- data/lib/mongo_adapter/modifier.rb +85 -0
- data/lib/mongo_adapter/query.rb +252 -0
- data/lib/mongo_adapter/query/java_script.rb +145 -0
- data/lib/mongo_adapter/resource.rb +147 -0
- data/lib/mongo_adapter/types/date.rb +28 -0
- data/lib/mongo_adapter/types/date_time.rb +28 -0
- data/lib/mongo_adapter/types/db_ref.rb +65 -0
- data/lib/mongo_adapter/types/discriminator.rb +32 -0
- data/lib/mongo_adapter/types/object_id.rb +72 -0
- data/lib/mongo_adapter/types/objects.rb +31 -0
- data/script/performance.rb +260 -0
- data/spec/legacy/README +6 -0
- data/spec/legacy/adapter_shared_spec.rb +299 -0
- data/spec/legacy/adapter_spec.rb +174 -0
- data/spec/legacy/associations_spec.rb +140 -0
- data/spec/legacy/embedded_resource_spec.rb +157 -0
- data/spec/legacy/embedments_spec.rb +177 -0
- data/spec/legacy/modifier_spec.rb +81 -0
- data/spec/legacy/property_spec.rb +51 -0
- data/spec/legacy/spec_helper.rb +3 -0
- data/spec/legacy/sti_spec.rb +53 -0
- data/spec/lib/cleanup_models.rb +32 -0
- data/spec/lib/raw_connections.rb +11 -0
- data/spec/public/embedded_collection_spec.rb +61 -0
- data/spec/public/embedded_resource_spec.rb +220 -0
- data/spec/public/model/embedment_spec.rb +186 -0
- data/spec/public/model_spec.rb +37 -0
- data/spec/public/resource_spec.rb +564 -0
- data/spec/public/shared/model_embedments_spec.rb +338 -0
- data/spec/public/shared/object_id_shared_spec.rb +56 -0
- data/spec/public/types/df_ref_spec.rb +6 -0
- data/spec/public/types/discriminator_spec.rb +118 -0
- data/spec/public/types/embedded_array_spec.rb +55 -0
- data/spec/public/types/embedded_hash_spec.rb +83 -0
- data/spec/public/types/object_id_spec.rb +6 -0
- data/spec/rcov.opts +6 -0
- data/spec/semipublic/embedded_model_spec.rb +43 -0
- data/spec/semipublic/model/embedment_spec.rb +42 -0
- data/spec/semipublic/resource_spec.rb +70 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +45 -0
- data/tasks/spec.rake +37 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +21 -0
- metadata +215 -0
@@ -0,0 +1,220 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataMapper::Mongo::EmbeddedResource do
|
4
|
+
before(:all) do
|
5
|
+
class ::Address
|
6
|
+
include DataMapper::Mongo::EmbeddedResource
|
7
|
+
property :street, String
|
8
|
+
property :city, String, :field => 'conurbation'
|
9
|
+
end
|
10
|
+
|
11
|
+
class ::AddressWithDefault
|
12
|
+
include DataMapper::Mongo::EmbeddedResource
|
13
|
+
property :city, String, :default => 'Rock Ridge'
|
14
|
+
end
|
15
|
+
|
16
|
+
class ::User
|
17
|
+
include DataMapper::Mongo::Resource
|
18
|
+
property :id, ObjectID
|
19
|
+
embeds 1, :address, :model => Address
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# attributes
|
25
|
+
#
|
26
|
+
|
27
|
+
describe 'attributes' do
|
28
|
+
before(:all) do
|
29
|
+
@address = Address.new(:street => 'Main Street', :city => 'Rock Ridge')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should return a Hash' do
|
33
|
+
@address.attributes.should be_kind_of(Hash)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should use the property names when key_on=:name' do
|
37
|
+
@address.attributes(:name).should ==
|
38
|
+
{ :street => 'Main Street', :city => 'Rock Ridge' }
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should use the field names when key_on=:field' do
|
42
|
+
@address.attributes(:field).should ==
|
43
|
+
{ 'street' => 'Main Street', 'conurbation' => 'Rock Ridge' }
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should use the property instances when key_on=:property' do
|
47
|
+
@address.attributes(:property).should == {
|
48
|
+
Address.properties[:street] => 'Main Street',
|
49
|
+
Address.properties[:city] => 'Rock Ridge'
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# dirty?
|
56
|
+
#
|
57
|
+
|
58
|
+
describe '#dirty?' do
|
59
|
+
describe 'on a new embedded resource' do
|
60
|
+
it 'should return false' do
|
61
|
+
Address.new.should_not be_dirty
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should return true if an attribute has been changed' do
|
65
|
+
Address.new(:city => 'Rock Ridge').should be_dirty
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should return false if a changed attribute has been saved' do
|
69
|
+
address = Address.new(:city => 'Rock Ridge')
|
70
|
+
|
71
|
+
lambda { User.create(:address => address) }.should \
|
72
|
+
change(address, :dirty?).to(false)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'on a persisted embedded resource' do
|
77
|
+
before(:each) do
|
78
|
+
user = User.create(:address => Address.new(:city => 'Rock Ridge'))
|
79
|
+
@address = user.address
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should return false when no changes have been made' do
|
83
|
+
@address.should_not be_dirty
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should return true if an attribute has been changed' do
|
87
|
+
@address.street = 'Main Street'
|
88
|
+
@address.should be_dirty
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should return false if a changed attribute has been saved' do
|
92
|
+
@address.street = 'Main Street'
|
93
|
+
|
94
|
+
lambda { @address.parent.save }.should \
|
95
|
+
change(@address, :dirty?).to(false)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# dirty_self?
|
102
|
+
#
|
103
|
+
|
104
|
+
describe '#dirty_self?' do
|
105
|
+
describe 'on a new embedded resource' do
|
106
|
+
it 'should return false if no changes have been made and no ' \
|
107
|
+
'properties have a default' do
|
108
|
+
Address.new.should_not be_dirty_self
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should return true if no changes have been made, but a property ' \
|
112
|
+
'has a default' do
|
113
|
+
AddressWithDefault.new.should be_dirty_self
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should return true if a change has been made' do
|
117
|
+
Address.new(:city => 'Rock Ridge').should be_dirty_self
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe 'on a persisited embedded resource' do
|
122
|
+
before(:each) do
|
123
|
+
user = User.create(:address => Address.new(:city => 'Rock Ridge'))
|
124
|
+
@address = user.address
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should return false if no changes have been made' do
|
128
|
+
@address.should_not be_dirty_self
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should return true if a change has been made' do
|
132
|
+
@address.street = 'Main Street'
|
133
|
+
@address.should be_dirty_self
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# parent?
|
140
|
+
#
|
141
|
+
|
142
|
+
describe '#parent?' do
|
143
|
+
it 'should return true if the embedded resource has a parent' do
|
144
|
+
address = Address.new
|
145
|
+
address.parent = User.new
|
146
|
+
address.parent?.should be_true
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'should return false if the embedded resource has no parent' do
|
150
|
+
Address.new.parent?.should be_false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# new?
|
156
|
+
#
|
157
|
+
|
158
|
+
describe '#new?' do
|
159
|
+
it 'should return false if it has been persisted' do
|
160
|
+
user = User.create(:address => Address.new(:city => 'Rock Ridge'))
|
161
|
+
user.address.should_not be_new
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should return true if its parent has not been persisted' do
|
165
|
+
user = User.new(:address => Address.new(:city => 'Rock Ridge'))
|
166
|
+
user.address.should be_new
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should return true if it has no parent' do
|
170
|
+
Address.new.should be_new
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
#
|
175
|
+
# saved?
|
176
|
+
#
|
177
|
+
|
178
|
+
describe '#saved?' do
|
179
|
+
it 'should return true if it has been persisted' do
|
180
|
+
user = User.create(:address => Address.new(:city => 'Rock Ridge'))
|
181
|
+
user.address.should be_saved
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'should return false if its parent has not been persisted' do
|
185
|
+
user = User.new(:address => Address.new(:city => 'Rock Ridge'))
|
186
|
+
user.address.should_not be_saved
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'should return false if it has no parent' do
|
190
|
+
Address.new.should_not be_saved
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
#
|
195
|
+
# save
|
196
|
+
#
|
197
|
+
|
198
|
+
describe '#save' do
|
199
|
+
it 'should raise MissingParentError if no parent is set' do
|
200
|
+
lambda { Address.new.save }.should raise_error(
|
201
|
+
DataMapper::Mongo::EmbeddedResource::MissingParentError)
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'should clear the original attributes if the parent saved' do
|
205
|
+
user = User.new(:address => Address.new(:city => 'Rock Ridge'))
|
206
|
+
|
207
|
+
expectation = lambda { user.address.original_attributes.empty? }
|
208
|
+
lambda { user.address.save }.should change(&expectation).to(true)
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'should not clear the original_attributes is the parent did not save' do
|
212
|
+
user = User.new(:address => Address.new(:city => 'Rock Ridge'))
|
213
|
+
user.stub!(:save).and_return(false)
|
214
|
+
|
215
|
+
expectation = lambda { user.address.original_attributes.empty? }
|
216
|
+
lambda { user.address.save }.should_not change(&expectation).to(true)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataMapper::Mongo::Embedments do
|
4
|
+
# Removes the Engine constant, runs the block, then redefines the Engine
|
5
|
+
# class.
|
6
|
+
def without_engine_defined
|
7
|
+
old_engine = ::Engine
|
8
|
+
Object.send(:remove_const, :Engine) if defined?(::Engine)
|
9
|
+
yield
|
10
|
+
Object.send(:const_set, :Engine, old_engine)
|
11
|
+
end
|
12
|
+
|
13
|
+
before(:all) do
|
14
|
+
class ::Car
|
15
|
+
include DataMapper::Mongo::Resource
|
16
|
+
property :id, ObjectID
|
17
|
+
property :name, String
|
18
|
+
end
|
19
|
+
|
20
|
+
class ::Engine
|
21
|
+
include DataMapper::Mongo::EmbeddedResource
|
22
|
+
property :name, String
|
23
|
+
end
|
24
|
+
|
25
|
+
class ::Door
|
26
|
+
include DataMapper::Mongo::EmbeddedResource
|
27
|
+
property :name, String
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def n
|
32
|
+
Car.n
|
33
|
+
end
|
34
|
+
|
35
|
+
it { Car.should respond_to(:embeds) }
|
36
|
+
it { Car.should respond_to(:embedments) }
|
37
|
+
|
38
|
+
describe '#embeds(1, ...)' do
|
39
|
+
before(:all) do
|
40
|
+
@model = Engine
|
41
|
+
@name = :engine
|
42
|
+
|
43
|
+
Car.embeds(1, :engine)
|
44
|
+
|
45
|
+
@car = Car.new(:name => 'Ford')
|
46
|
+
end
|
47
|
+
|
48
|
+
it_should_behave_like 'A singular embedment reader'
|
49
|
+
it_should_behave_like 'A singular embedment writer'
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#embeds(n, ...)' do
|
53
|
+
before(:all) do
|
54
|
+
@model = Door
|
55
|
+
@name = :doors
|
56
|
+
|
57
|
+
Car.embeds(n, :doors)
|
58
|
+
@car = Car.new(:name => 'Ford')
|
59
|
+
end
|
60
|
+
|
61
|
+
it_should_behave_like 'A many embedment reader'
|
62
|
+
it_should_behave_like 'A many embedment writer'
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#embeds(n, :through ...)' do
|
66
|
+
it 'should raise an ArgumentError' do
|
67
|
+
pending ':through option not supported on embedments'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#embeds(1..4)' do
|
72
|
+
before(:all) do
|
73
|
+
@model = Door
|
74
|
+
@name = :doors
|
75
|
+
|
76
|
+
Car.embeds(1..4, :doors)
|
77
|
+
@car = Car.new(:name => 'Ford')
|
78
|
+
end
|
79
|
+
|
80
|
+
it_should_behave_like 'A many embedment reader'
|
81
|
+
it_should_behave_like 'A many embedment writer'
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#embeds' do
|
85
|
+
describe 'when inferring the model from the name' do
|
86
|
+
it 'should set the relationship target model' do
|
87
|
+
Car.embeds(1, :engine)
|
88
|
+
Car.embedments[:engine].target_model.should == Engine
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should set the relationship target model when it is not defined' do
|
92
|
+
without_engine_defined do
|
93
|
+
Car.embeds(1, :engine)
|
94
|
+
end
|
95
|
+
|
96
|
+
Car.embedments[:engine].target_model.should == Engine
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe 'when the third argument is a model' do
|
101
|
+
it 'should set the relationship target model' do
|
102
|
+
Car.embeds(1, :engine, Engine)
|
103
|
+
Car.embedments[:engine].target_model.should == Engine
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe 'when the third argument is a string' do
|
108
|
+
it 'should set the relationship target model' do
|
109
|
+
without_engine_defined do
|
110
|
+
Car.embeds(1, :engine, 'Engine')
|
111
|
+
end
|
112
|
+
|
113
|
+
Car.embedments[:engine].target_model.should == Engine
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should raise NameError when given an invalid string' do
|
117
|
+
Car.embeds(1, :engine, 'DoesNotExist')
|
118
|
+
running = lambda { Car.embedments[:engine].target_model }
|
119
|
+
running.should raise_error(NameError)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe 'when a :model option is given' do
|
124
|
+
it 'should set the relationship target model when given a string' do
|
125
|
+
without_engine_defined do
|
126
|
+
Car.embeds(1, :engine, :model => 'Engine')
|
127
|
+
end
|
128
|
+
|
129
|
+
Car.embedments[:engine].target_model.should == Engine
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should raise NameError when given an invalid string' do
|
133
|
+
Car.embeds(1, :engine, :model => 'DoesNotExist')
|
134
|
+
running = lambda { Car.embedments[:engine].target_model }
|
135
|
+
running.should raise_error(NameError)
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'should set the relationship target model when given a model' do
|
139
|
+
Car.embeds(1, :engine, :model => Engine)
|
140
|
+
Car.embedments[:engine].target_model.should == Engine
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'when no model is given' do
|
145
|
+
it 'should raise NameError' do
|
146
|
+
Car.embeds(1, :does_not_exist)
|
147
|
+
running = lambda { Car.embedments[:engine].target_model }
|
148
|
+
running.should raise_error(NameError)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should raise an exception if a :through option is given' do
|
153
|
+
running = lambda { Car.embeds(1, Engine, :through => :engines) }
|
154
|
+
running.should raise_error(ArgumentError)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should raise an exception if a :remote_name option is given' do
|
158
|
+
running = lambda { Car.embeds(1, Engine, :remote_name => :engines) }
|
159
|
+
running.should raise_error(ArgumentError)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should raise an exception if a :via option is given' do
|
163
|
+
running = lambda { Car.embeds(1, Engine, :via => :engines) }
|
164
|
+
running.should raise_error(ArgumentError)
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should raise an exception if a :inverse option is given' do
|
168
|
+
running = lambda { Car.embeds(1, Engine, :inverse => :parent) }
|
169
|
+
running.should raise_error(ArgumentError)
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'should raise an exception if a :parent_key option is given' do
|
173
|
+
running = lambda { Car.embeds(1, Engine, :parent_key => :id) }
|
174
|
+
running.should raise_error(ArgumentError)
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'should raise an exception if the cardinality is not understood' do
|
178
|
+
lambda { Car.embeds(n..n, :doors) }.should raise_error(ArgumentError)
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'should raise an exception if the minimum constraint is larger than the maximum' do
|
182
|
+
lambda { Car.embeds(2..1, :doors) }.should raise_error(ArgumentError)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataMapper::Mongo::Model do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
class ::PropertyTest
|
7
|
+
include DataMapper::Mongo::Resource
|
8
|
+
property :array, Array
|
9
|
+
property :hash, Hash
|
10
|
+
property :date, Date
|
11
|
+
property :date_time, DateTime
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#property' do
|
16
|
+
it 'should cast Array types to EmbeddedArray' do
|
17
|
+
prop = PropertyTest.properties[:array]
|
18
|
+
prop.type.should == DataMapper::Mongo::Types::EmbeddedArray
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should cast Hash types to EmbeddedHash' do
|
22
|
+
prop = PropertyTest.properties[:hash]
|
23
|
+
prop.type.should == DataMapper::Mongo::Types::EmbeddedHash
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should cast Hash types to Types::Date' do
|
27
|
+
prop = PropertyTest.properties[:date]
|
28
|
+
prop.type.should == DataMapper::Mongo::Types::Date
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should cast Hash types to Types::DateTime' do
|
32
|
+
prop = PropertyTest.properties[:date_time]
|
33
|
+
prop.type.should == DataMapper::Mongo::Types::DateTime
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,564 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataMapper::Mongo::Resource do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
class ::Address
|
7
|
+
include DataMapper::Mongo::EmbeddedResource
|
8
|
+
property :city, String
|
9
|
+
property :state, String
|
10
|
+
end
|
11
|
+
|
12
|
+
class ::Location
|
13
|
+
include DataMapper::Mongo::EmbeddedResource
|
14
|
+
property :country, String
|
15
|
+
property :continent, String
|
16
|
+
end
|
17
|
+
|
18
|
+
class ::User
|
19
|
+
include DataMapper::Mongo::Resource
|
20
|
+
property :id, ObjectID
|
21
|
+
property :name, String
|
22
|
+
property :tags, Array
|
23
|
+
property :metadata, Hash
|
24
|
+
property :created_at, DateTime
|
25
|
+
embeds 1, :address, :model => Address
|
26
|
+
embeds n, :locations
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# all
|
32
|
+
#
|
33
|
+
|
34
|
+
describe '#all' do
|
35
|
+
describe 'with no query' do
|
36
|
+
it 'should return a collection' do
|
37
|
+
User.all.should be_kind_of(DataMapper::Collection)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should return an empty collection when there are no resources' do
|
41
|
+
User.all.destroy!
|
42
|
+
User.all.should be_empty
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should return all resources' do
|
46
|
+
expected = [User.create(:name => 'One'), User.create(:name => 'Two')]
|
47
|
+
User.all.should == expected
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should fail if incorrect conditions are given' do
|
51
|
+
lambda {
|
52
|
+
User.all :'adres.blah' => 'New York'
|
53
|
+
}.should raise_error
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'with a query' do
|
58
|
+
it 'should return a collection' do
|
59
|
+
User.all.should be_kind_of(DataMapper::Collection)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should return an empty collection when there are no matching resources' do
|
63
|
+
User.all.destroy!
|
64
|
+
User.create(:name => 'One')
|
65
|
+
User.all(:name => 'Two').should be_empty
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should return the specific resources' do
|
69
|
+
User.create(:name => 'One')
|
70
|
+
expected = User.create(:name => 'Two')
|
71
|
+
User.all(:name => 'Two').should == [expected]
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'including conditions for an embedded has 1 resource' do
|
75
|
+
before(:all) do
|
76
|
+
User.all.destroy!
|
77
|
+
User.create(:name => 'Boston guy', :address => { :city => 'Boston' })
|
78
|
+
@expected = User.create(:name => 'NY guy', :address => { :city => 'New York' })
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should return a collection' do
|
82
|
+
collection = User.all('address.city' => 'Washington')
|
83
|
+
collection.should be_kind_of(DataMapper::Collection)
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should return an empty collection when there are no matching resources' do
|
87
|
+
collection = User.all('address.city' => 'Washington')
|
88
|
+
collection.should be_empty
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should return specific resources' do
|
92
|
+
User.all('address.city' => 'New York').should == [@expected]
|
93
|
+
end
|
94
|
+
end # including conditions for an embedded has 1 resource
|
95
|
+
|
96
|
+
describe 'including conditions for an embedded has n resource' do
|
97
|
+
before(:all) do
|
98
|
+
User.all.destroy!
|
99
|
+
User.create(:name => 'Canada Guy', :locations => [{ :country => 'Canada' }])
|
100
|
+
|
101
|
+
@expected = User.create(:name => 'John Kowalski', :locations => [
|
102
|
+
{ :country => 'US', :continent => 'North America' },
|
103
|
+
{ :country => 'Poland', :continent => 'Europe' }])
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should return a collection' do
|
107
|
+
collection = User.all('locations.country' => 'Canada')
|
108
|
+
collection.should be_kind_of(DataMapper::Collection)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should return an empty collection when there are no matching resources' do
|
112
|
+
collection = User.all('locations.country' => 'Brazil')
|
113
|
+
collection.should be_empty
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should return specific resources' do
|
117
|
+
User.all('locations.country' => 'US').should == [@expected]
|
118
|
+
User.all('locations.country' => 'Poland', 'locations.continent' => 'Europe').should == [@expected]
|
119
|
+
end
|
120
|
+
end # including conditions for an embedded has n resource
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# first
|
127
|
+
#
|
128
|
+
|
129
|
+
describe '#first' do
|
130
|
+
before(:all) do
|
131
|
+
User.all.destroy!
|
132
|
+
@user_one = User.create(:name => 'Three')
|
133
|
+
@user_two = User.create(:name => 'Four')
|
134
|
+
end
|
135
|
+
|
136
|
+
describe 'with no query' do
|
137
|
+
it 'should return a resource' do
|
138
|
+
User.first.should be_kind_of(DataMapper::Mongo::Resource)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'should return the first resource' do
|
142
|
+
User.first.should == @user_one
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe 'when a query' do
|
147
|
+
it 'should return a resource' do
|
148
|
+
User.first(:name => @user_two.name).should \
|
149
|
+
be_kind_of(DataMapper::Mongo::Resource)
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should return the first resource' do
|
153
|
+
User.first(:name => @user_two.name).should == @user_two
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# aggregations
|
160
|
+
#
|
161
|
+
describe 'aggregations' do
|
162
|
+
before(:all) do
|
163
|
+
class ::Student
|
164
|
+
include DataMapper::Mongo::Resource
|
165
|
+
|
166
|
+
property :id, ObjectID
|
167
|
+
property :name, String
|
168
|
+
property :school, String
|
169
|
+
property :score, Float
|
170
|
+
end
|
171
|
+
|
172
|
+
Student.all.destroy!
|
173
|
+
|
174
|
+
@student_one = Student.create(:school => 'School 1', :name => 'One', :score => 3.0)
|
175
|
+
@student_two = Student.create(:school => 'School 2', :name => 'Two', :score => 3.5)
|
176
|
+
@student_three = Student.create(:school => 'School 2', :name => 'Three', :score => 4.5)
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# count
|
181
|
+
#
|
182
|
+
describe "#count" do
|
183
|
+
describe 'with no query' do
|
184
|
+
it 'should return number of all resources' do
|
185
|
+
Student.count.should == 3
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe 'with a query' do
|
190
|
+
it 'should return number of resources matching conditions' do
|
191
|
+
Student.count(:name => /one|two/i).should == 2
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
#
|
197
|
+
# aggregate
|
198
|
+
#
|
199
|
+
|
200
|
+
describe "#aggregate" do
|
201
|
+
|
202
|
+
#
|
203
|
+
# simple aggregation without any operators
|
204
|
+
#
|
205
|
+
|
206
|
+
describe "without operators" do
|
207
|
+
describe "without conditions" do
|
208
|
+
it "should return array of hashes based on all records" do
|
209
|
+
result = Student.aggregate(:school, :score)
|
210
|
+
|
211
|
+
result.should == [
|
212
|
+
{:school => "School 1", :score => 3.0},
|
213
|
+
{:school => "School 2", :score => 3.5},
|
214
|
+
{:school => "School 2", :score => 4.5}]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe "with conditions" do
|
219
|
+
it "should return array of hashes based on records that match conditions" do
|
220
|
+
result = Student.aggregate(:school, :score, :score.gt => 3.0)
|
221
|
+
|
222
|
+
result.should == [
|
223
|
+
{:school => "School 2", :score => 3.5},
|
224
|
+
{:school => "School 2", :score => 4.5}]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
#
|
230
|
+
# count
|
231
|
+
#
|
232
|
+
|
233
|
+
describe "count operator" do
|
234
|
+
describe "without conditions" do
|
235
|
+
it "should get correct results based on all records" do
|
236
|
+
result = Student.aggregate(:school, :score.count)
|
237
|
+
|
238
|
+
result.size.should == 2
|
239
|
+
|
240
|
+
school_1, school_2 = result
|
241
|
+
|
242
|
+
school_1[:school].should == 'School 1'
|
243
|
+
school_2[:school].should == 'School 2'
|
244
|
+
|
245
|
+
school_1[:score].should == 1
|
246
|
+
school_2[:score].should == 2
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
describe "with conditions" do
|
251
|
+
it "should get correct results based on records that match conditions" do
|
252
|
+
result = Student.aggregate(:school, :score.count, :name => /two|three/i)
|
253
|
+
|
254
|
+
result.size.should == 1
|
255
|
+
result.first[:score].should == 2
|
256
|
+
result.first[:school].should == 'School 2'
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
#
|
262
|
+
# avg
|
263
|
+
#
|
264
|
+
# TODO: add spec for #avg with conditions
|
265
|
+
|
266
|
+
describe "avg operator" do
|
267
|
+
describe 'without conditions' do
|
268
|
+
it 'should return an avarage value of the given field' do
|
269
|
+
result = Student.aggregate(:school, :score.avg)
|
270
|
+
|
271
|
+
school_1, school_2 = result
|
272
|
+
|
273
|
+
school_1[:school].should == 'School 1'
|
274
|
+
school_2[:school].should == 'School 2'
|
275
|
+
|
276
|
+
school_1[:score].should == 3.0
|
277
|
+
school_2[:score].should == 4.0
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
#
|
283
|
+
# min
|
284
|
+
#
|
285
|
+
# TODO: add spec for #min with conditions
|
286
|
+
|
287
|
+
describe "min operator" do
|
288
|
+
describe 'without conditions' do
|
289
|
+
it 'should return the minimum value of the given field' do
|
290
|
+
result = Student.aggregate(:school, :score.min)
|
291
|
+
|
292
|
+
school_1, school_2 = result
|
293
|
+
|
294
|
+
school_1[:school].should == 'School 1'
|
295
|
+
school_2[:school].should == 'School 2'
|
296
|
+
|
297
|
+
school_1[:score].should == 3.0
|
298
|
+
school_2[:score].should == 3.5
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
#
|
304
|
+
# max
|
305
|
+
#
|
306
|
+
# TODO: add spec for #max with conditions
|
307
|
+
|
308
|
+
describe "max operator" do
|
309
|
+
describe 'without conditions' do
|
310
|
+
it 'should return the maximum value of the given field' do
|
311
|
+
result = Student.aggregate(:school, :score.max)
|
312
|
+
|
313
|
+
school_1, school_2 = result
|
314
|
+
|
315
|
+
school_1[:school].should == 'School 1'
|
316
|
+
school_2[:school].should == 'School 2'
|
317
|
+
|
318
|
+
school_1[:score].should == 3.0
|
319
|
+
school_2[:score].should == 4.5
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
#
|
325
|
+
# max
|
326
|
+
#
|
327
|
+
# TODO: add spec for #sum with conditions
|
328
|
+
|
329
|
+
describe "sum operator" do
|
330
|
+
describe 'without conditions' do
|
331
|
+
it 'should return the maximum value of the given field' do
|
332
|
+
result = Student.aggregate(:school, :score.sum)
|
333
|
+
|
334
|
+
school_1, school_2 = result
|
335
|
+
|
336
|
+
school_1[:school].should == 'School 1'
|
337
|
+
school_2[:school].should == 'School 2'
|
338
|
+
|
339
|
+
school_1[:score].should == 3.0
|
340
|
+
school_2[:score].should == 8.0
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
#
|
348
|
+
# dirty?
|
349
|
+
#
|
350
|
+
|
351
|
+
describe '#dirty?' do
|
352
|
+
describe 'when the resource has a change' do
|
353
|
+
it 'should return true' do
|
354
|
+
User.new(:name => 'Mongo').should be_dirty
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
describe 'when the resource has no changes' do
|
359
|
+
it 'should return true if a one-to-one embedment has a change' do
|
360
|
+
user = User.new(:address => Address.new(:city => 'Rock Ridge'))
|
361
|
+
user.should be_dirty
|
362
|
+
end
|
363
|
+
|
364
|
+
it 'should return false having just been saved' do
|
365
|
+
user = User.new(:address => Address.new(:city => 'Rock Ridge'))
|
366
|
+
user.save
|
367
|
+
user.should_not be_dirty
|
368
|
+
end
|
369
|
+
|
370
|
+
it 'should return true if a one-to-many embedment has a change' do
|
371
|
+
user = User.new
|
372
|
+
user.locations << Address.new(:city => 'Rock Ridge')
|
373
|
+
user.should be_dirty
|
374
|
+
end
|
375
|
+
|
376
|
+
it 'should return false if no embedments have changes' do
|
377
|
+
user = User.new(:address => Address.new(:city => 'Rock Ridge'))
|
378
|
+
user.locations << Address.new(:city => 'Rock Ridge')
|
379
|
+
user.save
|
380
|
+
user.should_not be_dirty
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
#
|
386
|
+
# Array properties
|
387
|
+
#
|
388
|
+
|
389
|
+
describe 'Array properties' do
|
390
|
+
it 'should permit nil' do
|
391
|
+
user = User.new(:tags => nil)
|
392
|
+
user.tags.should be_nil
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'should persist nil' do
|
396
|
+
user = User.create(:tags => nil)
|
397
|
+
User.get(user.id).tags.should be_nil
|
398
|
+
end
|
399
|
+
|
400
|
+
it 'should permit an Array' do
|
401
|
+
user = User.new(:tags => ['loud', 'troll'])
|
402
|
+
user.tags.should == ['loud', 'troll']
|
403
|
+
end
|
404
|
+
|
405
|
+
it 'should persist an Array' do
|
406
|
+
user = User.create(:tags => ['loud', 'troll'])
|
407
|
+
User.get(user.id).tags.should ==['loud', 'troll']
|
408
|
+
end
|
409
|
+
|
410
|
+
it 'should persist nested properties in an Array' do
|
411
|
+
user = User.create(:tags => ['troll', ['system', 'banned']])
|
412
|
+
User.get(user.id).tags.should == ['troll', ['system', 'banned']]
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
#
|
417
|
+
# Hash properties
|
418
|
+
#
|
419
|
+
|
420
|
+
describe 'Hash properties' do
|
421
|
+
it 'should permit nil' do
|
422
|
+
user = User.new(:metadata => nil)
|
423
|
+
user.metadata.should be_nil
|
424
|
+
end
|
425
|
+
|
426
|
+
it 'should persist nil' do
|
427
|
+
user = User.create(:metadata => nil)
|
428
|
+
User.get(user.id).metadata.should be_nil
|
429
|
+
end
|
430
|
+
|
431
|
+
it 'should permit a Hash' do
|
432
|
+
user = User.new(:metadata => { :one => 'two' })
|
433
|
+
user.metadata.should == { :one => 'two' }
|
434
|
+
end
|
435
|
+
|
436
|
+
it 'should persist a Hash' do
|
437
|
+
user = User.create(:metadata => { :one => 'two' })
|
438
|
+
User.get(user.id).metadata.should == { :one => 'two' }
|
439
|
+
end
|
440
|
+
|
441
|
+
it 'should permit Hash-like Arrays' do
|
442
|
+
user = User.new(:metadata => [:one, 'two'])
|
443
|
+
user.metadata.should == { :one => 'two' }
|
444
|
+
end
|
445
|
+
|
446
|
+
it 'should persist Hash-like Arrays' do
|
447
|
+
user = User.create(:metadata => [:one, 'two'])
|
448
|
+
User.get(user.id).metadata.should == { :one => 'two' }
|
449
|
+
end
|
450
|
+
|
451
|
+
it 'should persist nested properties in an Array' do
|
452
|
+
user = User.create(:metadata => { :one => { :two => :three } })
|
453
|
+
pending "EmbeddedHash doesn't typecast nested keys yet" do
|
454
|
+
User.get(user.id).metadata.should == { :one => { :two => :three } }
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
describe "Modifier" do
|
460
|
+
before :all do
|
461
|
+
class ::Post
|
462
|
+
include DataMapper::Mongo::Resource
|
463
|
+
|
464
|
+
property :id, ObjectID
|
465
|
+
property :comment_count, Integer
|
466
|
+
property :body, Text
|
467
|
+
end
|
468
|
+
|
469
|
+
Post.all.destroy!
|
470
|
+
end
|
471
|
+
|
472
|
+
describe "#increment" do
|
473
|
+
before :all do
|
474
|
+
@post = Post.create(:comment_count => 1)
|
475
|
+
@post.increment(:comment_count, 1)
|
476
|
+
end
|
477
|
+
|
478
|
+
it "should update the given property with the incremented value" do
|
479
|
+
@post.comment_count.should == 2
|
480
|
+
Post.get(@post.id).comment_count.should == 2
|
481
|
+
end
|
482
|
+
|
483
|
+
it "should reload the updated resource" do
|
484
|
+
@post.dirty?.should be_false
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
describe "#decrement" do
|
489
|
+
before :all do
|
490
|
+
@post = Post.create(:comment_count => 10)
|
491
|
+
@post.decrement(:comment_count, 5)
|
492
|
+
end
|
493
|
+
|
494
|
+
it "should update the given property with the decremented value" do
|
495
|
+
@post.comment_count.should == 5
|
496
|
+
Post.get(@post.id).comment_count.should == 5
|
497
|
+
end
|
498
|
+
|
499
|
+
it "should reload the updated resource" do
|
500
|
+
@post.dirty?.should be_false
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
describe "#set" do
|
505
|
+
it "should set the value of a property" do
|
506
|
+
post = Post.create(:body => "This needs to be edited", :comment_count => 2)
|
507
|
+
|
508
|
+
pending "not implemented yet" do
|
509
|
+
post.set(:body => "This was edited", :comment_count => 3)
|
510
|
+
post.body.should == "This was edited"
|
511
|
+
post.comment_count.should == 3
|
512
|
+
fresh_post = Post.get(post.id)
|
513
|
+
fresh_post.body.should == "This was edited"
|
514
|
+
fresh_post.comment_count.should == 3
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
describe "#unset" do
|
520
|
+
it "should unset the value of a property" do
|
521
|
+
post = Post.create(:body => "This needs to be removed", :comment_count => 2)
|
522
|
+
|
523
|
+
pending "not implemented yet" do
|
524
|
+
post.unset(:body, :comment_count)
|
525
|
+
post.body.should be_nil
|
526
|
+
post.comment_count.should be_nil
|
527
|
+
fresh_post = Post.get(post.id)
|
528
|
+
fresh_post.body.should be_nil
|
529
|
+
fresh_post.comment_count.should be_nil
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
describe "#push" do
|
535
|
+
it "should be implemented" do
|
536
|
+
pending
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
describe "#push_all" do
|
541
|
+
it "should be implemented" do
|
542
|
+
pending
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
describe "#pop" do
|
547
|
+
it "should be implemented" do
|
548
|
+
pending
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
describe "#pull" do
|
553
|
+
it "should be implemented" do
|
554
|
+
pending
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
describe "#pull_all" do
|
559
|
+
it "should be implemented" do
|
560
|
+
pending
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|
564
|
+
end
|