fcoury-mongomapper 0.2.0

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