mongo_mapper-unstable 2009.10.11

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