tpitale-mongo_mapper 0.6.9

Sign up to get free protection for your applications and to get access to all the features.
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