fcoury-mongomapper 0.2.0
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 +7 -0
- data/History +30 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +71 -0
- data/VERSION +1 -0
- data/lib/mongomapper.rb +70 -0
- data/lib/mongomapper/associations.rb +69 -0
- data/lib/mongomapper/associations/array_proxy.rb +6 -0
- data/lib/mongomapper/associations/base.rb +54 -0
- data/lib/mongomapper/associations/belongs_to_proxy.rb +26 -0
- data/lib/mongomapper/associations/has_many_embedded_proxy.rb +19 -0
- data/lib/mongomapper/associations/has_many_proxy.rb +29 -0
- data/lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb +31 -0
- data/lib/mongomapper/associations/proxy.rb +66 -0
- data/lib/mongomapper/callbacks.rb +106 -0
- data/lib/mongomapper/document.rb +276 -0
- data/lib/mongomapper/document_rails_compatibility.rb +13 -0
- data/lib/mongomapper/embedded_document.rb +248 -0
- data/lib/mongomapper/embedded_document_rails_compatibility.rb +22 -0
- data/lib/mongomapper/finder_options.rb +81 -0
- data/lib/mongomapper/key.rb +82 -0
- data/lib/mongomapper/observing.rb +50 -0
- data/lib/mongomapper/save_with_validation.rb +19 -0
- data/lib/mongomapper/serialization.rb +55 -0
- data/lib/mongomapper/serializers/json_serializer.rb +77 -0
- data/lib/mongomapper/validations.rb +47 -0
- data/mongomapper.gemspec +105 -0
- data/test/serializers/test_json_serializer.rb +104 -0
- data/test/test_associations.rb +444 -0
- data/test/test_callbacks.rb +84 -0
- data/test/test_document.rb +1002 -0
- data/test/test_embedded_document.rb +253 -0
- data/test/test_finder_options.rb +148 -0
- data/test/test_helper.rb +62 -0
- data/test/test_key.rb +200 -0
- data/test/test_mongomapper.rb +28 -0
- data/test/test_observing.rb +101 -0
- data/test/test_rails_compatibility.rb +73 -0
- data/test/test_serializations.rb +54 -0
- data/test/test_validations.rb +409 -0
- metadata +155 -0
@@ -0,0 +1,1002 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Address
|
4
|
+
include MongoMapper::EmbeddedDocument
|
5
|
+
key :city, String
|
6
|
+
key :state, String
|
7
|
+
end
|
8
|
+
|
9
|
+
class DocumentTest < Test::Unit::TestCase
|
10
|
+
context "The Document Class" do
|
11
|
+
setup do
|
12
|
+
@document = Class.new do
|
13
|
+
include MongoMapper::Document
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
should "track its descendants" do
|
18
|
+
MongoMapper::Document.descendants.should include(@document)
|
19
|
+
end
|
20
|
+
|
21
|
+
should "find its parent model" do
|
22
|
+
class A < Address
|
23
|
+
key :new_key, String
|
24
|
+
end
|
25
|
+
|
26
|
+
A.parent_model.should == Address
|
27
|
+
end
|
28
|
+
|
29
|
+
should "inherit keys" do
|
30
|
+
class A < Address
|
31
|
+
key :new_key, String
|
32
|
+
end
|
33
|
+
|
34
|
+
A.keys.should include("new_key")
|
35
|
+
Address.keys.should_not include("new_key")
|
36
|
+
end
|
37
|
+
|
38
|
+
should "be able to define a key" do
|
39
|
+
key = @document.key(:name, String)
|
40
|
+
key.name.should == 'name'
|
41
|
+
key.type.should == String
|
42
|
+
key.should be_instance_of(MongoMapper::Key)
|
43
|
+
end
|
44
|
+
|
45
|
+
should "be able to define a key with options" do
|
46
|
+
key = @document.key(:name, String, :required => true)
|
47
|
+
key.options[:required].should be(true)
|
48
|
+
end
|
49
|
+
|
50
|
+
should "know what keys have been defined" do
|
51
|
+
@document.key(:name, String)
|
52
|
+
@document.key(:age, Integer)
|
53
|
+
@document.keys['name'].name.should == 'name'
|
54
|
+
@document.keys['name'].type.should == String
|
55
|
+
@document.keys['age'].name.should == 'age'
|
56
|
+
@document.keys['age'].type.should == Integer
|
57
|
+
end
|
58
|
+
|
59
|
+
should "allow redefining a key" do
|
60
|
+
@document.key(:foo, String)
|
61
|
+
@document.keys['foo'].type.should == String
|
62
|
+
@document.key(:foo, Integer)
|
63
|
+
@document.keys['foo'].type.should == Integer
|
64
|
+
end
|
65
|
+
|
66
|
+
should "use default database by default" do
|
67
|
+
@document.database.should == MongoMapper.database
|
68
|
+
end
|
69
|
+
|
70
|
+
should "have a connection" do
|
71
|
+
@document.connection.should be_instance_of(XGen::Mongo::Driver::Mongo)
|
72
|
+
end
|
73
|
+
|
74
|
+
should "allow setting different connection without affecting the default" do
|
75
|
+
conn = XGen::Mongo::Driver::Mongo.new
|
76
|
+
@document.connection conn
|
77
|
+
@document.connection.should == conn
|
78
|
+
@document.connection.should_not == MongoMapper.connection
|
79
|
+
end
|
80
|
+
|
81
|
+
should "allow setting a different database without affecting the default" do
|
82
|
+
@document.database AlternateDatabase
|
83
|
+
@document.database.name.should == AlternateDatabase
|
84
|
+
|
85
|
+
another_document = Class.new do
|
86
|
+
include MongoMapper::Document
|
87
|
+
end
|
88
|
+
another_document.database.should == MongoMapper.database
|
89
|
+
end
|
90
|
+
|
91
|
+
class Item; include MongoMapper::Document; end
|
92
|
+
should "default collection name to class name tableized" do
|
93
|
+
Item.collection.should be_instance_of(XGen::Mongo::Driver::Collection)
|
94
|
+
Item.collection.name.should == 'items'
|
95
|
+
end
|
96
|
+
|
97
|
+
should "allow setting the collection name" do
|
98
|
+
@document.collection('foobar')
|
99
|
+
@document.collection.should be_instance_of(XGen::Mongo::Driver::Collection)
|
100
|
+
@document.collection.name.should == 'foobar'
|
101
|
+
end
|
102
|
+
end # Document class
|
103
|
+
|
104
|
+
context "Database operations" do
|
105
|
+
setup do
|
106
|
+
@document = Class.new do
|
107
|
+
include MongoMapper::Document
|
108
|
+
collection 'users'
|
109
|
+
|
110
|
+
key :fname, String
|
111
|
+
key :lname, String
|
112
|
+
key :age, Integer
|
113
|
+
end
|
114
|
+
|
115
|
+
@document.collection.clear
|
116
|
+
end
|
117
|
+
|
118
|
+
context "Using key with type Array" do
|
119
|
+
setup do
|
120
|
+
@document.key :tags, Array
|
121
|
+
end
|
122
|
+
|
123
|
+
should "work" do
|
124
|
+
doc = @document.new
|
125
|
+
doc.tags.should == []
|
126
|
+
doc.tags = %w(foo bar)
|
127
|
+
doc.save
|
128
|
+
doc.tags.should == %w(foo bar)
|
129
|
+
@document.find(doc.id).tags.should == %w(foo bar)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "Using key with type Hash" do
|
134
|
+
setup do
|
135
|
+
@document.key :foo, Hash
|
136
|
+
end
|
137
|
+
|
138
|
+
should "work with indifferent access" do
|
139
|
+
doc = @document.new
|
140
|
+
doc.foo = {:baz => 'bar'}
|
141
|
+
doc.save
|
142
|
+
|
143
|
+
doc = @document.find(doc.id)
|
144
|
+
doc.foo[:baz].should == 'bar'
|
145
|
+
doc.foo['baz'].should == 'bar'
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "Saving a document with an embedded document" do
|
150
|
+
setup do
|
151
|
+
@document.class_eval do
|
152
|
+
key :foo, Address
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
should "embed embedded document" do
|
157
|
+
address = Address.new(:city => 'South Bend', :state => 'IN')
|
158
|
+
doc = @document.new(:foo => address)
|
159
|
+
doc.save
|
160
|
+
doc.foo.city.should == 'South Bend'
|
161
|
+
doc.foo.state.should == 'IN'
|
162
|
+
|
163
|
+
from_db = @document.find(doc.id)
|
164
|
+
from_db.foo.city.should == 'South Bend'
|
165
|
+
from_db.foo.state.should == 'IN'
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context "Creating a single document" do
|
170
|
+
setup do
|
171
|
+
@doc_instance = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
172
|
+
end
|
173
|
+
|
174
|
+
should "create a document in correct collection" do
|
175
|
+
@document.count.should == 1
|
176
|
+
end
|
177
|
+
|
178
|
+
should "automatically set id" do
|
179
|
+
@doc_instance.id.should_not be_nil
|
180
|
+
@doc_instance.id.size.should == 24
|
181
|
+
end
|
182
|
+
|
183
|
+
should "return instance of document" do
|
184
|
+
@doc_instance.should be_instance_of(@document)
|
185
|
+
@doc_instance.fname.should == 'John'
|
186
|
+
@doc_instance.lname.should == 'Nunemaker'
|
187
|
+
@doc_instance.age.should == 27
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context "Creating multiple documents" do
|
192
|
+
setup do
|
193
|
+
@doc_instances = @document.create([
|
194
|
+
{:fname => 'John', :lname => 'Nunemaker', :age => '27'},
|
195
|
+
{:fname => 'Steve', :lname => 'Smith', :age => '28'},
|
196
|
+
])
|
197
|
+
end
|
198
|
+
|
199
|
+
should "create multiple documents" do
|
200
|
+
@document.count.should == 2
|
201
|
+
end
|
202
|
+
|
203
|
+
should "return an array of doc instances" do
|
204
|
+
@doc_instances.map do |doc_instance|
|
205
|
+
doc_instance.should be_instance_of(@document)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context "Updating a document" do
|
211
|
+
setup do
|
212
|
+
doc = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
213
|
+
@doc_instance = @document.update(doc.id, {:age => 40})
|
214
|
+
end
|
215
|
+
|
216
|
+
should "update attributes provided" do
|
217
|
+
@doc_instance.age.should == 40
|
218
|
+
end
|
219
|
+
|
220
|
+
should "not update existing attributes that were not set to update" do
|
221
|
+
@doc_instance.fname.should == 'John'
|
222
|
+
@doc_instance.lname.should == 'Nunemaker'
|
223
|
+
end
|
224
|
+
|
225
|
+
should "not create new document" do
|
226
|
+
@document.count.should == 1
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
should "raise error when updating single doc if not provided id and attributes" do
|
231
|
+
doc = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
232
|
+
lambda { @document.update }.should raise_error(ArgumentError)
|
233
|
+
lambda { @document.update(doc.id) }.should raise_error(ArgumentError)
|
234
|
+
lambda { @document.update(doc.id, [1]) }.should raise_error(ArgumentError)
|
235
|
+
end
|
236
|
+
|
237
|
+
context "Updating multiple documents" do
|
238
|
+
setup do
|
239
|
+
@doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
240
|
+
@doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
|
241
|
+
|
242
|
+
@doc_instances = @document.update({
|
243
|
+
@doc1.id => {:age => 30},
|
244
|
+
@doc2.id => {:age => 30},
|
245
|
+
})
|
246
|
+
end
|
247
|
+
|
248
|
+
should "not create any new documents" do
|
249
|
+
@document.count.should == 2
|
250
|
+
end
|
251
|
+
|
252
|
+
should "should return an array of doc instances" do
|
253
|
+
@doc_instances.map do |doc_instance|
|
254
|
+
doc_instance.should be_instance_of(@document)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
should "update the documents" do
|
259
|
+
@document.find(@doc1.id).age.should == 30
|
260
|
+
@document.find(@doc2.id).age.should == 30
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
should "raise error when updating multiple documents if not a hash" do
|
265
|
+
lambda { @document.update([1, 2]) }.should raise_error(ArgumentError)
|
266
|
+
end
|
267
|
+
|
268
|
+
context "Finding documents" do
|
269
|
+
setup do
|
270
|
+
@doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
271
|
+
@doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
|
272
|
+
@doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
|
273
|
+
end
|
274
|
+
|
275
|
+
should "raise document not found if nothing provided" do
|
276
|
+
lambda { @document.find }.should raise_error(MongoMapper::DocumentNotFound)
|
277
|
+
end
|
278
|
+
|
279
|
+
context "with a single id" do
|
280
|
+
should "work" do
|
281
|
+
@document.find(@doc1.id).should == @doc1
|
282
|
+
end
|
283
|
+
|
284
|
+
should "raise error if document not found" do
|
285
|
+
lambda { @document.find(1) }.should raise_error(MongoMapper::DocumentNotFound)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
context "with multiple id's" do
|
290
|
+
should "work as arguments" do
|
291
|
+
@document.find(@doc1.id, @doc2.id).should == [@doc1, @doc2]
|
292
|
+
end
|
293
|
+
|
294
|
+
should "work as array" do
|
295
|
+
@document.find([@doc1.id, @doc2.id]).should == [@doc1, @doc2]
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
context "with :all" do
|
300
|
+
should "find all documents" do
|
301
|
+
@document.find(:all).should == [@doc1, @doc2, @doc3]
|
302
|
+
end
|
303
|
+
|
304
|
+
should "be able to add conditions" do
|
305
|
+
@document.find(:all, :conditions => {:fname => 'John'}).should == [@doc1]
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
context "with #all" do
|
310
|
+
should "find all documents based on criteria" do
|
311
|
+
@document.all.should == [@doc1, @doc2, @doc3]
|
312
|
+
@document.all(:conditions => {:lname => 'Nunemaker'}).should == [@doc1, @doc3]
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
context "with :first" do
|
317
|
+
should "find first document" do
|
318
|
+
@document.find(:first).should == @doc1
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
context "with #first" do
|
323
|
+
should "find first document based on criteria" do
|
324
|
+
@document.first.should == @doc1
|
325
|
+
@document.first(:conditions => {:age => 28}).should == @doc2
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
context "with :last" do
|
330
|
+
should "find last document" do
|
331
|
+
@document.find(:last).should == @doc3
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
context "with #last" do
|
336
|
+
should "find last document based on criteria" do
|
337
|
+
@document.last.should == @doc3
|
338
|
+
@document.last(:conditions => {:age => 28}).should == @doc2
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end # finding documents
|
342
|
+
|
343
|
+
context "Finding document by id" do
|
344
|
+
setup do
|
345
|
+
@doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
346
|
+
@doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
|
347
|
+
end
|
348
|
+
|
349
|
+
should "be able to find by id" do
|
350
|
+
@document.find_by_id(@doc1.id).should == @doc1
|
351
|
+
@document.find_by_id(@doc2.id).should == @doc2
|
352
|
+
end
|
353
|
+
|
354
|
+
should "return nil if document not found" do
|
355
|
+
@document.find_by_id(1234).should be(nil)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
context "Deleting a document" do
|
360
|
+
setup do
|
361
|
+
@doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
362
|
+
@doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
|
363
|
+
@document.delete(@doc1.id)
|
364
|
+
end
|
365
|
+
|
366
|
+
should "remove document from collection" do
|
367
|
+
@document.count.should == 1
|
368
|
+
end
|
369
|
+
|
370
|
+
should "not remove other documents" do
|
371
|
+
@document.find(@doc2.id).should_not be(nil)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
context "Deleting multiple documents" do
|
376
|
+
should "work with multiple arguments" do
|
377
|
+
@doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
378
|
+
@doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
|
379
|
+
@doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
|
380
|
+
@document.delete(@doc1.id, @doc2.id)
|
381
|
+
|
382
|
+
@document.count.should == 1
|
383
|
+
end
|
384
|
+
|
385
|
+
should "work with array as argument" do
|
386
|
+
@doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
387
|
+
@doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
|
388
|
+
@doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
|
389
|
+
@document.delete([@doc1.id, @doc2.id])
|
390
|
+
|
391
|
+
@document.count.should == 1
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
context "Deleting all documents" do
|
396
|
+
setup do
|
397
|
+
@doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
398
|
+
@doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
|
399
|
+
@doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
|
400
|
+
end
|
401
|
+
|
402
|
+
should "remove all documents when given no conditions" do
|
403
|
+
@document.delete_all
|
404
|
+
@document.count.should == 0
|
405
|
+
end
|
406
|
+
|
407
|
+
should "only remove matching documents when given conditions" do
|
408
|
+
@document.delete_all({:fname => 'John'})
|
409
|
+
@document.count.should == 2
|
410
|
+
end
|
411
|
+
|
412
|
+
should "convert the conditions to mongo criteria" do
|
413
|
+
@document.delete_all(:age => [26, 27])
|
414
|
+
@document.count.should == 1
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
context "Destroying a document" do
|
419
|
+
setup do
|
420
|
+
@doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
421
|
+
@doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
|
422
|
+
@document.destroy(@doc1.id)
|
423
|
+
end
|
424
|
+
|
425
|
+
should "remove document from collection" do
|
426
|
+
@document.count.should == 1
|
427
|
+
end
|
428
|
+
|
429
|
+
should "not remove other documents" do
|
430
|
+
@document.find(@doc2.id).should_not be(nil)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
context "Destroying multiple documents" do
|
435
|
+
should "work with multiple arguments" do
|
436
|
+
@doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
437
|
+
@doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
|
438
|
+
@doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
|
439
|
+
@document.destroy(@doc1.id, @doc2.id)
|
440
|
+
|
441
|
+
@document.count.should == 1
|
442
|
+
end
|
443
|
+
|
444
|
+
should "work with array as argument" do
|
445
|
+
@doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
446
|
+
@doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
|
447
|
+
@doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
|
448
|
+
@document.destroy([@doc1.id, @doc2.id])
|
449
|
+
|
450
|
+
@document.count.should == 1
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
context "Destroying all documents" do
|
455
|
+
setup do
|
456
|
+
@doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
457
|
+
@doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
|
458
|
+
@doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
|
459
|
+
end
|
460
|
+
|
461
|
+
should "remove all documents when given no conditions" do
|
462
|
+
@document.destroy_all
|
463
|
+
@document.count.should == 0
|
464
|
+
end
|
465
|
+
|
466
|
+
should "only remove matching documents when given conditions" do
|
467
|
+
@document.destroy_all(:fname => 'John')
|
468
|
+
@document.count.should == 2
|
469
|
+
@document.destroy_all(:age => 26)
|
470
|
+
@document.count.should == 1
|
471
|
+
end
|
472
|
+
|
473
|
+
should "convert the conditions to mongo criteria" do
|
474
|
+
@document.destroy_all(:age => [26, 27])
|
475
|
+
@document.count.should == 1
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
context "Counting documents in collection" do
|
480
|
+
setup do
|
481
|
+
@doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
482
|
+
@doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
|
483
|
+
@doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
|
484
|
+
end
|
485
|
+
|
486
|
+
should "count all with no arguments" do
|
487
|
+
@document.count.should == 3
|
488
|
+
end
|
489
|
+
|
490
|
+
should "return 0 if there are no documents in the collection" do
|
491
|
+
@document.delete_all
|
492
|
+
@document.count.should == 0
|
493
|
+
end
|
494
|
+
|
495
|
+
should "return 0 if the collection does not exist" do
|
496
|
+
klass = Class.new do
|
497
|
+
include MongoMapper::Document
|
498
|
+
collection 'foobarbazwickdoesnotexist'
|
499
|
+
end
|
500
|
+
|
501
|
+
klass.count.should == 0
|
502
|
+
end
|
503
|
+
|
504
|
+
should "return count for matching documents if conditions provided" do
|
505
|
+
@document.count(:age => 27).should == 1
|
506
|
+
end
|
507
|
+
|
508
|
+
should "convert the conditions to mongo criteria" do
|
509
|
+
@document.count(:age => [26, 27]).should == 2
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
context "Paginating" do
|
514
|
+
setup do
|
515
|
+
@doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
|
516
|
+
@doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
|
517
|
+
@doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
|
518
|
+
end
|
519
|
+
|
520
|
+
should "return the total pages" do
|
521
|
+
result = @document.paginate(:per_page => 2, :page => 1)
|
522
|
+
result.total_pages.should == 2
|
523
|
+
end
|
524
|
+
|
525
|
+
should "return the total of records" do
|
526
|
+
result = @document.paginate(:per_page => 2, :page => 1)
|
527
|
+
result.total_entries.should == 3
|
528
|
+
end
|
529
|
+
|
530
|
+
should "return the items" do
|
531
|
+
result = @document.paginate(:per_page => 2, :page => 1)
|
532
|
+
result.size.should == 2
|
533
|
+
result.subject.should == [@doc1, @doc2]
|
534
|
+
end
|
535
|
+
|
536
|
+
should "accept conditions" do
|
537
|
+
result = @document.paginate({
|
538
|
+
:conditions => {:lname => 'Nunemaker'},
|
539
|
+
:order => "age DESC",
|
540
|
+
:per_page => 2,
|
541
|
+
:page => 1,
|
542
|
+
})
|
543
|
+
result.first.age.should == 27
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
context "Indexing" do
|
548
|
+
setup do
|
549
|
+
@document.collection.drop_indexes
|
550
|
+
end
|
551
|
+
|
552
|
+
should "allow creating index for a key" do
|
553
|
+
lambda {
|
554
|
+
@document.ensure_index :fname
|
555
|
+
}.should change { @document.collection.index_information.size }.by(1)
|
556
|
+
|
557
|
+
index = @document.collection.index_information['fname_1']
|
558
|
+
index.should_not be_nil
|
559
|
+
index.should include(['fname', 1])
|
560
|
+
end
|
561
|
+
|
562
|
+
should "allow creating unique index for a key" do
|
563
|
+
@document.collection.expects(:create_index).with(:fname, true)
|
564
|
+
@document.ensure_index :fname, :unique => true
|
565
|
+
end
|
566
|
+
|
567
|
+
should "allow creating index on multiple keys" do
|
568
|
+
lambda {
|
569
|
+
@document.ensure_index [[:fname, 1], [:lname, -1]]
|
570
|
+
}.should change { @document.collection.index_information.size }.by(1)
|
571
|
+
|
572
|
+
index = @document.collection.index_information['lname_-1_fname_1']
|
573
|
+
index.should_not be_nil
|
574
|
+
index.should include(['fname', 1])
|
575
|
+
index.should include(['lname', -1])
|
576
|
+
end
|
577
|
+
|
578
|
+
should "work with :index shortcut when defining key" do
|
579
|
+
lambda {
|
580
|
+
@document.key :father, String, :index => true
|
581
|
+
}.should change { @document.collection.index_information.size }.by(1)
|
582
|
+
|
583
|
+
index = @document.collection.index_information['father_1']
|
584
|
+
index.should_not be_nil
|
585
|
+
index.should include(['father', 1])
|
586
|
+
end
|
587
|
+
end
|
588
|
+
end # Database operations
|
589
|
+
|
590
|
+
context "An instance of a document" do
|
591
|
+
setup do
|
592
|
+
@document = Class.new do
|
593
|
+
include MongoMapper::Document
|
594
|
+
|
595
|
+
key :name, String
|
596
|
+
key :age, Integer
|
597
|
+
end
|
598
|
+
@document.collection.clear
|
599
|
+
end
|
600
|
+
|
601
|
+
should "have access to the class's collection" do
|
602
|
+
doc = @document.new
|
603
|
+
doc.collection.should == @document.collection
|
604
|
+
end
|
605
|
+
|
606
|
+
should "automatically have an _id key" do
|
607
|
+
@document.keys.keys.should include('_id')
|
608
|
+
end
|
609
|
+
|
610
|
+
should "automatically have a created_at key" do
|
611
|
+
@document.keys.keys.should include('created_at')
|
612
|
+
end
|
613
|
+
|
614
|
+
should "automatically have an updated_at key" do
|
615
|
+
@document.keys.keys.should include('updated_at')
|
616
|
+
end
|
617
|
+
|
618
|
+
should "use default values if defined for keys" do
|
619
|
+
@document.key :active, Boolean, :default => true
|
620
|
+
|
621
|
+
@document.new.active.should be_true
|
622
|
+
@document.new(:active => false).active.should be_false
|
623
|
+
end
|
624
|
+
|
625
|
+
context "new?" do
|
626
|
+
should "be true if no id" do
|
627
|
+
@document.new.new?.should be(true)
|
628
|
+
end
|
629
|
+
|
630
|
+
should "be true if has id but id not in database" do
|
631
|
+
@document.new('_id' => 1).new?.should be(true)
|
632
|
+
end
|
633
|
+
|
634
|
+
should "be false if has id and id is in database" do
|
635
|
+
doc = @document.create(:name => 'John Nunemaker', :age => 27)
|
636
|
+
doc.new?.should be(false)
|
637
|
+
end
|
638
|
+
end
|
639
|
+
|
640
|
+
context "mass assigning keys" do
|
641
|
+
should "update values for keys provided" do
|
642
|
+
doc = @document.new(:name => 'foobar', :age => 10)
|
643
|
+
doc.attributes = {:name => 'new value', :age => 5}
|
644
|
+
doc.attributes[:name].should == 'new value'
|
645
|
+
doc.attributes[:age].should == 5
|
646
|
+
end
|
647
|
+
|
648
|
+
should "not update values for keys that were not provided" do
|
649
|
+
doc = @document.new(:name => 'foobar', :age => 10)
|
650
|
+
doc.attributes = {:name => 'new value'}
|
651
|
+
doc.attributes[:name].should == 'new value'
|
652
|
+
doc.attributes[:age].should == 10
|
653
|
+
end
|
654
|
+
|
655
|
+
should "ignore keys that do not exist" do
|
656
|
+
doc = @document.new(:name => 'foobar', :age => 10)
|
657
|
+
doc.attributes = {:name => 'new value', :foobar => 'baz'}
|
658
|
+
doc.attributes[:name].should == 'new value'
|
659
|
+
doc.attributes[:foobar].should be(nil)
|
660
|
+
end
|
661
|
+
|
662
|
+
should "typecast key values" do
|
663
|
+
doc = @document.new(:name => 1234, :age => '21')
|
664
|
+
doc.name.should == '1234'
|
665
|
+
doc.age.should == 21
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
context "requesting keys" do
|
670
|
+
should "default to empty hash" do
|
671
|
+
doc = @document.new
|
672
|
+
doc.attributes.should == {}
|
673
|
+
end
|
674
|
+
|
675
|
+
should "return all keys that aren't nil" do
|
676
|
+
doc = @document.new(:name => 'string', :age => nil)
|
677
|
+
doc.attributes.should == {'name' => 'string'}
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
context "key shorcuts" do
|
682
|
+
should "be able to read key with []" do
|
683
|
+
doc = @document.new(:name => 'string')
|
684
|
+
doc[:name].should == 'string'
|
685
|
+
end
|
686
|
+
|
687
|
+
should "be able to write key value with []=" do
|
688
|
+
doc = @document.new
|
689
|
+
doc[:name] = 'string'
|
690
|
+
doc[:name].should == 'string'
|
691
|
+
end
|
692
|
+
end
|
693
|
+
|
694
|
+
context "indifferent access" do
|
695
|
+
should "be enabled for keys" do
|
696
|
+
doc = @document.new(:name => 'string')
|
697
|
+
doc.attributes[:name].should == 'string'
|
698
|
+
doc.attributes['name'].should == 'string'
|
699
|
+
end
|
700
|
+
end
|
701
|
+
|
702
|
+
context "reading an attribute" do
|
703
|
+
should "work for defined keys" do
|
704
|
+
doc = @document.new(:name => 'string')
|
705
|
+
doc.name.should == 'string'
|
706
|
+
end
|
707
|
+
|
708
|
+
should "raise no method error for undefined keys" do
|
709
|
+
doc = @document.new
|
710
|
+
lambda { doc.fart }.should raise_error(NoMethodError)
|
711
|
+
end
|
712
|
+
|
713
|
+
should "know if reader defined" do
|
714
|
+
doc = @document.new
|
715
|
+
doc.reader?('name').should be(true)
|
716
|
+
doc.reader?(:name).should be(true)
|
717
|
+
doc.reader?('age').should be(true)
|
718
|
+
doc.reader?(:age).should be(true)
|
719
|
+
doc.reader?('foobar').should be(false)
|
720
|
+
doc.reader?(:foobar).should be(false)
|
721
|
+
end
|
722
|
+
|
723
|
+
should "be accessible for use in the model" do
|
724
|
+
@document.class_eval do
|
725
|
+
def name_and_age
|
726
|
+
"#{read_attribute(:name)} (#{read_attribute(:age)})"
|
727
|
+
end
|
728
|
+
end
|
729
|
+
|
730
|
+
doc = @document.new(:name => 'John', :age => 27)
|
731
|
+
doc.name_and_age.should == 'John (27)'
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
context "reading an attribute before typcasting" do
|
736
|
+
should "work for defined keys" do
|
737
|
+
doc = @document.new(:name => 12)
|
738
|
+
doc.name_before_typecast.should == 12
|
739
|
+
end
|
740
|
+
|
741
|
+
should "raise no method error for undefined keys" do
|
742
|
+
doc = @document.new
|
743
|
+
lambda { doc.foo_before_typecast }.should raise_error(NoMethodError)
|
744
|
+
end
|
745
|
+
|
746
|
+
should "be accessible for use in a document" do
|
747
|
+
@document.class_eval do
|
748
|
+
def untypcasted_name
|
749
|
+
read_attribute_before_typecast(:name)
|
750
|
+
end
|
751
|
+
end
|
752
|
+
|
753
|
+
doc = @document.new(:name => 12)
|
754
|
+
doc.name.should == '12'
|
755
|
+
doc.untypcasted_name.should == 12
|
756
|
+
end
|
757
|
+
end
|
758
|
+
|
759
|
+
context "writing an attribute" do
|
760
|
+
should "work for defined keys" do
|
761
|
+
doc = @document.new
|
762
|
+
doc.name = 'John'
|
763
|
+
doc.name.should == 'John'
|
764
|
+
end
|
765
|
+
|
766
|
+
should "raise no method error for undefined keys" do
|
767
|
+
doc = @document.new
|
768
|
+
lambda { doc.fart = 'poof!' }.should raise_error(NoMethodError)
|
769
|
+
end
|
770
|
+
|
771
|
+
should "typecast value" do
|
772
|
+
doc = @document.new
|
773
|
+
doc.name = 1234
|
774
|
+
doc.name.should == '1234'
|
775
|
+
doc.age = '21'
|
776
|
+
doc.age.should == 21
|
777
|
+
end
|
778
|
+
|
779
|
+
should "know if writer defined" do
|
780
|
+
doc = @document.new
|
781
|
+
doc.writer?('name').should be(true)
|
782
|
+
doc.writer?('name=').should be(true)
|
783
|
+
doc.writer?(:name).should be(true)
|
784
|
+
doc.writer?('age').should be(true)
|
785
|
+
doc.writer?('age=').should be(true)
|
786
|
+
doc.writer?(:age).should be(true)
|
787
|
+
doc.writer?('foobar').should be(false)
|
788
|
+
doc.writer?('foobar=').should be(false)
|
789
|
+
doc.writer?(:foobar).should be(false)
|
790
|
+
end
|
791
|
+
|
792
|
+
should "be accessible for use in the model" do
|
793
|
+
@document.class_eval do
|
794
|
+
def name_and_age=(new_value)
|
795
|
+
new_value.match(/([^\(\s]+) \((.*)\)/)
|
796
|
+
write_attribute :name, $1
|
797
|
+
write_attribute :age, $2
|
798
|
+
end
|
799
|
+
end
|
800
|
+
|
801
|
+
doc = @document.new
|
802
|
+
doc.name_and_age = 'Frank (62)'
|
803
|
+
doc.name.should == 'Frank'
|
804
|
+
doc.age.should == 62
|
805
|
+
end
|
806
|
+
end # writing an attribute
|
807
|
+
|
808
|
+
context "respond_to?" do
|
809
|
+
setup do
|
810
|
+
@doc = @document.new
|
811
|
+
end
|
812
|
+
|
813
|
+
should "work for readers" do
|
814
|
+
@doc.respond_to?(:name).should be_true
|
815
|
+
@doc.respond_to?('name').should be_true
|
816
|
+
end
|
817
|
+
|
818
|
+
should "work for writers" do
|
819
|
+
@doc.respond_to?(:name=).should be_true
|
820
|
+
@doc.respond_to?('name=').should be_true
|
821
|
+
end
|
822
|
+
|
823
|
+
should "work for readers before typecast" do
|
824
|
+
@doc.respond_to?(:name_before_typecast).should be_true
|
825
|
+
@doc.respond_to?('name_before_typecast').should be_true
|
826
|
+
end
|
827
|
+
end
|
828
|
+
|
829
|
+
context "equality" do
|
830
|
+
should "be equal if id and class are the same" do
|
831
|
+
(@document.new('_id' => 1) == @document.new('_id' => 1)).should be(true)
|
832
|
+
end
|
833
|
+
|
834
|
+
should "not be equal if class same but id different" do
|
835
|
+
(@document.new('_id' => 1) == @document.new('_id' => 2)).should be(false)
|
836
|
+
end
|
837
|
+
|
838
|
+
should "not be equal if id same but class different" do
|
839
|
+
@another_document = Class.new do
|
840
|
+
include MongoMapper::Document
|
841
|
+
end
|
842
|
+
|
843
|
+
(@document.new('_id' => 1) == @another_document.new('_id' => 1)).should be(false)
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
847
|
+
context "Saving a new document" do
|
848
|
+
setup do
|
849
|
+
@doc = @document.new(:name => 'John Nunemaker', :age => '27')
|
850
|
+
@doc.save
|
851
|
+
end
|
852
|
+
|
853
|
+
should "insert document into the collection" do
|
854
|
+
@document.count.should == 1
|
855
|
+
end
|
856
|
+
|
857
|
+
should "assign an id for the document" do
|
858
|
+
@doc.id.should_not be(nil)
|
859
|
+
@doc.id.size.should == 24
|
860
|
+
end
|
861
|
+
|
862
|
+
should "save attributes" do
|
863
|
+
@doc.name.should == 'John Nunemaker'
|
864
|
+
@doc.age.should == 27
|
865
|
+
end
|
866
|
+
|
867
|
+
should "update attributes in the database" do
|
868
|
+
from_db = @document.find(@doc.id)
|
869
|
+
from_db.should == @doc
|
870
|
+
from_db.name.should == 'John Nunemaker'
|
871
|
+
from_db.age.should == 27
|
872
|
+
end
|
873
|
+
end
|
874
|
+
|
875
|
+
context "Saving an existing document" do
|
876
|
+
setup do
|
877
|
+
@doc = @document.create(:name => 'John Nunemaker', :age => '27')
|
878
|
+
@doc.name = 'John Doe'
|
879
|
+
@doc.age = 30
|
880
|
+
@doc.save
|
881
|
+
end
|
882
|
+
|
883
|
+
should "not insert document into collection" do
|
884
|
+
@document.count.should == 1
|
885
|
+
end
|
886
|
+
|
887
|
+
should "update attributes" do
|
888
|
+
@doc.name.should == 'John Doe'
|
889
|
+
@doc.age.should == 30
|
890
|
+
end
|
891
|
+
|
892
|
+
should "update attributes in the database" do
|
893
|
+
from_db = @document.find(@doc.id)
|
894
|
+
from_db.name.should == 'John Doe'
|
895
|
+
from_db.age.should == 30
|
896
|
+
end
|
897
|
+
end
|
898
|
+
|
899
|
+
context "Calling update attributes on a new document" do
|
900
|
+
setup do
|
901
|
+
@doc = @document.new(:name => 'John Nunemaker', :age => '27')
|
902
|
+
@doc.update_attributes(:name => 'John Doe', :age => 30)
|
903
|
+
end
|
904
|
+
|
905
|
+
should "insert document into the collection" do
|
906
|
+
@document.count.should == 1
|
907
|
+
end
|
908
|
+
|
909
|
+
should "assign an id for the document" do
|
910
|
+
@doc.id.should_not be(nil)
|
911
|
+
@doc.id.size.should == 24
|
912
|
+
end
|
913
|
+
|
914
|
+
should "save attributes" do
|
915
|
+
@doc.name.should == 'John Doe'
|
916
|
+
@doc.age.should == 30
|
917
|
+
end
|
918
|
+
|
919
|
+
should "update attributes in the database" do
|
920
|
+
from_db = @document.find(@doc.id)
|
921
|
+
from_db.should == @doc
|
922
|
+
from_db.name.should == 'John Doe'
|
923
|
+
from_db.age.should == 30
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
927
|
+
context "Updating an existing document using update attributes" do
|
928
|
+
setup do
|
929
|
+
@doc = @document.create(:name => 'John Nunemaker', :age => '27')
|
930
|
+
@doc.update_attributes(:name => 'John Doe', :age => 30)
|
931
|
+
end
|
932
|
+
|
933
|
+
should "not insert document into collection" do
|
934
|
+
@document.count.should == 1
|
935
|
+
end
|
936
|
+
|
937
|
+
should "update attributes" do
|
938
|
+
@doc.name.should == 'John Doe'
|
939
|
+
@doc.age.should == 30
|
940
|
+
end
|
941
|
+
|
942
|
+
should "update attributes in the database" do
|
943
|
+
from_db = @document.find(@doc.id)
|
944
|
+
from_db.name.should == 'John Doe'
|
945
|
+
from_db.age.should == 30
|
946
|
+
end
|
947
|
+
end
|
948
|
+
|
949
|
+
context "Destroying a document that exists" do
|
950
|
+
setup do
|
951
|
+
@doc = @document.create(:name => 'John Nunemaker', :age => '27')
|
952
|
+
@doc.destroy
|
953
|
+
end
|
954
|
+
|
955
|
+
should "remove the document from the collection" do
|
956
|
+
@document.count.should == 0
|
957
|
+
end
|
958
|
+
|
959
|
+
should "raise error if assignment is attempted" do
|
960
|
+
lambda { @doc.name = 'Foo' }.should raise_error(TypeError)
|
961
|
+
end
|
962
|
+
end
|
963
|
+
|
964
|
+
context "Destroying a document that is a new" do
|
965
|
+
setup do
|
966
|
+
setup do
|
967
|
+
@doc = @document.new(:name => 'John Nunemaker', :age => '27')
|
968
|
+
@doc.destroy
|
969
|
+
end
|
970
|
+
|
971
|
+
should "not affect collection count" do
|
972
|
+
@document.collection.count.should == 0
|
973
|
+
end
|
974
|
+
|
975
|
+
should "raise error if assignment is attempted" do
|
976
|
+
lambda { @doc.name = 'Foo' }.should raise_error(TypeError)
|
977
|
+
end
|
978
|
+
end
|
979
|
+
end
|
980
|
+
|
981
|
+
context "timestamping" do
|
982
|
+
should "set created_at and updated_at on create" do
|
983
|
+
doc = @document.new(:name => 'John Nunemaker', :age => 27)
|
984
|
+
doc.created_at.should be(nil)
|
985
|
+
doc.updated_at.should be(nil)
|
986
|
+
doc.save
|
987
|
+
doc.created_at.should_not be(nil)
|
988
|
+
doc.updated_at.should_not be(nil)
|
989
|
+
end
|
990
|
+
|
991
|
+
should "set updated_at on update but leave created_at alone" do
|
992
|
+
doc = @document.create(:name => 'John Nunemaker', :age => 27)
|
993
|
+
old_created_at = doc.created_at
|
994
|
+
old_updated_at = doc.updated_at
|
995
|
+
doc.name = 'John Doe'
|
996
|
+
doc.save
|
997
|
+
doc.created_at.should == old_created_at
|
998
|
+
doc.updated_at.should_not == old_updated_at
|
999
|
+
end
|
1000
|
+
end
|
1001
|
+
end # instance of a document
|
1002
|
+
end # DocumentTest
|