jmonteiro-mongo_mapper 0.1.0

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