ramsingla-mongomapper 0.2.1

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 (42) hide show
  1. data/.gitignore +7 -0
  2. data/History +30 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +71 -0
  6. data/VERSION +1 -0
  7. data/lib/mongomapper.rb +60 -0
  8. data/lib/mongomapper/associations.rb +69 -0
  9. data/lib/mongomapper/associations/array_proxy.rb +6 -0
  10. data/lib/mongomapper/associations/base.rb +50 -0
  11. data/lib/mongomapper/associations/belongs_to_proxy.rb +26 -0
  12. data/lib/mongomapper/associations/has_many_embedded_proxy.rb +19 -0
  13. data/lib/mongomapper/associations/has_many_proxy.rb +28 -0
  14. data/lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb +31 -0
  15. data/lib/mongomapper/associations/proxy.rb +60 -0
  16. data/lib/mongomapper/callbacks.rb +106 -0
  17. data/lib/mongomapper/document.rb +263 -0
  18. data/lib/mongomapper/embedded_document.rb +295 -0
  19. data/lib/mongomapper/finder_options.rb +81 -0
  20. data/lib/mongomapper/key.rb +82 -0
  21. data/lib/mongomapper/observing.rb +50 -0
  22. data/lib/mongomapper/pagination.rb +52 -0
  23. data/lib/mongomapper/rails_compatibility.rb +23 -0
  24. data/lib/mongomapper/save_with_validation.rb +19 -0
  25. data/lib/mongomapper/serialization.rb +55 -0
  26. data/lib/mongomapper/serializers/json_serializer.rb +77 -0
  27. data/lib/mongomapper/validations.rb +47 -0
  28. data/mongomapper.gemspec +105 -0
  29. data/test/serializers/test_json_serializer.rb +104 -0
  30. data/test/test_associations.rb +211 -0
  31. data/test/test_callbacks.rb +84 -0
  32. data/test/test_document.rb +995 -0
  33. data/test/test_embedded_document.rb +253 -0
  34. data/test/test_finder_options.rb +148 -0
  35. data/test/test_helper.rb +62 -0
  36. data/test/test_key.rb +200 -0
  37. data/test/test_mongomapper.rb +28 -0
  38. data/test/test_observing.rb +101 -0
  39. data/test/test_rails_compatibility.rb +29 -0
  40. data/test/test_serializations.rb +54 -0
  41. data/test/test_validations.rb +409 -0
  42. metadata +156 -0
@@ -0,0 +1,84 @@
1
+ require 'test_helper'
2
+
3
+ class CallbacksTest < Test::Unit::TestCase
4
+ context "Defining and running callbacks" do
5
+ setup do
6
+ @document = Class.new do
7
+ include MongoMapper::Document
8
+
9
+ key :name, String
10
+
11
+ [ :before_validation_on_create, :before_validation_on_update,
12
+ :before_validation, :after_validation,
13
+ :before_create, :after_create,
14
+ :before_update, :after_update,
15
+ :before_save, :after_save,
16
+ :before_destroy, :after_destroy].each do |callback|
17
+ callback_method = "#{callback}_callback"
18
+ send(callback, callback_method)
19
+ define_method(callback_method) do
20
+ history << callback.to_sym
21
+ end
22
+ end
23
+
24
+ def history
25
+ @history ||= []
26
+ end
27
+
28
+ def clear_history
29
+ @history = nil
30
+ end
31
+ end
32
+ @document.collection.clear
33
+ end
34
+
35
+ should "get the order right for creating documents" do
36
+ doc = @document.create(:name => 'John Nunemaker')
37
+ doc.history.should == [:before_validation, :before_validation_on_create, :after_validation, :before_save, :before_create, :after_create, :after_save]
38
+ end
39
+
40
+ should "get the order right for updating documents" do
41
+ doc = @document.create(:name => 'John Nunemaker')
42
+ doc.clear_history
43
+ doc.name = 'John'
44
+ doc.save
45
+ doc.history.should == [:before_validation, :before_validation_on_update, :after_validation, :before_save, :before_update, :after_update, :after_save]
46
+ end
47
+
48
+ should "work for before and after validation" do
49
+ doc = @document.new(:name => 'John Nunemaker')
50
+ doc.valid?
51
+ doc.history.should include(:before_validation)
52
+ doc.history.should include(:after_validation)
53
+ end
54
+
55
+ should "work for before and after create" do
56
+ doc = @document.create(:name => 'John Nunemaker')
57
+ doc.history.should include(:before_create)
58
+ doc.history.should include(:after_create)
59
+ end
60
+
61
+ should "work for before and after update" do
62
+ doc = @document.create(:name => 'John Nunemaker')
63
+ doc.name = 'John Doe'
64
+ doc.save
65
+ doc.history.should include(:before_update)
66
+ doc.history.should include(:after_update)
67
+ end
68
+
69
+ should "work for before and after save" do
70
+ doc = @document.new
71
+ doc.name = 'John Doe'
72
+ doc.save
73
+ doc.history.should include(:before_save)
74
+ doc.history.should include(:after_save)
75
+ end
76
+
77
+ should "work for before and after destroy" do
78
+ doc = @document.create(:name => 'John Nunemaker')
79
+ doc.destroy
80
+ doc.history.should include(:before_destroy)
81
+ doc.history.should include(:after_destroy)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,995 @@
1
+ require 'test_helper'
2
+
3
+ class Address
4
+ include MongoMapper::EmbeddedDocument
5
+ key :city, String
6
+ key :state, String
7
+ end
8
+
9
+ class DocumentTest < Test::Unit::TestCase
10
+ context "The Document Class" do
11
+ setup do
12
+ @document = Class.new do
13
+ include MongoMapper::Document
14
+ end
15
+ end
16
+
17
+ should "track its descendants" do
18
+ MongoMapper::Document.descendants.should include(@document)
19
+ end
20
+
21
+ should "find its parent model" do
22
+ class A < Address
23
+ key :new_key, String
24
+ end
25
+
26
+ A.parent_model.should == Address
27
+ end
28
+
29
+ should "inherit keys" do
30
+ class A < Address
31
+ key :new_key, String
32
+ end
33
+
34
+ A.keys.should include("new_key")
35
+ Address.keys.should_not include("new_key")
36
+ end
37
+
38
+ should "be able to define a key" do
39
+ key = @document.key(:name, String)
40
+ key.name.should == 'name'
41
+ key.type.should == String
42
+ key.should be_instance_of(MongoMapper::Key)
43
+ end
44
+
45
+ should "be able to define a key with options" do
46
+ key = @document.key(:name, String, :required => true)
47
+ key.options[:required].should be(true)
48
+ end
49
+
50
+ should "know what keys have been defined" do
51
+ @document.key(:name, String)
52
+ @document.key(:age, Integer)
53
+ @document.keys['name'].name.should == 'name'
54
+ @document.keys['name'].type.should == String
55
+ @document.keys['age'].name.should == 'age'
56
+ @document.keys['age'].type.should == Integer
57
+ end
58
+
59
+ should "use default database by default" do
60
+ @document.database.should == MongoMapper.database
61
+ end
62
+
63
+ should "have a connection" do
64
+ @document.connection.should be_instance_of(XGen::Mongo::Driver::Mongo)
65
+ end
66
+
67
+ should "allow setting different connection without affecting the default" do
68
+ conn = XGen::Mongo::Driver::Mongo.new
69
+ @document.connection conn
70
+ @document.connection.should == conn
71
+ @document.connection.should_not == MongoMapper.connection
72
+ end
73
+
74
+ should "allow setting a different database without affecting the default" do
75
+ @document.database AlternateDatabase
76
+ @document.database.name.should == AlternateDatabase
77
+
78
+ another_document = Class.new do
79
+ include MongoMapper::Document
80
+ end
81
+ another_document.database.should == MongoMapper.database
82
+ end
83
+
84
+ class Item; include MongoMapper::Document; end
85
+ should "default collection name to class name tableized" do
86
+ Item.collection.should be_instance_of(XGen::Mongo::Driver::Collection)
87
+ Item.collection.name.should == 'items'
88
+ end
89
+
90
+ should "allow setting the collection name" do
91
+ @document.collection('foobar')
92
+ @document.collection.should be_instance_of(XGen::Mongo::Driver::Collection)
93
+ @document.collection.name.should == 'foobar'
94
+ end
95
+ end # Document class
96
+
97
+ context "Database operations" do
98
+ setup do
99
+ @document = Class.new do
100
+ include MongoMapper::Document
101
+ collection 'users'
102
+
103
+ key :fname, String
104
+ key :lname, String
105
+ key :age, Integer
106
+ end
107
+
108
+ @document.collection.clear
109
+ end
110
+
111
+ context "Using key with type Array" do
112
+ setup do
113
+ @document.key :tags, Array
114
+ end
115
+
116
+ should "work" do
117
+ doc = @document.new
118
+ doc.tags.should == []
119
+ doc.tags = %w(foo bar)
120
+ doc.save
121
+ doc.tags.should == %w(foo bar)
122
+ @document.find(doc.id).tags.should == %w(foo bar)
123
+ end
124
+ end
125
+
126
+ context "Using key with type Hash" do
127
+ setup do
128
+ @document.key :foo, Hash
129
+ end
130
+
131
+ should "work with indifferent access" do
132
+ doc = @document.new
133
+ doc.foo = {:baz => 'bar'}
134
+ doc.save
135
+
136
+ doc = @document.find(doc.id)
137
+ doc.foo[:baz].should == 'bar'
138
+ doc.foo['baz'].should == 'bar'
139
+ end
140
+ end
141
+
142
+ context "Saving a document with an embedded document" do
143
+ setup do
144
+ @document.class_eval do
145
+ key :foo, Address
146
+ end
147
+ end
148
+
149
+ should "embed embedded document" do
150
+ address = Address.new(:city => 'South Bend', :state => 'IN')
151
+ doc = @document.new(:foo => address)
152
+ doc.save
153
+ doc.foo.city.should == 'South Bend'
154
+ doc.foo.state.should == 'IN'
155
+
156
+ from_db = @document.find(doc.id)
157
+ from_db.foo.city.should == 'South Bend'
158
+ from_db.foo.state.should == 'IN'
159
+ end
160
+ end
161
+
162
+ context "Creating a single document" do
163
+ setup do
164
+ @doc_instance = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
165
+ end
166
+
167
+ should "create a document in correct collection" do
168
+ @document.count.should == 1
169
+ end
170
+
171
+ should "automatically set id" do
172
+ @doc_instance.id.should_not be_nil
173
+ @doc_instance.id.size.should == 24
174
+ end
175
+
176
+ should "return instance of document" do
177
+ @doc_instance.should be_instance_of(@document)
178
+ @doc_instance.fname.should == 'John'
179
+ @doc_instance.lname.should == 'Nunemaker'
180
+ @doc_instance.age.should == 27
181
+ end
182
+ end
183
+
184
+ context "Creating multiple documents" do
185
+ setup do
186
+ @doc_instances = @document.create([
187
+ {:fname => 'John', :lname => 'Nunemaker', :age => '27'},
188
+ {:fname => 'Steve', :lname => 'Smith', :age => '28'},
189
+ ])
190
+ end
191
+
192
+ should "create multiple documents" do
193
+ @document.count.should == 2
194
+ end
195
+
196
+ should "return an array of doc instances" do
197
+ @doc_instances.map do |doc_instance|
198
+ doc_instance.should be_instance_of(@document)
199
+ end
200
+ end
201
+ end
202
+
203
+ context "Updating a document" do
204
+ setup do
205
+ doc = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
206
+ @doc_instance = @document.update(doc.id, {:age => 40})
207
+ end
208
+
209
+ should "update attributes provided" do
210
+ @doc_instance.age.should == 40
211
+ end
212
+
213
+ should "not update existing attributes that were not set to update" do
214
+ @doc_instance.fname.should == 'John'
215
+ @doc_instance.lname.should == 'Nunemaker'
216
+ end
217
+
218
+ should "not create new document" do
219
+ @document.count.should == 1
220
+ end
221
+ end
222
+
223
+ should "raise error when updating single doc if not provided id and attributes" do
224
+ doc = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
225
+ lambda { @document.update }.should raise_error(ArgumentError)
226
+ lambda { @document.update(doc.id) }.should raise_error(ArgumentError)
227
+ lambda { @document.update(doc.id, [1]) }.should raise_error(ArgumentError)
228
+ end
229
+
230
+ context "Updating multiple documents" do
231
+ setup do
232
+ @doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
233
+ @doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
234
+
235
+ @doc_instances = @document.update({
236
+ @doc1.id => {:age => 30},
237
+ @doc2.id => {:age => 30},
238
+ })
239
+ end
240
+
241
+ should "not create any new documents" do
242
+ @document.count.should == 2
243
+ end
244
+
245
+ should "should return an array of doc instances" do
246
+ @doc_instances.map do |doc_instance|
247
+ doc_instance.should be_instance_of(@document)
248
+ end
249
+ end
250
+
251
+ should "update the documents" do
252
+ @document.find(@doc1.id).age.should == 30
253
+ @document.find(@doc2.id).age.should == 30
254
+ end
255
+ end
256
+
257
+ should "raise error when updating multiple documents if not a hash" do
258
+ lambda { @document.update([1, 2]) }.should raise_error(ArgumentError)
259
+ end
260
+
261
+ context "Finding documents" do
262
+ setup do
263
+ @doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
264
+ @doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
265
+ @doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
266
+ end
267
+
268
+ should "raise document not found if nothing provided" do
269
+ lambda { @document.find }.should raise_error(MongoMapper::DocumentNotFound)
270
+ end
271
+
272
+ context "with a single id" do
273
+ should "work" do
274
+ @document.find(@doc1.id).should == @doc1
275
+ end
276
+
277
+ should "raise error if document not found" do
278
+ lambda { @document.find(1) }.should raise_error(MongoMapper::DocumentNotFound)
279
+ end
280
+ end
281
+
282
+ context "with multiple id's" do
283
+ should "work as arguments" do
284
+ @document.find(@doc1.id, @doc2.id).should == [@doc1, @doc2]
285
+ end
286
+
287
+ should "work as array" do
288
+ @document.find([@doc1.id, @doc2.id]).should == [@doc1, @doc2]
289
+ end
290
+ end
291
+
292
+ context "with :all" do
293
+ should "find all documents" do
294
+ @document.find(:all).should == [@doc1, @doc2, @doc3]
295
+ end
296
+
297
+ should "be able to add conditions" do
298
+ @document.find(:all, :conditions => {:fname => 'John'}).should == [@doc1]
299
+ end
300
+ end
301
+
302
+ context "with #all" do
303
+ should "find all documents based on criteria" do
304
+ @document.all.should == [@doc1, @doc2, @doc3]
305
+ @document.all(:conditions => {:lname => 'Nunemaker'}).should == [@doc1, @doc3]
306
+ end
307
+ end
308
+
309
+ context "with :first" do
310
+ should "find first document" do
311
+ @document.find(:first).should == @doc1
312
+ end
313
+ end
314
+
315
+ context "with #first" do
316
+ should "find first document based on criteria" do
317
+ @document.first.should == @doc1
318
+ @document.first(:conditions => {:age => 28}).should == @doc2
319
+ end
320
+ end
321
+
322
+ context "with :last" do
323
+ should "find last document" do
324
+ @document.find(:last).should == @doc3
325
+ end
326
+ end
327
+
328
+ context "with #last" do
329
+ should "find last document based on criteria" do
330
+ @document.last.should == @doc3
331
+ @document.last(:conditions => {:age => 28}).should == @doc2
332
+ end
333
+ end
334
+ end # finding documents
335
+
336
+ context "Finding document by id" do
337
+ setup do
338
+ @doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
339
+ @doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
340
+ end
341
+
342
+ should "be able to find by id" do
343
+ @document.find_by_id(@doc1.id).should == @doc1
344
+ @document.find_by_id(@doc2.id).should == @doc2
345
+ end
346
+
347
+ should "return nil if document not found" do
348
+ @document.find_by_id(1234).should be(nil)
349
+ end
350
+ end
351
+
352
+ context "Deleting a document" do
353
+ setup do
354
+ @doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
355
+ @doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
356
+ @document.delete(@doc1.id)
357
+ end
358
+
359
+ should "remove document from collection" do
360
+ @document.count.should == 1
361
+ end
362
+
363
+ should "not remove other documents" do
364
+ @document.find(@doc2.id).should_not be(nil)
365
+ end
366
+ end
367
+
368
+ context "Deleting multiple documents" do
369
+ should "work with multiple arguments" do
370
+ @doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
371
+ @doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
372
+ @doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
373
+ @document.delete(@doc1.id, @doc2.id)
374
+
375
+ @document.count.should == 1
376
+ end
377
+
378
+ should "work with array as argument" do
379
+ @doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
380
+ @doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
381
+ @doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
382
+ @document.delete([@doc1.id, @doc2.id])
383
+
384
+ @document.count.should == 1
385
+ end
386
+ end
387
+
388
+ context "Deleting all documents" do
389
+ setup do
390
+ @doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
391
+ @doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
392
+ @doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
393
+ end
394
+
395
+ should "remove all documents when given no conditions" do
396
+ @document.delete_all
397
+ @document.count.should == 0
398
+ end
399
+
400
+ should "only remove matching documents when given conditions" do
401
+ @document.delete_all({:fname => 'John'})
402
+ @document.count.should == 2
403
+ end
404
+
405
+ should "convert the conditions to mongo criteria" do
406
+ @document.delete_all(:age => [26, 27])
407
+ @document.count.should == 1
408
+ end
409
+ end
410
+
411
+ context "Destroying a document" do
412
+ setup do
413
+ @doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
414
+ @doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
415
+ @document.destroy(@doc1.id)
416
+ end
417
+
418
+ should "remove document from collection" do
419
+ @document.count.should == 1
420
+ end
421
+
422
+ should "not remove other documents" do
423
+ @document.find(@doc2.id).should_not be(nil)
424
+ end
425
+ end
426
+
427
+ context "Destroying multiple documents" do
428
+ should "work with multiple arguments" do
429
+ @doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
430
+ @doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
431
+ @doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
432
+ @document.destroy(@doc1.id, @doc2.id)
433
+
434
+ @document.count.should == 1
435
+ end
436
+
437
+ should "work with array as argument" do
438
+ @doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
439
+ @doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
440
+ @doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
441
+ @document.destroy([@doc1.id, @doc2.id])
442
+
443
+ @document.count.should == 1
444
+ end
445
+ end
446
+
447
+ context "Destroying all documents" do
448
+ setup do
449
+ @doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
450
+ @doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
451
+ @doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
452
+ end
453
+
454
+ should "remove all documents when given no conditions" do
455
+ @document.destroy_all
456
+ @document.count.should == 0
457
+ end
458
+
459
+ should "only remove matching documents when given conditions" do
460
+ @document.destroy_all(:fname => 'John')
461
+ @document.count.should == 2
462
+ @document.destroy_all(:age => 26)
463
+ @document.count.should == 1
464
+ end
465
+
466
+ should "convert the conditions to mongo criteria" do
467
+ @document.destroy_all(:age => [26, 27])
468
+ @document.count.should == 1
469
+ end
470
+ end
471
+
472
+ context "Counting documents in collection" do
473
+ setup do
474
+ @doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
475
+ @doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
476
+ @doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
477
+ end
478
+
479
+ should "count all with no arguments" do
480
+ @document.count.should == 3
481
+ end
482
+
483
+ should "return 0 if there are no documents in the collection" do
484
+ @document.delete_all
485
+ @document.count.should == 0
486
+ end
487
+
488
+ should "return 0 if the collection does not exist" do
489
+ klass = Class.new do
490
+ include MongoMapper::Document
491
+ collection 'foobarbazwickdoesnotexist'
492
+ end
493
+
494
+ klass.count.should == 0
495
+ end
496
+
497
+ should "return count for matching documents if conditions provided" do
498
+ @document.count(:age => 27).should == 1
499
+ end
500
+
501
+ should "convert the conditions to mongo criteria" do
502
+ @document.count(:age => [26, 27]).should == 2
503
+ end
504
+ end
505
+
506
+ context "Paginating" do
507
+ setup do
508
+ @doc1 = @document.create({:fname => 'John', :lname => 'Nunemaker', :age => '27'})
509
+ @doc2 = @document.create({:fname => 'Steve', :lname => 'Smith', :age => '28'})
510
+ @doc3 = @document.create({:fname => 'Steph', :lname => 'Nunemaker', :age => '26'})
511
+ end
512
+
513
+ should "return the total pages" do
514
+ result = @document.paginate(:per_page => 2, :page => 1)
515
+ result.total_pages.should == 2
516
+ end
517
+
518
+ should "return the total of records" do
519
+ result = @document.paginate(:per_page => 2, :page => 1)
520
+ result.total_entries.should == 3
521
+ end
522
+
523
+ should "return the items" do
524
+ result = @document.paginate(:per_page => 2, :page => 1)
525
+ result.size.should == 2
526
+ result.subject.should == [@doc1, @doc2]
527
+ end
528
+
529
+ should "accept conditions" do
530
+ result = @document.paginate({
531
+ :conditions => {:lname => 'Nunemaker'},
532
+ :order => "age DESC",
533
+ :per_page => 2,
534
+ :page => 1,
535
+ })
536
+ result.first.age.should == 27
537
+ end
538
+ end
539
+
540
+ context "Indexing" do
541
+ setup do
542
+ @document.collection.drop_indexes
543
+ end
544
+
545
+ should "allow creating index for a key" do
546
+ lambda {
547
+ @document.ensure_index :fname
548
+ }.should change { @document.collection.index_information.size }.by(1)
549
+
550
+ index = @document.collection.index_information['fname_1']
551
+ index.should_not be_nil
552
+ index.should include(['fname', 1])
553
+ end
554
+
555
+ should "allow creating unique index for a key" do
556
+ @document.collection.expects(:create_index).with(:fname, true)
557
+ @document.ensure_index :fname, :unique => true
558
+ end
559
+
560
+ should "allow creating index on multiple keys" do
561
+ lambda {
562
+ @document.ensure_index [[:fname, 1], [:lname, -1]]
563
+ }.should change { @document.collection.index_information.size }.by(1)
564
+
565
+ index = @document.collection.index_information['lname_-1_fname_1']
566
+ index.should_not be_nil
567
+ index.should include(['fname', 1])
568
+ index.should include(['lname', -1])
569
+ end
570
+
571
+ should "work with :index shortcut when defining key" do
572
+ lambda {
573
+ @document.key :father, String, :index => true
574
+ }.should change { @document.collection.index_information.size }.by(1)
575
+
576
+ index = @document.collection.index_information['father_1']
577
+ index.should_not be_nil
578
+ index.should include(['father', 1])
579
+ end
580
+ end
581
+ end # Database operations
582
+
583
+ context "An instance of a document" do
584
+ setup do
585
+ @document = Class.new do
586
+ include MongoMapper::Document
587
+
588
+ key :name, String
589
+ key :age, Integer
590
+ end
591
+ @document.collection.clear
592
+ end
593
+
594
+ should "have access to the class's collection" do
595
+ doc = @document.new
596
+ doc.collection.should == @document.collection
597
+ end
598
+
599
+ should "automatically have an _id key" do
600
+ @document.keys.keys.should include('_id')
601
+ end
602
+
603
+ should "automatically have a created_at key" do
604
+ @document.keys.keys.should include('created_at')
605
+ end
606
+
607
+ should "automatically have an updated_at key" do
608
+ @document.keys.keys.should include('updated_at')
609
+ end
610
+
611
+ should "use default values if defined for keys" do
612
+ @document.key :active, Boolean, :default => true
613
+
614
+ @document.new.active.should be_true
615
+ @document.new(:active => false).active.should be_false
616
+ end
617
+
618
+ context "new?" do
619
+ should "be true if no id" do
620
+ @document.new.new?.should be(true)
621
+ end
622
+
623
+ should "be true if has id but id not in database" do
624
+ @document.new('_id' => 1).new?.should be(true)
625
+ end
626
+
627
+ should "be false if has id and id is in database" do
628
+ doc = @document.create(:name => 'John Nunemaker', :age => 27)
629
+ doc.new?.should be(false)
630
+ end
631
+ end
632
+
633
+ context "mass assigning keys" do
634
+ should "update values for keys provided" do
635
+ doc = @document.new(:name => 'foobar', :age => 10)
636
+ doc.attributes = {:name => 'new value', :age => 5}
637
+ doc.attributes[:name].should == 'new value'
638
+ doc.attributes[:age].should == 5
639
+ end
640
+
641
+ should "not update values for keys that were not provided" do
642
+ doc = @document.new(:name => 'foobar', :age => 10)
643
+ doc.attributes = {:name => 'new value'}
644
+ doc.attributes[:name].should == 'new value'
645
+ doc.attributes[:age].should == 10
646
+ end
647
+
648
+ should "ignore keys that do not exist" do
649
+ doc = @document.new(:name => 'foobar', :age => 10)
650
+ doc.attributes = {:name => 'new value', :foobar => 'baz'}
651
+ doc.attributes[:name].should == 'new value'
652
+ doc.attributes[:foobar].should be(nil)
653
+ end
654
+
655
+ should "typecast key values" do
656
+ doc = @document.new(:name => 1234, :age => '21')
657
+ doc.name.should == '1234'
658
+ doc.age.should == 21
659
+ end
660
+ end
661
+
662
+ context "requesting keys" do
663
+ should "default to empty hash" do
664
+ doc = @document.new
665
+ doc.attributes.should == {}
666
+ end
667
+
668
+ should "return all keys that aren't nil" do
669
+ doc = @document.new(:name => 'string', :age => nil)
670
+ doc.attributes.should == {'name' => 'string'}
671
+ end
672
+ end
673
+
674
+ context "key shorcuts" do
675
+ should "be able to read key with []" do
676
+ doc = @document.new(:name => 'string')
677
+ doc[:name].should == 'string'
678
+ end
679
+
680
+ should "be able to write key value with []=" do
681
+ doc = @document.new
682
+ doc[:name] = 'string'
683
+ doc[:name].should == 'string'
684
+ end
685
+ end
686
+
687
+ context "indifferent access" do
688
+ should "be enabled for keys" do
689
+ doc = @document.new(:name => 'string')
690
+ doc.attributes[:name].should == 'string'
691
+ doc.attributes['name'].should == 'string'
692
+ end
693
+ end
694
+
695
+ context "reading an attribute" do
696
+ should "work for defined keys" do
697
+ doc = @document.new(:name => 'string')
698
+ doc.name.should == 'string'
699
+ end
700
+
701
+ should "raise no method error for undefined keys" do
702
+ doc = @document.new
703
+ lambda { doc.fart }.should raise_error(NoMethodError)
704
+ end
705
+
706
+ should "know if reader defined" do
707
+ doc = @document.new
708
+ doc.reader?('name').should be(true)
709
+ doc.reader?(:name).should be(true)
710
+ doc.reader?('age').should be(true)
711
+ doc.reader?(:age).should be(true)
712
+ doc.reader?('foobar').should be(false)
713
+ doc.reader?(:foobar).should be(false)
714
+ end
715
+
716
+ should "be accessible for use in the model" do
717
+ @document.class_eval do
718
+ def name_and_age
719
+ "#{read_attribute(:name)} (#{read_attribute(:age)})"
720
+ end
721
+ end
722
+
723
+ doc = @document.new(:name => 'John', :age => 27)
724
+ doc.name_and_age.should == 'John (27)'
725
+ end
726
+ end
727
+
728
+ context "reading an attribute before typcasting" do
729
+ should "work for defined keys" do
730
+ doc = @document.new(:name => 12)
731
+ doc.name_before_typecast.should == 12
732
+ end
733
+
734
+ should "raise no method error for undefined keys" do
735
+ doc = @document.new
736
+ lambda { doc.foo_before_typecast }.should raise_error(NoMethodError)
737
+ end
738
+
739
+ should "be accessible for use in a document" do
740
+ @document.class_eval do
741
+ def untypcasted_name
742
+ read_attribute_before_typecast(:name)
743
+ end
744
+ end
745
+
746
+ doc = @document.new(:name => 12)
747
+ doc.name.should == '12'
748
+ doc.untypcasted_name.should == 12
749
+ end
750
+ end
751
+
752
+ context "writing an attribute" do
753
+ should "work for defined keys" do
754
+ doc = @document.new
755
+ doc.name = 'John'
756
+ doc.name.should == 'John'
757
+ end
758
+
759
+ should "raise no method error for undefined keys" do
760
+ doc = @document.new
761
+ lambda { doc.fart = 'poof!' }.should raise_error(NoMethodError)
762
+ end
763
+
764
+ should "typecast value" do
765
+ doc = @document.new
766
+ doc.name = 1234
767
+ doc.name.should == '1234'
768
+ doc.age = '21'
769
+ doc.age.should == 21
770
+ end
771
+
772
+ should "know if writer defined" do
773
+ doc = @document.new
774
+ doc.writer?('name').should be(true)
775
+ doc.writer?('name=').should be(true)
776
+ doc.writer?(:name).should be(true)
777
+ doc.writer?('age').should be(true)
778
+ doc.writer?('age=').should be(true)
779
+ doc.writer?(:age).should be(true)
780
+ doc.writer?('foobar').should be(false)
781
+ doc.writer?('foobar=').should be(false)
782
+ doc.writer?(:foobar).should be(false)
783
+ end
784
+
785
+ should "be accessible for use in the model" do
786
+ @document.class_eval do
787
+ def name_and_age=(new_value)
788
+ new_value.match(/([^\(\s]+) \((.*)\)/)
789
+ write_attribute :name, $1
790
+ write_attribute :age, $2
791
+ end
792
+ end
793
+
794
+ doc = @document.new
795
+ doc.name_and_age = 'Frank (62)'
796
+ doc.name.should == 'Frank'
797
+ doc.age.should == 62
798
+ end
799
+ end # writing an attribute
800
+
801
+ context "respond_to?" do
802
+ setup do
803
+ @doc = @document.new
804
+ end
805
+
806
+ should "work for readers" do
807
+ @doc.respond_to?(:name).should be_true
808
+ @doc.respond_to?('name').should be_true
809
+ end
810
+
811
+ should "work for writers" do
812
+ @doc.respond_to?(:name=).should be_true
813
+ @doc.respond_to?('name=').should be_true
814
+ end
815
+
816
+ should "work for readers before typecast" do
817
+ @doc.respond_to?(:name_before_typecast).should be_true
818
+ @doc.respond_to?('name_before_typecast').should be_true
819
+ end
820
+ end
821
+
822
+ context "equality" do
823
+ should "be equal if id and class are the same" do
824
+ (@document.new('_id' => 1) == @document.new('_id' => 1)).should be(true)
825
+ end
826
+
827
+ should "not be equal if class same but id different" do
828
+ (@document.new('_id' => 1) == @document.new('_id' => 2)).should be(false)
829
+ end
830
+
831
+ should "not be equal if id same but class different" do
832
+ @another_document = Class.new do
833
+ include MongoMapper::Document
834
+ end
835
+
836
+ (@document.new('_id' => 1) == @another_document.new('_id' => 1)).should be(false)
837
+ end
838
+ end
839
+
840
+ context "Saving a new document" do
841
+ setup do
842
+ @doc = @document.new(:name => 'John Nunemaker', :age => '27')
843
+ @doc.save
844
+ end
845
+
846
+ should "insert document into the collection" do
847
+ @document.count.should == 1
848
+ end
849
+
850
+ should "assign an id for the document" do
851
+ @doc.id.should_not be(nil)
852
+ @doc.id.size.should == 24
853
+ end
854
+
855
+ should "save attributes" do
856
+ @doc.name.should == 'John Nunemaker'
857
+ @doc.age.should == 27
858
+ end
859
+
860
+ should "update attributes in the database" do
861
+ from_db = @document.find(@doc.id)
862
+ from_db.should == @doc
863
+ from_db.name.should == 'John Nunemaker'
864
+ from_db.age.should == 27
865
+ end
866
+ end
867
+
868
+ context "Saving an existing document" do
869
+ setup do
870
+ @doc = @document.create(:name => 'John Nunemaker', :age => '27')
871
+ @doc.name = 'John Doe'
872
+ @doc.age = 30
873
+ @doc.save
874
+ end
875
+
876
+ should "not insert document into collection" do
877
+ @document.count.should == 1
878
+ end
879
+
880
+ should "update attributes" do
881
+ @doc.name.should == 'John Doe'
882
+ @doc.age.should == 30
883
+ end
884
+
885
+ should "update attributes in the database" do
886
+ from_db = @document.find(@doc.id)
887
+ from_db.name.should == 'John Doe'
888
+ from_db.age.should == 30
889
+ end
890
+ end
891
+
892
+ context "Calling update attributes on a new document" do
893
+ setup do
894
+ @doc = @document.new(:name => 'John Nunemaker', :age => '27')
895
+ @doc.update_attributes(:name => 'John Doe', :age => 30)
896
+ end
897
+
898
+ should "insert document into the collection" do
899
+ @document.count.should == 1
900
+ end
901
+
902
+ should "assign an id for the document" do
903
+ @doc.id.should_not be(nil)
904
+ @doc.id.size.should == 24
905
+ end
906
+
907
+ should "save attributes" do
908
+ @doc.name.should == 'John Doe'
909
+ @doc.age.should == 30
910
+ end
911
+
912
+ should "update attributes in the database" do
913
+ from_db = @document.find(@doc.id)
914
+ from_db.should == @doc
915
+ from_db.name.should == 'John Doe'
916
+ from_db.age.should == 30
917
+ end
918
+ end
919
+
920
+ context "Updating an existing document using update attributes" do
921
+ setup do
922
+ @doc = @document.create(:name => 'John Nunemaker', :age => '27')
923
+ @doc.update_attributes(:name => 'John Doe', :age => 30)
924
+ end
925
+
926
+ should "not insert document into collection" do
927
+ @document.count.should == 1
928
+ end
929
+
930
+ should "update attributes" do
931
+ @doc.name.should == 'John Doe'
932
+ @doc.age.should == 30
933
+ end
934
+
935
+ should "update attributes in the database" do
936
+ from_db = @document.find(@doc.id)
937
+ from_db.name.should == 'John Doe'
938
+ from_db.age.should == 30
939
+ end
940
+ end
941
+
942
+ context "Destroying a document that exists" do
943
+ setup do
944
+ @doc = @document.create(:name => 'John Nunemaker', :age => '27')
945
+ @doc.destroy
946
+ end
947
+
948
+ should "remove the document from the collection" do
949
+ @document.count.should == 0
950
+ end
951
+
952
+ should "raise error if assignment is attempted" do
953
+ lambda { @doc.name = 'Foo' }.should raise_error(TypeError)
954
+ end
955
+ end
956
+
957
+ context "Destroying a document that is a new" do
958
+ setup do
959
+ setup do
960
+ @doc = @document.new(:name => 'John Nunemaker', :age => '27')
961
+ @doc.destroy
962
+ end
963
+
964
+ should "not affect collection count" do
965
+ @document.collection.count.should == 0
966
+ end
967
+
968
+ should "raise error if assignment is attempted" do
969
+ lambda { @doc.name = 'Foo' }.should raise_error(TypeError)
970
+ end
971
+ end
972
+ end
973
+
974
+ context "timestamping" do
975
+ should "set created_at and updated_at on create" do
976
+ doc = @document.new(:name => 'John Nunemaker', :age => 27)
977
+ doc.created_at.should be(nil)
978
+ doc.updated_at.should be(nil)
979
+ doc.save
980
+ doc.created_at.should_not be(nil)
981
+ doc.updated_at.should_not be(nil)
982
+ end
983
+
984
+ should "set updated_at on update but leave created_at alone" do
985
+ doc = @document.create(:name => 'John Nunemaker', :age => 27)
986
+ old_created_at = doc.created_at
987
+ old_updated_at = doc.updated_at
988
+ doc.name = 'John Doe'
989
+ doc.save
990
+ doc.created_at.should == old_created_at
991
+ doc.updated_at.should_not == old_updated_at
992
+ end
993
+ end
994
+ end # instance of a document
995
+ end # DocumentTest