dm-mongo-adapter 0.2.0.pre1
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/.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
|