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.
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