mrkurt-mongo_mapper 0.6.8

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 (77) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +38 -0
  4. data/Rakefile +55 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +60 -0
  7. data/lib/mongo_mapper.rb +139 -0
  8. data/lib/mongo_mapper/associations.rb +72 -0
  9. data/lib/mongo_mapper/associations/base.rb +113 -0
  10. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +26 -0
  11. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +21 -0
  12. data/lib/mongo_mapper/associations/collection.rb +19 -0
  13. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +26 -0
  14. data/lib/mongo_mapper/associations/many_documents_proxy.rb +115 -0
  15. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +31 -0
  16. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +54 -0
  17. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
  18. data/lib/mongo_mapper/associations/one_proxy.rb +61 -0
  19. data/lib/mongo_mapper/associations/proxy.rb +111 -0
  20. data/lib/mongo_mapper/callbacks.rb +61 -0
  21. data/lib/mongo_mapper/dirty.rb +117 -0
  22. data/lib/mongo_mapper/document.rb +496 -0
  23. data/lib/mongo_mapper/dynamic_finder.rb +74 -0
  24. data/lib/mongo_mapper/embedded_document.rb +380 -0
  25. data/lib/mongo_mapper/finder_options.rb +145 -0
  26. data/lib/mongo_mapper/key.rb +36 -0
  27. data/lib/mongo_mapper/mongo_mapper.rb +125 -0
  28. data/lib/mongo_mapper/pagination.rb +66 -0
  29. data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
  30. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +28 -0
  31. data/lib/mongo_mapper/serialization.rb +54 -0
  32. data/lib/mongo_mapper/serializers/json_serializer.rb +48 -0
  33. data/lib/mongo_mapper/support.rb +192 -0
  34. data/lib/mongo_mapper/validations.rb +39 -0
  35. data/mongo_mapper.gemspec +173 -0
  36. data/specs.watchr +30 -0
  37. data/test/NOTE_ON_TESTING +1 -0
  38. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
  39. data/test/functional/associations/test_belongs_to_proxy.rb +91 -0
  40. data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
  41. data/test/functional/associations/test_many_documents_proxy.rb +477 -0
  42. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +156 -0
  43. data/test/functional/associations/test_many_embedded_proxy.rb +192 -0
  44. data/test/functional/associations/test_many_polymorphic_proxy.rb +339 -0
  45. data/test/functional/associations/test_one_proxy.rb +131 -0
  46. data/test/functional/test_associations.rb +44 -0
  47. data/test/functional/test_binary.rb +33 -0
  48. data/test/functional/test_callbacks.rb +85 -0
  49. data/test/functional/test_dirty.rb +159 -0
  50. data/test/functional/test_document.rb +1198 -0
  51. data/test/functional/test_embedded_document.rb +135 -0
  52. data/test/functional/test_logger.rb +20 -0
  53. data/test/functional/test_modifiers.rb +242 -0
  54. data/test/functional/test_pagination.rb +95 -0
  55. data/test/functional/test_rails_compatibility.rb +25 -0
  56. data/test/functional/test_string_id_compatibility.rb +72 -0
  57. data/test/functional/test_validations.rb +361 -0
  58. data/test/models.rb +271 -0
  59. data/test/support/custom_matchers.rb +55 -0
  60. data/test/support/timing.rb +16 -0
  61. data/test/test_helper.rb +27 -0
  62. data/test/unit/associations/test_base.rb +182 -0
  63. data/test/unit/associations/test_proxy.rb +91 -0
  64. data/test/unit/serializers/test_json_serializer.rb +189 -0
  65. data/test/unit/test_document.rb +236 -0
  66. data/test/unit/test_dynamic_finder.rb +125 -0
  67. data/test/unit/test_embedded_document.rb +709 -0
  68. data/test/unit/test_finder_options.rb +325 -0
  69. data/test/unit/test_key.rb +172 -0
  70. data/test/unit/test_mongo_mapper.rb +65 -0
  71. data/test/unit/test_pagination.rb +119 -0
  72. data/test/unit/test_rails_compatibility.rb +52 -0
  73. data/test/unit/test_serializations.rb +52 -0
  74. data/test/unit/test_support.rb +346 -0
  75. data/test/unit/test_time_zones.rb +40 -0
  76. data/test/unit/test_validations.rb +503 -0
  77. metadata +239 -0
@@ -0,0 +1,1198 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class DocumentTest < Test::Unit::TestCase
5
+ def setup
6
+ @document = Class.new do
7
+ include MongoMapper::Document
8
+ set_collection_name 'users'
9
+
10
+ key :first_name, String
11
+ key :last_name, String
12
+ key :age, Integer
13
+ key :date, Date
14
+ end
15
+ @document.collection.remove
16
+ end
17
+
18
+ context "Using key with type Array" do
19
+ setup do
20
+ @document.key :tags, Array
21
+ end
22
+
23
+ should "give correct default" do
24
+ doc = @document.new
25
+ doc.tags.should == []
26
+ end
27
+
28
+ should "work with assignment" do
29
+ doc = @document.new
30
+ doc.tags = %w(foo bar)
31
+ doc.tags.should == %w(foo bar)
32
+ end
33
+
34
+ should "work with assignment after saving" do
35
+ doc = @document.new
36
+ doc.tags = %w(foo bar)
37
+ doc.save
38
+ doc.tags.should == %w(foo bar)
39
+ doc.reload.tags.should == %w(foo bar)
40
+ end
41
+
42
+ should "work with assignment then <<" do
43
+ doc = @document.new
44
+ doc.tags = []
45
+ doc.tags << "foo"
46
+ doc.tags.should == ["foo"]
47
+ end
48
+
49
+ should "work with <<" do
50
+ doc = @document.new
51
+ doc.tags << "foo"
52
+ doc.tags.should == ["foo"]
53
+ end
54
+
55
+ should "work with << then save" do
56
+ doc = @document.new
57
+ doc.tags << "foo"
58
+ doc.tags << "bar"
59
+ doc.save
60
+ doc.tags.should == %w(foo bar)
61
+ doc.reload.tags.should == %w(foo bar)
62
+ end
63
+ end
64
+
65
+ context "Using key with type Hash" do
66
+ setup do
67
+ @document.key :foo, Hash
68
+ end
69
+
70
+ should "give correct default" do
71
+ doc = @document.new
72
+ doc.foo.should == {}
73
+ end
74
+
75
+ should "work with []=" do
76
+ doc = @document.new
77
+ doc.foo["quux"] = "bar"
78
+ doc.foo["quux"].should == "bar"
79
+ doc.foo.should == { "quux" => "bar" }
80
+ end
81
+
82
+ should "work with indifferent access" do
83
+ doc = @document.new
84
+ doc.foo = {:baz => 'bar'}
85
+ doc.foo[:baz].should == 'bar'
86
+ doc.foo['baz'].should == 'bar'
87
+ end
88
+
89
+ should "work with indifferent access after save" do
90
+ doc = @document.new
91
+ doc.foo = {:baz => 'bar'}
92
+ doc.save
93
+
94
+ doc = doc.reload
95
+ doc.foo[:baz].should == 'bar'
96
+ doc.foo['baz'].should == 'bar'
97
+ end
98
+ end
99
+
100
+ context "Using key with custom type with default" do
101
+ setup do
102
+ @document.key :window, WindowSize, :default => WindowSize.new(600, 480)
103
+ end
104
+
105
+ should "default to default" do
106
+ doc = @document.new
107
+ doc.window.should == WindowSize.new(600, 480)
108
+
109
+ end
110
+
111
+ should "save and load from mongo" do
112
+ doc = @document.new
113
+ doc.save
114
+
115
+ doc = doc.reload
116
+ doc.window.should == WindowSize.new(600, 480)
117
+ end
118
+ end
119
+
120
+ context "ClassMethods#create (single document)" do
121
+ setup do
122
+ @doc_instance = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
123
+ end
124
+
125
+ should "create a document in correct collection" do
126
+ @document.count.should == 1
127
+ end
128
+
129
+ should "automatically set id" do
130
+ @doc_instance.id.should be_instance_of(Mongo::ObjectID)
131
+ @doc_instance._id.should be_instance_of(Mongo::ObjectID)
132
+ end
133
+
134
+ should "no longer be new?" do
135
+ @doc_instance.new?.should be_false
136
+ end
137
+
138
+ should "return instance of document" do
139
+ @doc_instance.should be_instance_of(@document)
140
+ @doc_instance.first_name.should == 'John'
141
+ @doc_instance.last_name.should == 'Nunemaker'
142
+ @doc_instance.age.should == 27
143
+ end
144
+
145
+ should "not fail if no attributes provided" do
146
+ document = Class.new do
147
+ include MongoMapper::Document
148
+ set_collection_name 'test'
149
+ end
150
+ document.collection.remove
151
+
152
+ lambda { document.create }.should change { document.count }.by(1)
153
+ end
154
+ end
155
+
156
+ context "ClassMethods#create (multiple documents)" do
157
+ setup do
158
+ @doc_instances = @document.create([
159
+ {:first_name => 'John', :last_name => 'Nunemaker', :age => '27'},
160
+ {:first_name => 'Steve', :last_name => 'Smith', :age => '28'},
161
+ ])
162
+ end
163
+
164
+ should "create multiple documents" do
165
+ @document.count.should == 2
166
+ end
167
+
168
+ should "return an array of doc instances" do
169
+ @doc_instances.map do |doc_instance|
170
+ doc_instance.should be_instance_of(@document)
171
+ end
172
+ end
173
+ end
174
+
175
+ context "ClassMethods#update (single document)" do
176
+ setup do
177
+ doc = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
178
+ @doc_instance = @document.update(doc._id, {:age => 40})
179
+ end
180
+
181
+ should "update attributes provided" do
182
+ @doc_instance.age.should == 40
183
+ end
184
+
185
+ should "not update existing attributes that were not set to update" do
186
+ @doc_instance.first_name.should == 'John'
187
+ @doc_instance.last_name.should == 'Nunemaker'
188
+ end
189
+
190
+ should "not create new document" do
191
+ @document.count.should == 1
192
+ end
193
+
194
+ should "raise error if not provided id" do
195
+ doc = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
196
+ lambda { @document.update }.should raise_error(ArgumentError)
197
+ end
198
+
199
+ should "raise error if not provided attributes" do
200
+ doc = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
201
+ lambda { @document.update(doc._id) }.should raise_error(ArgumentError)
202
+ lambda { @document.update(doc._id, [1]) }.should raise_error(ArgumentError)
203
+ end
204
+ end
205
+
206
+ context "ClassMethods#update (multiple documents)" do
207
+ setup do
208
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
209
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
210
+
211
+ @doc_instances = @document.update({
212
+ @doc1._id => {:age => 30},
213
+ @doc2._id => {:age => 30},
214
+ })
215
+ end
216
+
217
+ should "not create any new documents" do
218
+ @document.count.should == 2
219
+ end
220
+
221
+ should "should return an array of doc instances" do
222
+ @doc_instances.map do |doc_instance|
223
+ doc_instance.should be_instance_of(@document)
224
+ end
225
+ end
226
+
227
+ should "update the documents" do
228
+ @document.find(@doc1._id).age.should == 30
229
+ @document.find(@doc2._id).age.should == 30
230
+ end
231
+
232
+ should "raise error if not a hash" do
233
+ lambda { @document.update([1, 2]) }.should raise_error(ArgumentError)
234
+ end
235
+ end
236
+
237
+ context "ClassMethods#find" do
238
+ setup do
239
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
240
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
241
+ @doc3 = @document.create({:first_name => 'Steph', :last_name => 'Nunemaker', :age => '26'})
242
+ end
243
+
244
+ should "return nil if nothing provided for find" do
245
+ @document.find.should be_nil
246
+ end
247
+
248
+ should "raise document not found if nothing provided for find!" do
249
+ lambda { @document.find! }.should raise_error(MongoMapper::DocumentNotFound)
250
+ end
251
+
252
+ context "(with a single id)" do
253
+ should "work" do
254
+ @document.find(@doc1._id).should == @doc1
255
+ end
256
+
257
+ should "return nil if document not found with find" do
258
+ @document.find(123).should be_nil
259
+ end
260
+
261
+ should "raise error if document not found with find!" do
262
+ lambda {
263
+ @document.find!(123)
264
+ }.should raise_error(MongoMapper::DocumentNotFound)
265
+ end
266
+ end
267
+
268
+ context "(with multiple id's)" do
269
+ should "work as arguments" do
270
+ @document.find(@doc1._id, @doc2._id).should == [@doc1, @doc2]
271
+ end
272
+
273
+ should "work as array" do
274
+ @document.find([@doc1._id, @doc2._id]).should == [@doc1, @doc2]
275
+ end
276
+
277
+ should "return array if array only has one element" do
278
+ @document.find([@doc1._id]).should == [@doc1]
279
+ end
280
+ end
281
+
282
+ should "be able to find using condition auto-detection" do
283
+ @document.first(:first_name => 'John').should == @doc1
284
+ @document.all(:last_name => 'Nunemaker', :order => 'age desc').should == [@doc1, @doc3]
285
+ end
286
+
287
+ context "(with :all)" do
288
+ should "find all documents" do
289
+ @document.find(:all, :order => 'first_name').should == [@doc1, @doc3, @doc2]
290
+ end
291
+
292
+ should "be able to add conditions" do
293
+ @document.find(:all, :first_name => 'John').should == [@doc1]
294
+ end
295
+ end
296
+
297
+ context "(with #all)" do
298
+ should "find all documents based on criteria" do
299
+ @document.all(:order => 'first_name').should == [@doc1, @doc3, @doc2]
300
+ @document.all(:last_name => 'Nunemaker', :order => 'age desc').should == [@doc1, @doc3]
301
+ end
302
+ end
303
+
304
+ context "(with :first)" do
305
+ should "find first document" do
306
+ @document.find(:first, :order => 'first_name').should == @doc1
307
+ end
308
+ end
309
+
310
+ context "(with #first)" do
311
+ should "find first document based on criteria" do
312
+ @document.first(:order => 'first_name').should == @doc1
313
+ @document.first(:age => 28).should == @doc2
314
+ end
315
+ end
316
+
317
+ context "(with :last)" do
318
+ should "find last document" do
319
+ @document.find(:last, :order => 'age').should == @doc2
320
+ end
321
+ end
322
+
323
+ context "(with #last)" do
324
+ should "find last document based on criteria" do
325
+ @document.last(:order => 'age').should == @doc2
326
+ @document.last(:order => 'age', :age => 28).should == @doc2
327
+ end
328
+
329
+ should "raise error if no order provided" do
330
+ lambda { @document.last() }.should raise_error
331
+ end
332
+ end
333
+
334
+ context "(with :find_by)" do
335
+ should "find document based on argument" do
336
+ @document.find_by_first_name('John').should == @doc1
337
+ @document.find_by_last_name('Nunemaker', :order => 'age desc').should == @doc1
338
+ @document.find_by_age(27).should == @doc1
339
+ end
340
+
341
+ should "not raise error" do
342
+ @document.find_by_first_name('Mongo').should be_nil
343
+ end
344
+
345
+ should "define a method for each key" do
346
+ @document.methods(false).select { |e| e =~ /^find_by_/ }.size == @document.keys.size
347
+ end
348
+ end
349
+
350
+ context "(with dynamic finders)" do
351
+ should "find document based on all arguments" do
352
+ @document.find_by_first_name_and_last_name_and_age('John', 'Nunemaker', 27).should == @doc1
353
+ end
354
+
355
+ should "not find the document if an argument is wrong" do
356
+ @document.find_by_first_name_and_last_name_and_age('John', 'Nunemaker', 28).should be_nil
357
+ end
358
+
359
+ should "find all documents based on arguments" do
360
+ docs = @document.find_all_by_last_name('Nunemaker')
361
+ docs.should be_kind_of(Array)
362
+ docs.should include(@doc1)
363
+ docs.should include(@doc3)
364
+ end
365
+
366
+ should "initialize document with given arguments" do
367
+ doc = @document.find_or_initialize_by_first_name_and_last_name('David', 'Cuadrado')
368
+ doc.should be_new
369
+ doc.first_name.should == 'David'
370
+ end
371
+
372
+ should "not initialize document if document is found" do
373
+ doc = @document.find_or_initialize_by_first_name('John')
374
+ doc.should_not be_new
375
+ end
376
+
377
+ should "create document with given arguments" do
378
+ doc = @document.find_or_create_by_first_name_and_last_name('David', 'Cuadrado')
379
+ doc.should_not be_new
380
+ doc.first_name.should == 'David'
381
+ end
382
+
383
+ should "raise error if document is not found when using !" do
384
+ lambda {
385
+ @document.find_by_first_name_and_last_name!(1,2)
386
+ }.should raise_error(MongoMapper::DocumentNotFound)
387
+ end
388
+ end
389
+ end # finding documents
390
+
391
+ context "ClassMethods#find_by_id" do
392
+ setup do
393
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
394
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
395
+ end
396
+
397
+ should "be able to find by id" do
398
+ @document.find_by_id(@doc1._id).should == @doc1
399
+ @document.find_by_id(@doc2._id).should == @doc2
400
+ end
401
+
402
+ should "return nil if document not found" do
403
+ @document.find_by_id(1234).should be(nil)
404
+ end
405
+ end
406
+
407
+ context "ClassMethods#delete (single document)" do
408
+ setup do
409
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
410
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
411
+ @document.delete(@doc1._id)
412
+ end
413
+
414
+ should "remove document from collection" do
415
+ @document.count.should == 1
416
+ end
417
+
418
+ should "not remove other documents" do
419
+ @document.find(@doc2._id).should_not be(nil)
420
+ end
421
+ end
422
+
423
+ context "ClassMethods#delete (multiple documents)" do
424
+ should "work with multiple arguments" do
425
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
426
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
427
+ @doc3 = @document.create({:first_name => 'Steph', :last_name => 'Nunemaker', :age => '26'})
428
+ @document.delete(@doc1._id, @doc2._id)
429
+
430
+ @document.count.should == 1
431
+ end
432
+
433
+ should "work with array as argument" do
434
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
435
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
436
+ @doc3 = @document.create({:first_name => 'Steph', :last_name => 'Nunemaker', :age => '26'})
437
+ @document.delete([@doc1._id, @doc2._id])
438
+
439
+ @document.count.should == 1
440
+ end
441
+ end
442
+
443
+ context "ClassMethods#delete_all" do
444
+ setup do
445
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
446
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
447
+ @doc3 = @document.create({:first_name => 'Steph', :last_name => 'Nunemaker', :age => '26'})
448
+ end
449
+
450
+ should "remove all documents when given no conditions" do
451
+ @document.delete_all
452
+ @document.count.should == 0
453
+ end
454
+
455
+ should "only remove matching documents when given conditions" do
456
+ @document.delete_all({:first_name => 'John'})
457
+ @document.count.should == 2
458
+ end
459
+
460
+ should "convert the conditions to mongo criteria" do
461
+ @document.delete_all(:age => [26, 27])
462
+ @document.count.should == 1
463
+ end
464
+ end
465
+
466
+ context "ClassMethods#destroy (single document)" do
467
+ setup do
468
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
469
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
470
+ @document.destroy(@doc1._id)
471
+ end
472
+
473
+ should "remove document from collection" do
474
+ @document.count.should == 1
475
+ end
476
+
477
+ should "not remove other documents" do
478
+ @document.find(@doc2._id).should_not be(nil)
479
+ end
480
+ end
481
+
482
+ context "ClassMethods#destroy (multiple documents)" do
483
+ should "work with multiple arguments" do
484
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
485
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
486
+ @doc3 = @document.create({:first_name => 'Steph', :last_name => 'Nunemaker', :age => '26'})
487
+ @document.destroy(@doc1._id, @doc2._id)
488
+
489
+ @document.count.should == 1
490
+ end
491
+
492
+ should "work with array as argument" do
493
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
494
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
495
+ @doc3 = @document.create({:first_name => 'Steph', :last_name => 'Nunemaker', :age => '26'})
496
+ @document.destroy([@doc1._id, @doc2._id])
497
+
498
+ @document.count.should == 1
499
+ end
500
+ end
501
+
502
+ context "ClassMethods#destroy_all" do
503
+ setup do
504
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
505
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
506
+ @doc3 = @document.create({:first_name => 'Steph', :last_name => 'Nunemaker', :age => '26'})
507
+ end
508
+
509
+ should "remove all documents when given no conditions" do
510
+ @document.destroy_all
511
+ @document.count.should == 0
512
+ end
513
+
514
+ should "only remove matching documents when given conditions" do
515
+ @document.destroy_all(:first_name => 'John')
516
+ @document.count.should == 2
517
+ @document.destroy_all(:age => 26)
518
+ @document.count.should == 1
519
+ end
520
+
521
+ should "convert the conditions to mongo criteria" do
522
+ @document.destroy_all(:age => [26, 27])
523
+ @document.count.should == 1
524
+ end
525
+ end
526
+
527
+ context "ClassMethods#count" do
528
+ setup do
529
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
530
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
531
+ @doc3 = @document.create({:first_name => 'Steph', :last_name => 'Nunemaker', :age => '26'})
532
+ end
533
+
534
+ should "count all with no arguments" do
535
+ @document.count.should == 3
536
+ end
537
+
538
+ should "return 0 if there are no documents in the collection" do
539
+ @document.delete_all
540
+ @document.count.should == 0
541
+ end
542
+
543
+ should "return 0 if the collection does not exist" do
544
+ klass = Class.new do
545
+ include MongoMapper::Document
546
+ set_collection_name 'foobarbazwickdoesnotexist'
547
+ end
548
+ @document.collection.remove
549
+
550
+ klass.count.should == 0
551
+ end
552
+
553
+ should "return count for matching documents if conditions provided" do
554
+ @document.count(:age => 27).should == 1
555
+ end
556
+
557
+ should "convert the conditions to mongo criteria" do
558
+ @document.count(:age => [26, 27]).should == 2
559
+ end
560
+ end
561
+
562
+ should "have instance method for collection" do
563
+ @document.new.collection.name.should == @document.collection.name
564
+ end
565
+
566
+ should "have instance method for database" do
567
+ @document.new.database.should == @document.database
568
+ end
569
+
570
+ context "#save (new document)" do
571
+ setup do
572
+ @doc = @document.new(:first_name => 'John', :age => '27')
573
+ @doc.save
574
+ end
575
+
576
+ should "insert document into the collection" do
577
+ @document.count.should == 1
578
+ end
579
+
580
+ should "assign an id for the document" do
581
+ @doc.id.should be_instance_of(Mongo::ObjectID)
582
+ end
583
+
584
+ should "save attributes" do
585
+ @doc.first_name.should == 'John'
586
+ @doc.age.should == 27
587
+ end
588
+
589
+ should "update attributes in the database" do
590
+ doc = @doc.reload
591
+ doc.should == @doc
592
+ doc.first_name.should == 'John'
593
+ doc.age.should == 27
594
+ end
595
+
596
+ should "allow to add custom attributes to the document" do
597
+ @doc = @document.new(:first_name => 'David', :age => '26', :gender => 'male', :tags => [1, "2"])
598
+ @doc.save
599
+ doc = @doc.reload
600
+ doc.gender.should == 'male'
601
+ doc.tags.should == [1, "2"]
602
+ end
603
+
604
+ should "allow to use custom methods to assign properties" do
605
+ person = RealPerson.new(:realname => 'David')
606
+ person.save
607
+ person.reload.name.should == 'David'
608
+ end
609
+
610
+ context "with key of type date" do
611
+ should "save the date value as a Time object" do
612
+ doc = @document.new(:first_name => 'John', :age => '27', :date => "12/01/2009")
613
+ doc.save
614
+ doc.date.should == Date.new(2009, 12, 1)
615
+ end
616
+ end
617
+ end
618
+
619
+ context "#save (existing document)" do
620
+ setup do
621
+ @doc = @document.create(:first_name => 'John', :age => '27')
622
+ @doc.first_name = 'Johnny'
623
+ @doc.age = 30
624
+ @doc.save
625
+ end
626
+
627
+ should "not insert document into collection" do
628
+ @document.count.should == 1
629
+ end
630
+
631
+ should "update attributes" do
632
+ @doc.first_name.should == 'Johnny'
633
+ @doc.age.should == 30
634
+ end
635
+
636
+ should "update attributes in the database" do
637
+ doc = @doc.reload
638
+ doc.first_name.should == 'Johnny'
639
+ doc.age.should == 30
640
+ end
641
+
642
+ should "allow updating custom attributes" do
643
+ @doc = @document.new(:first_name => 'David', :age => '26', :gender => 'male')
644
+ @doc.gender = 'Male'
645
+ @doc.save
646
+ @doc.reload.gender.should == 'Male'
647
+ end
648
+
649
+ should "fail if wrong version (and locking enabled)" do
650
+ @document.locking!
651
+ doc = @document.create(:first_name => 'Blah')
652
+ doc2 = @document.find(doc.id)
653
+ doc.first_name = 'Bloop'
654
+ doc.save
655
+
656
+ doc2.first_name = 'Blip'
657
+
658
+ lambda{ doc2.save }.should raise_error(MongoMapper::StaleDocumentError)
659
+ doc.reload.first_name.should == 'Bloop'
660
+ end
661
+ end
662
+
663
+ context "#update_attributes (new document)" do
664
+ setup do
665
+ @doc = @document.new(:first_name => 'John', :age => '27')
666
+ @doc.update_attributes(:first_name => 'Johnny', :age => 30)
667
+ end
668
+
669
+ should "insert document into the collection" do
670
+ @document.count.should == 1
671
+ end
672
+
673
+ should "assign an id for the document" do
674
+ @doc.id.should be_instance_of(Mongo::ObjectID)
675
+ end
676
+
677
+ should "save attributes" do
678
+ @doc.first_name.should == 'Johnny'
679
+ @doc.age.should == 30
680
+ end
681
+
682
+ should "update attributes in the database" do
683
+ doc = @doc.reload
684
+ doc.should == @doc
685
+ doc.first_name.should == 'Johnny'
686
+ doc.age.should == 30
687
+ end
688
+
689
+ should "allow updating custom attributes" do
690
+ @doc.update_attributes(:gender => 'mALe')
691
+ @doc.reload.gender.should == 'mALe'
692
+ end
693
+ end
694
+
695
+ context "#update_attributes (existing document)" do
696
+ setup do
697
+ @doc = @document.create(:first_name => 'John', :age => '27')
698
+ @doc.update_attributes(:first_name => 'Johnny', :age => 30)
699
+ end
700
+
701
+ should "not insert document into collection" do
702
+ @document.count.should == 1
703
+ end
704
+
705
+ should "update attributes" do
706
+ @doc.first_name.should == 'Johnny'
707
+ @doc.age.should == 30
708
+ end
709
+
710
+ should "update attributes in the database" do
711
+ doc = @doc.reload
712
+ doc.first_name.should == 'Johnny'
713
+ doc.age.should == 30
714
+ end
715
+ end
716
+
717
+ context "#update_attributes" do
718
+ setup do
719
+ @document.key :foo, String, :required => true
720
+ end
721
+
722
+ should "return true if document valid" do
723
+ @document.new.update_attributes(:foo => 'bar').should be_true
724
+ end
725
+
726
+ should "return false if document not valid" do
727
+ @document.new.update_attributes({}).should be_false
728
+ end
729
+ end
730
+
731
+ context "#save (with validations off)" do
732
+ setup do
733
+ @document = Class.new do
734
+ include MongoMapper::Document
735
+ set_collection_name 'test'
736
+ key :name, String, :required => true
737
+ end
738
+ @document.collection.remove
739
+ end
740
+
741
+ should "insert document" do
742
+ doc = @document.new
743
+ doc.save(:validate => false)
744
+ @document.count.should == 1
745
+ end
746
+
747
+ should "work with false passed to save" do
748
+ doc = @document.new
749
+ doc.save(false)
750
+ @document.count.should == 1
751
+ end
752
+ end
753
+
754
+ context "#save (with options)" do
755
+ setup do
756
+ MongoMapper.ensured_indexes = []
757
+
758
+ @document = Class.new do
759
+ include MongoMapper::Document
760
+ set_collection_name 'test'
761
+ key :name, String
762
+ ensure_index :name, :unique => true
763
+ end
764
+ @document.collection.drop_indexes
765
+
766
+ MongoMapper.ensure_indexes!
767
+ end
768
+
769
+ should "allow passing safe" do
770
+ doc = @document.new(:name => 'John')
771
+ doc.save
772
+
773
+ assert_raises(Mongo::OperationFailure) do
774
+ @document.new(:name => 'John').save(:safe => true)
775
+ end
776
+ end
777
+ end
778
+
779
+ context "#destroy" do
780
+ setup do
781
+ @doc = @document.create(:first_name => 'John', :age => '27')
782
+ @doc.destroy
783
+ end
784
+
785
+ should "remove the document from the collection" do
786
+ @document.count.should == 0
787
+ end
788
+ end
789
+
790
+ context "#delete" do
791
+ setup do
792
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
793
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
794
+
795
+ @document.class_eval do
796
+ before_destroy :before_destroy_callback
797
+ after_destroy :after_destroy_callback
798
+
799
+ def history; @history ||= [] end
800
+ def before_destroy_callback; history << :after_destroy end
801
+ def after_destroy_callback; history << :after_destroy end
802
+ end
803
+
804
+ @doc1.delete
805
+ end
806
+
807
+ should "remove document from collection" do
808
+ @document.count.should == 1
809
+ end
810
+
811
+ should "not remove other documents" do
812
+ @document.find(@doc2.id).should_not be(nil)
813
+ end
814
+
815
+ should "not call before/after destroy callbacks" do
816
+ @doc1.history.should == []
817
+ end
818
+ end
819
+
820
+ context "Single collection inheritance" do
821
+ setup do
822
+ class ::DocParent
823
+ include MongoMapper::Document
824
+ key :_type, String
825
+ key :name, String
826
+ end
827
+ DocParent.collection.remove
828
+
829
+ class ::DocDaughter < ::DocParent; end
830
+ class ::DocSon < ::DocParent; end
831
+ class ::DocGrandSon < ::DocSon; end
832
+
833
+ DocSon.many :children, :class_name => 'DocGrandSon'
834
+
835
+ @parent = DocParent.new({:name => "Daddy Warbucks"})
836
+ @daughter = DocDaughter.new({:name => "Little Orphan Annie"})
837
+ end
838
+
839
+ teardown do
840
+ Object.send :remove_const, 'DocParent' if defined?(::DocParent)
841
+ Object.send :remove_const, 'DocDaughter' if defined?(::DocDaughter)
842
+ Object.send :remove_const, 'DocSon' if defined?(::DocSon)
843
+ Object.send :remove_const, 'DocGrandSon' if defined?(::DocGrandSon)
844
+ end
845
+
846
+ should "use the same collection in the subclass" do
847
+ DocDaughter.collection.name.should == DocParent.collection.name
848
+ end
849
+
850
+ should "assign the class name into the _type property" do
851
+ @parent._type.should == 'DocParent'
852
+ @daughter._type.should == 'DocDaughter'
853
+ end
854
+
855
+ should "load the document with the assigned type" do
856
+ @parent.save
857
+ @daughter.save
858
+
859
+ collection = DocParent.find(:all)
860
+ collection.size.should == 2
861
+ collection.first.should be_kind_of(DocParent)
862
+ collection.first.name.should == "Daddy Warbucks"
863
+ collection.last.should be_kind_of(DocDaughter)
864
+ collection.last.name.should == "Little Orphan Annie"
865
+ end
866
+
867
+ should "gracefully handle when the type can't be constantized" do
868
+ doc = DocParent.new(:name => 'Nunes')
869
+ doc._type = 'FoobarBaz'
870
+ doc.save
871
+
872
+ collection = DocParent.all
873
+ collection.last.should == doc
874
+ collection.last.should be_kind_of(DocParent)
875
+ end
876
+
877
+ should "find scoped to class" do
878
+ john = DocSon.create(:name => 'John')
879
+ steve = DocSon.create(:name => 'Steve')
880
+ steph = DocDaughter.create(:name => 'Steph')
881
+ carrie = DocDaughter.create(:name => 'Carrie')
882
+
883
+ DocGrandSon.all(:order => 'name').should == []
884
+ DocSon.all(:order => 'name').should == [john, steve]
885
+ DocDaughter.all(:order => 'name').should == [carrie, steph]
886
+ DocParent.all(:order => 'name').should == [carrie, john, steph, steve]
887
+ end
888
+
889
+ should "work with nested hash conditions" do
890
+ john = DocSon.create(:name => 'John')
891
+ steve = DocSon.create(:name => 'Steve')
892
+ DocSon.all(:name => {'$ne' => 'Steve'}).should == [john]
893
+ end
894
+
895
+ should "raise error if not found scoped to class" do
896
+ john = DocSon.create(:name => 'John')
897
+ steph = DocDaughter.create(:name => 'Steph')
898
+
899
+ lambda {
900
+ DocSon.find!(steph._id)
901
+ }.should raise_error(MongoMapper::DocumentNotFound)
902
+ end
903
+
904
+ should "not raise error for find with parent" do
905
+ john = DocSon.create(:name => 'John')
906
+
907
+ DocParent.find!(john._id).should == john
908
+ end
909
+
910
+ should "count scoped to class" do
911
+ john = DocSon.create(:name => 'John')
912
+ steve = DocSon.create(:name => 'Steve')
913
+ steph = DocDaughter.create(:name => 'Steph')
914
+ carrie = DocDaughter.create(:name => 'Carrie')
915
+
916
+ DocGrandSon.count.should == 0
917
+ DocSon.count.should == 2
918
+ DocDaughter.count.should == 2
919
+ DocParent.count.should == 4
920
+ end
921
+
922
+ should "know if it is single_collection_inherited?" do
923
+ DocParent.single_collection_inherited?.should be_false
924
+
925
+ DocDaughter.single_collection_inherited?.should be_true
926
+ DocSon.single_collection_inherited?.should be_true
927
+ end
928
+
929
+ should "know if single_collection_inherited_superclass?" do
930
+ DocParent.single_collection_inherited_superclass?.should be_false
931
+
932
+ DocDaughter.single_collection_inherited_superclass?.should be_true
933
+ DocSon.single_collection_inherited_superclass?.should be_true
934
+ DocGrandSon.single_collection_inherited_superclass?.should be_true
935
+ end
936
+
937
+ should "not be able to destroy each other" do
938
+ john = DocSon.create(:name => 'John')
939
+ steph = DocDaughter.create(:name => 'Steph')
940
+
941
+ lambda {
942
+ DocSon.destroy(steph._id)
943
+ }.should raise_error(MongoMapper::DocumentNotFound)
944
+ end
945
+
946
+ should "not be able to delete each other" do
947
+ john = DocSon.create(:name => 'John')
948
+ steph = DocDaughter.create(:name => 'Steph')
949
+
950
+ lambda {
951
+ DocSon.delete(steph._id)
952
+ }.should_not change { DocParent.count }
953
+ end
954
+
955
+ should "be able to destroy using parent" do
956
+ john = DocSon.create(:name => 'John')
957
+ steph = DocDaughter.create(:name => 'Steph')
958
+
959
+ lambda {
960
+ DocParent.destroy_all
961
+ }.should change { DocParent.count }.by(-2)
962
+ end
963
+
964
+ should "be able to delete using parent" do
965
+ john = DocSon.create(:name => 'John')
966
+ steph = DocDaughter.create(:name => 'Steph')
967
+
968
+ lambda {
969
+ DocParent.delete_all
970
+ }.should change { DocParent.count }.by(-2)
971
+ end
972
+
973
+ should "be able to reload parent inherited class" do
974
+ brian = DocParent.create(:name => 'Brian')
975
+ brian.name = 'B-Dawg'
976
+ brian.reload
977
+ brian.name.should == 'Brian'
978
+ end
979
+ end
980
+
981
+ context "timestamping" do
982
+ setup do
983
+ @document.timestamps!
984
+ end
985
+
986
+ should "set created_at and updated_at on create" do
987
+ doc = @document.new(:first_name => 'John', :age => 27)
988
+ doc.created_at.should be(nil)
989
+ doc.updated_at.should be(nil)
990
+ doc.save
991
+ doc.created_at.should_not be(nil)
992
+ doc.updated_at.should_not be(nil)
993
+ end
994
+
995
+ should "not overwrite created_at if it already exists" do
996
+ original_created_at = 1.month.ago
997
+ doc = @document.new(:first_name => 'John', :age => 27, :created_at => original_created_at)
998
+ doc.created_at.to_i.should == original_created_at.to_i
999
+ doc.updated_at.should be_nil
1000
+ doc.save
1001
+ doc.created_at.to_i.should == original_created_at.to_i
1002
+ doc.updated_at.should_not be_nil
1003
+ end
1004
+
1005
+ should "set updated_at on field update but leave created_at alone" do
1006
+ doc = @document.create(:first_name => 'John', :age => 27)
1007
+ old_created_at = doc.created_at
1008
+ old_updated_at = doc.updated_at
1009
+ doc.first_name = 'Johnny'
1010
+
1011
+ Timecop.freeze(Time.now + 5.seconds) do
1012
+ doc.save
1013
+ end
1014
+
1015
+ doc.created_at.should == old_created_at
1016
+ doc.updated_at.should_not == old_updated_at
1017
+ end
1018
+
1019
+ should "set updated_at on document update but leave created_at alone" do
1020
+ doc = @document.create(:first_name => 'John', :age => 27)
1021
+ old_created_at = doc.created_at
1022
+ old_updated_at = doc.updated_at
1023
+
1024
+ Timecop.freeze(Time.now + 5.seconds) do
1025
+ @document.update(doc._id, { :first_name => 'Johnny' })
1026
+ end
1027
+
1028
+ doc = doc.reload
1029
+ doc.created_at.should == old_created_at
1030
+ doc.updated_at.should_not == old_updated_at
1031
+ end
1032
+ end
1033
+
1034
+ context "userstamping" do
1035
+ setup do
1036
+ @document.userstamps!
1037
+ end
1038
+
1039
+ should "add creator_id key" do
1040
+ @document.keys.keys.should include('creator_id')
1041
+ end
1042
+
1043
+ should "add updater_id key" do
1044
+ @document.keys.keys.should include('updater_id')
1045
+ end
1046
+
1047
+ should "add belongs_to creator" do
1048
+ @document.associations.keys.should include('creator')
1049
+ end
1050
+
1051
+ should "add belongs_to updater" do
1052
+ @document.associations.keys.should include('updater')
1053
+ end
1054
+ end
1055
+
1056
+ context "#exist?" do
1057
+ setup do
1058
+ @doc = @document.create(:first_name => "James", :age => 27)
1059
+ end
1060
+
1061
+ should "be true when at least one document exists" do
1062
+ @document.exists?.should == true
1063
+ end
1064
+
1065
+ should "be false when no documents exist" do
1066
+ @doc.destroy
1067
+ @document.exists?.should == false
1068
+ end
1069
+
1070
+ should "be true when at least one document exists that matches the conditions" do
1071
+ @document.exists?(:first_name => "James").should == true
1072
+ end
1073
+
1074
+ should "be false when no documents exist with the provided conditions" do
1075
+ @document.exists?(:first_name => "Jean").should == false
1076
+ end
1077
+ end
1078
+
1079
+ context "#reload" do
1080
+ setup do
1081
+ @foo_class = Class.new do
1082
+ include MongoMapper::Document
1083
+ key :name
1084
+ end
1085
+ @foo_class.collection.remove
1086
+
1087
+ @bar_class = Class.new do
1088
+ include MongoMapper::EmbeddedDocument
1089
+ key :name
1090
+ end
1091
+
1092
+ @document.many :foos, :class => @foo_class
1093
+ @document.many :bars, :class => @bar_class
1094
+
1095
+ @instance = @document.create({
1096
+ :age => 39,
1097
+ :foos => [@foo_class.new(:name => '1')],
1098
+ :bars => [@bar_class.new(:name => '1')],
1099
+ })
1100
+ end
1101
+
1102
+ should "reload keys from the database" do
1103
+ @instance.age = 37
1104
+ @instance.age.should == 37
1105
+ @instance.reload
1106
+ @instance.age.should == 39
1107
+ end
1108
+
1109
+ should "reset all associations" do
1110
+ @instance.foos.expects(:reset).at_least_once
1111
+ @instance.bars.expects(:reset).at_least_once
1112
+ @instance.reload
1113
+ end
1114
+
1115
+ should "reinstantiate embedded associations" do
1116
+ @instance.reload
1117
+ @instance.bars.first.name.should == '1'
1118
+ end
1119
+
1120
+ should "return self" do
1121
+ @instance.reload.object_id.should == @instance.object_id
1122
+ end
1123
+ end
1124
+
1125
+ context "Saving a document with a custom id" do
1126
+ should "clear custom id flag when saved" do
1127
+ @document.key :_id, String
1128
+ doc = @document.new(:id => '1234')
1129
+ doc.using_custom_id?.should be_true
1130
+ doc.save.should be_true
1131
+ doc.using_custom_id?.should be_false
1132
+ end
1133
+ end
1134
+
1135
+ context "Loading a document from the database with keys that are not defined" do
1136
+ setup do
1137
+ @id = Mongo::ObjectID.new
1138
+ @document.collection.insert({
1139
+ :_id => @id,
1140
+ :first_name => 'John',
1141
+ :last_name => 'Nunemaker',
1142
+ :age => 27,
1143
+ :favorite_color => 'red',
1144
+ :skills => ['ruby', 'rails', 'javascript', 'xhtml', 'css']
1145
+ })
1146
+ end
1147
+
1148
+ should "assign all keys from database" do
1149
+ doc = @document.find(@id)
1150
+ doc.first_name.should == 'John'
1151
+ doc.last_name.should == 'Nunemaker'
1152
+ doc.age.should == 27
1153
+ doc.favorite_color.should == 'red'
1154
+ doc.skills.should == ['ruby', 'rails', 'javascript', 'xhtml', 'css']
1155
+ end
1156
+ end
1157
+
1158
+ context "Indexing" do
1159
+ setup do
1160
+ MongoMapper.ensured_indexes = []
1161
+ @document.collection.drop_indexes
1162
+ end
1163
+
1164
+ should "allow creating index for a key" do
1165
+ @document.ensure_index :first_name
1166
+ MongoMapper.ensure_indexes!
1167
+
1168
+ @document.should have_index('first_name_1')
1169
+ end
1170
+
1171
+ should "allow creating unique index for a key" do
1172
+ @document.ensure_index :first_name, :unique => true
1173
+ MongoMapper.ensure_indexes!
1174
+
1175
+ @document.should have_index('first_name_1')
1176
+ end
1177
+
1178
+ should "allow creating index on multiple keys" do
1179
+ @document.ensure_index [[:first_name, 1], [:last_name, -1]]
1180
+ MongoMapper.ensure_indexes!
1181
+
1182
+ # order is different for different versions of ruby so instead of
1183
+ # just checking have_index('first_name_1_last_name_-1') I'm checking
1184
+ # the values of the indexes to make sure the index creation was successful
1185
+ @document.collection.index_information.detect do |index|
1186
+ keys = index[1]
1187
+ keys.include?(['first_name', 1]) && keys.include?(['last_name', -1])
1188
+ end.should_not be_nil
1189
+ end
1190
+
1191
+ should "work with :index shortcut when defining key" do
1192
+ @document.key :father, String, :index => true
1193
+ MongoMapper.ensure_indexes!
1194
+
1195
+ @document.should have_index('father_1')
1196
+ end
1197
+ end
1198
+ end