numon 0.0.1

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