ramsingla-mongomapper 0.2.1

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