tpitale-mongo_mapper 0.6.9

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