dm-mongo-adapter 0.2.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +9 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +130 -0
  4. data/Rakefile +36 -0
  5. data/TODO +33 -0
  6. data/VERSION.yml +5 -0
  7. data/bin/console +31 -0
  8. data/dm-mongo-adapter.gemspec +154 -0
  9. data/lib/mongo_adapter.rb +71 -0
  10. data/lib/mongo_adapter/adapter.rb +255 -0
  11. data/lib/mongo_adapter/aggregates.rb +21 -0
  12. data/lib/mongo_adapter/conditions.rb +100 -0
  13. data/lib/mongo_adapter/embedded_model.rb +187 -0
  14. data/lib/mongo_adapter/embedded_resource.rb +134 -0
  15. data/lib/mongo_adapter/embedments/one_to_many.rb +139 -0
  16. data/lib/mongo_adapter/embedments/one_to_one.rb +53 -0
  17. data/lib/mongo_adapter/embedments/relationship.rb +258 -0
  18. data/lib/mongo_adapter/migrations.rb +45 -0
  19. data/lib/mongo_adapter/model.rb +89 -0
  20. data/lib/mongo_adapter/model/embedment.rb +215 -0
  21. data/lib/mongo_adapter/modifier.rb +85 -0
  22. data/lib/mongo_adapter/query.rb +252 -0
  23. data/lib/mongo_adapter/query/java_script.rb +145 -0
  24. data/lib/mongo_adapter/resource.rb +147 -0
  25. data/lib/mongo_adapter/types/date.rb +28 -0
  26. data/lib/mongo_adapter/types/date_time.rb +28 -0
  27. data/lib/mongo_adapter/types/db_ref.rb +65 -0
  28. data/lib/mongo_adapter/types/discriminator.rb +32 -0
  29. data/lib/mongo_adapter/types/object_id.rb +72 -0
  30. data/lib/mongo_adapter/types/objects.rb +31 -0
  31. data/script/performance.rb +260 -0
  32. data/spec/legacy/README +6 -0
  33. data/spec/legacy/adapter_shared_spec.rb +299 -0
  34. data/spec/legacy/adapter_spec.rb +174 -0
  35. data/spec/legacy/associations_spec.rb +140 -0
  36. data/spec/legacy/embedded_resource_spec.rb +157 -0
  37. data/spec/legacy/embedments_spec.rb +177 -0
  38. data/spec/legacy/modifier_spec.rb +81 -0
  39. data/spec/legacy/property_spec.rb +51 -0
  40. data/spec/legacy/spec_helper.rb +3 -0
  41. data/spec/legacy/sti_spec.rb +53 -0
  42. data/spec/lib/cleanup_models.rb +32 -0
  43. data/spec/lib/raw_connections.rb +11 -0
  44. data/spec/public/embedded_collection_spec.rb +61 -0
  45. data/spec/public/embedded_resource_spec.rb +220 -0
  46. data/spec/public/model/embedment_spec.rb +186 -0
  47. data/spec/public/model_spec.rb +37 -0
  48. data/spec/public/resource_spec.rb +564 -0
  49. data/spec/public/shared/model_embedments_spec.rb +338 -0
  50. data/spec/public/shared/object_id_shared_spec.rb +56 -0
  51. data/spec/public/types/df_ref_spec.rb +6 -0
  52. data/spec/public/types/discriminator_spec.rb +118 -0
  53. data/spec/public/types/embedded_array_spec.rb +55 -0
  54. data/spec/public/types/embedded_hash_spec.rb +83 -0
  55. data/spec/public/types/object_id_spec.rb +6 -0
  56. data/spec/rcov.opts +6 -0
  57. data/spec/semipublic/embedded_model_spec.rb +43 -0
  58. data/spec/semipublic/model/embedment_spec.rb +42 -0
  59. data/spec/semipublic/resource_spec.rb +70 -0
  60. data/spec/spec.opts +4 -0
  61. data/spec/spec_helper.rb +45 -0
  62. data/tasks/spec.rake +37 -0
  63. data/tasks/yard.rake +9 -0
  64. data/tasks/yardstick.rake +21 -0
  65. 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