jmonteiro-mongo_mapper 0.1.0

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