mongo_mapper 0.6.10 → 0.7.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 (106) hide show
  1. data/README.rdoc +5 -14
  2. data/Rakefile +1 -1
  3. data/VERSION +1 -1
  4. data/lib/mongo_mapper.rb +48 -56
  5. data/lib/mongo_mapper/document.rb +136 -164
  6. data/lib/mongo_mapper/embedded_document.rb +29 -354
  7. data/lib/mongo_mapper/plugins.rb +31 -0
  8. data/lib/mongo_mapper/plugins/associations.rb +105 -0
  9. data/lib/mongo_mapper/plugins/associations/base.rb +123 -0
  10. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +30 -0
  11. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +25 -0
  12. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  13. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +50 -0
  14. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +139 -0
  15. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  16. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +117 -0
  17. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  18. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  19. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  20. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +68 -0
  21. data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
  22. data/lib/mongo_mapper/plugins/callbacks.rb +65 -0
  23. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  24. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  25. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  26. data/lib/mongo_mapper/plugins/equality.rb +23 -0
  27. data/lib/mongo_mapper/plugins/identity_map.rb +122 -0
  28. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  29. data/lib/mongo_mapper/plugins/keys.rb +324 -0
  30. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  31. data/lib/mongo_mapper/plugins/pagination.rb +24 -0
  32. data/lib/mongo_mapper/plugins/pagination/proxy.rb +68 -0
  33. data/lib/mongo_mapper/plugins/protected.rb +45 -0
  34. data/lib/mongo_mapper/plugins/rails.rb +45 -0
  35. data/lib/mongo_mapper/plugins/serialization.rb +105 -0
  36. data/lib/mongo_mapper/plugins/validations.rb +46 -0
  37. data/lib/mongo_mapper/query.rb +130 -0
  38. data/lib/mongo_mapper/support.rb +40 -17
  39. data/lib/mongo_mapper/support/descendant_appends.rb +46 -0
  40. data/lib/mongo_mapper/support/find.rb +77 -0
  41. data/mongo_mapper.gemspec +55 -38
  42. data/performance/read_write.rb +52 -0
  43. data/specs.watchr +23 -2
  44. data/test/functional/associations/test_belongs_to_proxy.rb +12 -10
  45. data/test/functional/associations/test_many_documents_as_proxy.rb +4 -21
  46. data/test/functional/associations/test_many_documents_proxy.rb +2 -8
  47. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +59 -39
  48. data/test/functional/associations/test_many_embedded_proxy.rb +145 -81
  49. data/test/functional/associations/test_many_polymorphic_proxy.rb +2 -40
  50. data/test/functional/associations/test_one_proxy.rb +25 -10
  51. data/test/functional/test_binary.rb +2 -8
  52. data/test/functional/test_callbacks.rb +1 -5
  53. data/test/functional/test_dirty.rb +27 -23
  54. data/test/functional/test_document.rb +224 -165
  55. data/test/functional/test_embedded_document.rb +72 -82
  56. data/test/functional/test_identity_map.rb +508 -0
  57. data/test/functional/test_modifiers.rb +15 -5
  58. data/test/functional/test_pagination.rb +1 -3
  59. data/test/functional/test_protected.rb +155 -0
  60. data/test/functional/test_string_id_compatibility.rb +7 -12
  61. data/test/functional/test_validations.rb +26 -58
  62. data/test/models.rb +0 -39
  63. data/test/test_helper.rb +37 -3
  64. data/test/unit/associations/test_base.rb +5 -5
  65. data/test/unit/associations/test_proxy.rb +8 -6
  66. data/test/unit/test_descendant_appends.rb +71 -0
  67. data/test/unit/test_document.rb +71 -76
  68. data/test/unit/test_dynamic_finder.rb +27 -29
  69. data/test/unit/test_embedded_document.rb +555 -601
  70. data/test/unit/{test_key.rb → test_keys.rb} +2 -5
  71. data/test/unit/test_mongo_mapper.rb +69 -9
  72. data/test/unit/test_pagination.rb +40 -32
  73. data/test/unit/test_plugins.rb +50 -0
  74. data/test/unit/{test_finder_options.rb → test_query.rb} +74 -65
  75. data/test/unit/test_rails.rb +123 -0
  76. data/test/unit/{test_serializations.rb → test_serialization.rb} +1 -2
  77. data/test/unit/test_support.rb +23 -7
  78. data/test/unit/test_time_zones.rb +3 -4
  79. data/test/unit/test_validations.rb +58 -17
  80. metadata +53 -36
  81. data/lib/mongo_mapper/associations.rb +0 -78
  82. data/lib/mongo_mapper/associations/base.rb +0 -119
  83. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +0 -26
  84. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +0 -21
  85. data/lib/mongo_mapper/associations/collection.rb +0 -19
  86. data/lib/mongo_mapper/associations/in_array_proxy.rb +0 -137
  87. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +0 -26
  88. data/lib/mongo_mapper/associations/many_documents_proxy.rb +0 -115
  89. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +0 -31
  90. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +0 -54
  91. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +0 -11
  92. data/lib/mongo_mapper/associations/one_proxy.rb +0 -64
  93. data/lib/mongo_mapper/associations/proxy.rb +0 -116
  94. data/lib/mongo_mapper/callbacks.rb +0 -61
  95. data/lib/mongo_mapper/dirty.rb +0 -117
  96. data/lib/mongo_mapper/dynamic_finder.rb +0 -74
  97. data/lib/mongo_mapper/finder_options.rb +0 -145
  98. data/lib/mongo_mapper/key.rb +0 -36
  99. data/lib/mongo_mapper/mongo_mapper.rb +0 -125
  100. data/lib/mongo_mapper/pagination.rb +0 -66
  101. data/lib/mongo_mapper/rails_compatibility/document.rb +0 -15
  102. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +0 -28
  103. data/lib/mongo_mapper/serialization.rb +0 -54
  104. data/lib/mongo_mapper/serializers/json_serializer.rb +0 -48
  105. data/lib/mongo_mapper/validations.rb +0 -39
  106. data/test/functional/test_rails_compatibility.rb +0 -25
@@ -176,22 +176,6 @@ class ManyPolymorphicProxyTest < Test::Unit::TestCase
176
176
  end
177
177
  end
178
178
 
179
- context "with :all" do
180
- should "work" do
181
- @lounge.messages.find(:all, :order => "position").should == [@lm1, @lm2]
182
- end
183
-
184
- should "work with conditions" do
185
- messages = @lounge.messages.find(:all, :body => 'Loungin!', :order => "position")
186
- messages.should == [@lm1]
187
- end
188
-
189
- should "work with order" do
190
- messages = @lounge.messages.find(:all, :order => 'position desc')
191
- messages.should == [@lm2, @lm1]
192
- end
193
- end
194
-
195
179
  context "with #all" do
196
180
  should "work" do
197
181
  @lounge.messages.all(:order => "position").should == [@lm1, @lm2]
@@ -208,17 +192,6 @@ class ManyPolymorphicProxyTest < Test::Unit::TestCase
208
192
  end
209
193
  end
210
194
 
211
- context "with :first" do
212
- should "work" do
213
- @lounge.messages.find(:first, :order => "position asc").should == @lm1
214
- end
215
-
216
- should "work with conditions" do
217
- message = @lounge.messages.find(:first, :body => 'I love loungin!', :order => "position asc")
218
- message.should == @lm2
219
- end
220
- end
221
-
222
195
  context "with #first" do
223
196
  should "work" do
224
197
  @lounge.messages.first(:order => "position asc").should == @lm1
@@ -230,17 +203,6 @@ class ManyPolymorphicProxyTest < Test::Unit::TestCase
230
203
  end
231
204
  end
232
205
 
233
- context "with :last" do
234
- should "work" do
235
- @lounge.messages.find(:last, :order => "position asc").should == @lm2
236
- end
237
-
238
- should "work with conditions" do
239
- message = @lounge.messages.find(:last, :body => 'Loungin!', :order => "position asc")
240
- message.should == @lm1
241
- end
242
- end
243
-
244
206
  context "with #last" do
245
207
  should "work" do
246
208
  @lounge.messages.last(:order => "position asc").should == @lm2
@@ -285,9 +247,9 @@ class ManyPolymorphicProxyTest < Test::Unit::TestCase
285
247
  end
286
248
 
287
249
  should "not work for ids not in association" do
288
- lambda {
250
+ assert_raises(MongoMapper::DocumentNotFound) do
289
251
  @lounge.messages.find!(@lm1._id, @lm2._id, @hm2._id)
290
- }.should raise_error(MongoMapper::DocumentNotFound)
252
+ end
291
253
  end
292
254
  end
293
255
 
@@ -2,18 +2,10 @@ require 'test_helper'
2
2
 
3
3
  class OneProxyTest < Test::Unit::TestCase
4
4
  def setup
5
- @post_class = Class.new do
6
- include MongoMapper::Document
7
- def self.name; 'Post' end
8
- end
9
-
10
- @author_class = Class.new do
11
- include MongoMapper::Document
5
+ @post_class = Doc('Post')
6
+ @author_class = Doc do
12
7
  key :post_id, ObjectId
13
8
  end
14
-
15
- @post_class.collection.remove
16
- @author_class.collection.remove
17
9
  end
18
10
 
19
11
  should "default to nil" do
@@ -21,6 +13,18 @@ class OneProxyTest < Test::Unit::TestCase
21
13
  @post_class.new.author.nil?.should be_true
22
14
  end
23
15
 
16
+ should "send object id to target" do
17
+ @post_class.one :author, :class => @author_class
18
+
19
+ post = @post_class.new
20
+ author = @author_class.new(:name => 'Frank')
21
+ post.author = author
22
+ author.save.should be_true
23
+ post.save.should be_true
24
+
25
+ post.author.object_id.should == post.author.target.object_id
26
+ end
27
+
24
28
  should "be able to replace the association" do
25
29
  @post_class.one :author, :class => @author_class
26
30
 
@@ -47,6 +51,17 @@ class OneProxyTest < Test::Unit::TestCase
47
51
  post.author?.should be_true
48
52
  end
49
53
 
54
+ should "work with criteria" do
55
+ @post_class.one :primary_author, :class => @author_class, :primary => true
56
+ @post_class.one :author, :class => @author_class
57
+
58
+ post = @post_class.create
59
+ author = @author_class.create(:name => 'Frank', :primary => false, :post_id => post.id)
60
+ primary = @author_class.create(:name => 'Bill', :primary => true, :post_id => post.id)
61
+ post.author.should == author
62
+ post.primary_author.should == primary
63
+ end
64
+
50
65
  should "unset the association" do
51
66
  @post_class.one :author, :class => @author_class
52
67
  post = @post_class.new
@@ -2,12 +2,9 @@ require 'test_helper'
2
2
 
3
3
  class BinaryTest < Test::Unit::TestCase
4
4
  should "serialize and deserialize correctly" do
5
- klass = Class.new do
6
- include MongoMapper::Document
7
- set_collection_name 'test'
5
+ klass = Doc do
8
6
  key :contents, Binary
9
7
  end
10
- klass.collection.remove
11
8
 
12
9
  doc = klass.new(:contents => '010101')
13
10
  doc.save
@@ -18,12 +15,9 @@ class BinaryTest < Test::Unit::TestCase
18
15
 
19
16
  context "Saving a document with a blank binary value" do
20
17
  setup do
21
- @document = Class.new do
22
- include MongoMapper::Document
23
- set_collection_name 'test'
18
+ @document = Doc do
24
19
  key :file, Binary
25
20
  end
26
- @document.collection.remove
27
21
  end
28
22
 
29
23
  should "not fail" do
@@ -3,10 +3,7 @@ require 'test_helper'
3
3
  class CallbacksTest < Test::Unit::TestCase
4
4
  context "Defining and running callbacks" do
5
5
  setup do
6
- @document = Class.new do
7
- include MongoMapper::Document
8
- set_collection_name 'test'
9
-
6
+ @document = Doc do
10
7
  key :name, String
11
8
 
12
9
  [ :before_validation_on_create, :before_validation_on_update,
@@ -30,7 +27,6 @@ class CallbacksTest < Test::Unit::TestCase
30
27
  @history = nil
31
28
  end
32
29
  end
33
- @document.collection.remove
34
30
  end
35
31
 
36
32
  should "get the order right for creating documents" do
@@ -1,26 +1,17 @@
1
1
  require 'test_helper'
2
- require 'models'
3
2
 
4
3
  class DirtyTest < Test::Unit::TestCase
5
4
  def setup
6
- @document = Class.new do
7
- include MongoMapper::Document
8
- set_collection_name 'test'
9
- key :phrase, String
10
- end
11
- @document.collection.remove
12
-
13
- Status.collection.remove
14
- Project.collection.remove
5
+ @document = Doc { key :phrase, String }
15
6
  end
16
-
7
+
17
8
  context "marking changes" do
18
9
  should "not happen if there are none" do
19
10
  doc = @document.new
20
11
  doc.phrase_changed?.should be_false
21
12
  doc.phrase_change.should be_nil
22
13
  end
23
-
14
+
24
15
  should "happen when change happens" do
25
16
  doc = @document.new
26
17
  doc.phrase = 'Golly Gee Willikers Batman'
@@ -28,12 +19,12 @@ class DirtyTest < Test::Unit::TestCase
28
19
  doc.phrase_was.should be_nil
29
20
  doc.phrase_change.should == [nil, 'Golly Gee Willikers Batman']
30
21
  end
31
-
22
+
32
23
  should "happen when initializing" do
33
24
  doc = @document.new(:phrase => 'Foo')
34
25
  doc.changed?.should be_true
35
26
  end
36
-
27
+
37
28
  should "clear changes on save" do
38
29
  doc = @document.new
39
30
  doc.phrase = 'Golly Gee Willikers Batman'
@@ -42,7 +33,7 @@ class DirtyTest < Test::Unit::TestCase
42
33
  doc.phrase_changed?.should_not be_true
43
34
  doc.phrase_change.should be_nil
44
35
  end
45
-
36
+
46
37
  should "clear changes on save!" do
47
38
  doc = @document.new
48
39
  doc.phrase = 'Golly Gee Willikers Batman'
@@ -51,15 +42,18 @@ class DirtyTest < Test::Unit::TestCase
51
42
  doc.phrase_changed?.should_not be_true
52
43
  doc.phrase_change.should be_nil
53
44
  end
54
-
45
+
55
46
  should "not happen when loading from database" do
56
47
  doc = @document.create(:phrase => 'Foo')
48
+ doc = @document.find(doc.id)
49
+
50
+ doc.changed?.should be_false
57
51
  doc.phrase = 'Fart'
58
52
  doc.changed?.should be_true
59
53
  doc.reload
60
54
  doc.changed?.should be_false
61
55
  end
62
-
56
+
63
57
  should "happen if changed after loading from database" do
64
58
  doc = @document.create(:phrase => 'Foo')
65
59
  doc.reload
@@ -68,7 +62,7 @@ class DirtyTest < Test::Unit::TestCase
68
62
  doc.changed?.should be_true
69
63
  end
70
64
  end
71
-
65
+
72
66
  context "blank new value and type integer" do
73
67
  should "not mark changes" do
74
68
  @document.key :age, Integer
@@ -115,7 +109,7 @@ class DirtyTest < Test::Unit::TestCase
115
109
  should "be hash of keys with values of changes if there are changes" do
116
110
  doc = @document.new
117
111
  doc.phrase = 'A penny saved is a penny earned.'
118
- doc.changes.should == {'phrase' => [nil, 'A penny saved is a penny earned.']}
112
+ doc.changes['phrase'].should == [nil, 'A penny saved is a penny earned.']
119
113
  end
120
114
  end
121
115
 
@@ -150,10 +144,20 @@ class DirtyTest < Test::Unit::TestCase
150
144
 
151
145
  context "changing a foreign key through association" do
152
146
  should "mark changes" do
153
- status = Status.create(:name => 'Foo')
154
- status.project = Project.create(:name => 'Bar')
155
- status.changed?.should be_true
156
- status.changed.should == %w(project_id)
147
+ project_class = Doc do
148
+ key :name, String
149
+ end
150
+
151
+ milestone_class = Doc do
152
+ key :project_id, ObjectId
153
+ key :name, String
154
+ end
155
+ milestone_class.belongs_to :project, :class => project_class
156
+
157
+ milestone = milestone_class.create(:name => 'Launch')
158
+ milestone.project = project_class.create(:name => 'Harmony')
159
+ milestone.changed?.should be_true
160
+ milestone.changed.should == %w(project_id)
157
161
  end
158
162
  end
159
163
  end
@@ -3,8 +3,7 @@ require 'models'
3
3
 
4
4
  class DocumentTest < Test::Unit::TestCase
5
5
  def setup
6
- @document = Class.new do
7
- include MongoMapper::Document
6
+ @document = Doc do
8
7
  set_collection_name 'users'
9
8
 
10
9
  key :first_name, String
@@ -12,10 +11,9 @@ class DocumentTest < Test::Unit::TestCase
12
11
  key :age, Integer
13
12
  key :date, Date
14
13
  end
15
- @document.collection.remove
16
14
  end
17
15
 
18
- context "Using key with type Array" do
16
+ context "array key" do
19
17
  setup do
20
18
  @document.key :tags, Array
21
19
  end
@@ -62,7 +60,7 @@ class DocumentTest < Test::Unit::TestCase
62
60
  end
63
61
  end
64
62
 
65
- context "Using key with type Hash" do
63
+ context "hash key" do
66
64
  setup do
67
65
  @document.key :foo, Hash
68
66
  end
@@ -97,7 +95,7 @@ class DocumentTest < Test::Unit::TestCase
97
95
  end
98
96
  end
99
97
 
100
- context "Using key with custom type with default" do
98
+ context "custom type key with default" do
101
99
  setup do
102
100
  @document.key :window, WindowSize, :default => WindowSize.new(600, 480)
103
101
  end
@@ -143,12 +141,7 @@ class DocumentTest < Test::Unit::TestCase
143
141
  end
144
142
 
145
143
  should "not fail if no attributes provided" do
146
- document = Class.new do
147
- include MongoMapper::Document
148
- set_collection_name 'test'
149
- end
150
- document.collection.remove
151
-
144
+ document = Doc()
152
145
  lambda { document.create }.should change { document.count }.by(1)
153
146
  end
154
147
  end
@@ -246,7 +239,19 @@ class DocumentTest < Test::Unit::TestCase
246
239
  end
247
240
 
248
241
  should "raise document not found if nothing provided for find!" do
249
- lambda { @document.find! }.should raise_error(MongoMapper::DocumentNotFound)
242
+ assert_raises(MongoMapper::DocumentNotFound) do
243
+ @document.find!
244
+ end
245
+ end
246
+
247
+ should "raise error if trying to find with :all, :first, or :last" do
248
+ [:all, :first, :last].each do |m|
249
+ assert_raises(ArgumentError) { @document.find(m) }
250
+ end
251
+
252
+ [:all, :first, :last].each do |m|
253
+ assert_raises(ArgumentError) { @document.find!(m) }
254
+ end
250
255
  end
251
256
 
252
257
  context "(with a single id)" do
@@ -259,9 +264,7 @@ class DocumentTest < Test::Unit::TestCase
259
264
  end
260
265
 
261
266
  should "raise error if document not found with find!" do
262
- lambda {
263
- @document.find!(123)
264
- }.should raise_error(MongoMapper::DocumentNotFound)
267
+ assert_raises(MongoMapper::DocumentNotFound) { @document.find!(123) }
265
268
  end
266
269
  end
267
270
 
@@ -274,7 +277,17 @@ class DocumentTest < Test::Unit::TestCase
274
277
  @document.find([@doc1._id, @doc2._id]).should == [@doc1, @doc2]
275
278
  end
276
279
 
277
- should "return array if array only has one element" do
280
+ should "compact not found when using find" do
281
+ @document.find(@doc1._id, 1234).should == [@doc1]
282
+ end
283
+
284
+ should "raise error if not all found when using find!" do
285
+ assert_raises(MongoMapper::DocumentNotFound) do
286
+ @document.find!(@doc1._id, 1234)
287
+ end
288
+ end
289
+
290
+ should "return array if array with one element" do
278
291
  @document.find([@doc1._id]).should == [@doc1]
279
292
  end
280
293
  end
@@ -284,43 +297,21 @@ class DocumentTest < Test::Unit::TestCase
284
297
  @document.all(:last_name => 'Nunemaker', :order => 'age desc').should == [@doc1, @doc3]
285
298
  end
286
299
 
287
- context "(with :all)" do
288
- should "find all documents" do
289
- @document.find(:all, :order => 'first_name').should == [@doc1, @doc3, @doc2]
290
- end
291
-
292
- should "be able to add conditions" do
293
- @document.find(:all, :first_name => 'John').should == [@doc1]
294
- end
295
- end
296
-
297
- context "(with #all)" do
300
+ context "#all" do
298
301
  should "find all documents based on criteria" do
299
302
  @document.all(:order => 'first_name').should == [@doc1, @doc3, @doc2]
300
303
  @document.all(:last_name => 'Nunemaker', :order => 'age desc').should == [@doc1, @doc3]
301
304
  end
302
305
  end
303
306
 
304
- context "(with :first)" do
305
- should "find first document" do
306
- @document.find(:first, :order => 'first_name').should == @doc1
307
- end
308
- end
309
-
310
- context "(with #first)" do
307
+ context "#first" do
311
308
  should "find first document based on criteria" do
312
309
  @document.first(:order => 'first_name').should == @doc1
313
310
  @document.first(:age => 28).should == @doc2
314
311
  end
315
312
  end
316
313
 
317
- context "(with :last)" do
318
- should "find last document" do
319
- @document.find(:last, :order => 'age').should == @doc2
320
- end
321
- end
322
-
323
- context "(with #last)" do
314
+ context "#last" do
324
315
  should "find last document based on criteria" do
325
316
  @document.last(:order => 'age').should == @doc2
326
317
  @document.last(:order => 'age', :age => 28).should == @doc2
@@ -331,7 +322,7 @@ class DocumentTest < Test::Unit::TestCase
331
322
  end
332
323
  end
333
324
 
334
- context "(with :find_by)" do
325
+ context "#find_by..." do
335
326
  should "find document based on argument" do
336
327
  @document.find_by_first_name('John').should == @doc1
337
328
  @document.find_by_last_name('Nunemaker', :order => 'age desc').should == @doc1
@@ -347,7 +338,19 @@ class DocumentTest < Test::Unit::TestCase
347
338
  end
348
339
  end
349
340
 
350
- context "(with dynamic finders)" do
341
+ context "#find_each" do
342
+ should "yield all documents found, based on criteria" do
343
+ yield_documents = []
344
+ @document.find_each(:order => "first_name") {|doc| yield_documents << doc }
345
+ yield_documents.should == [@doc1, @doc3, @doc2]
346
+
347
+ yield_documents = []
348
+ @document.find_each(:last_name => 'Nunemaker', :order => 'age desc') {|doc| yield_documents << doc }
349
+ yield_documents.should == [@doc1, @doc3]
350
+ end
351
+ end
352
+
353
+ context "dynamic finders" do
351
354
  should "find document based on all arguments" do
352
355
  @document.find_by_first_name_and_last_name_and_age('John', 'Nunemaker', 27).should == @doc1
353
356
  end
@@ -400,7 +403,44 @@ class DocumentTest < Test::Unit::TestCase
400
403
  end
401
404
 
402
405
  should "return nil if document not found" do
403
- @document.find_by_id(1234).should be(nil)
406
+ @document.find_by_id(1234).should be_nil
407
+ end
408
+ end
409
+
410
+ context "first_or_create" do
411
+ should "find if exists" do
412
+ created = @document.create(:first_name => 'John', :last_name => 'Nunemaker')
413
+ lambda {
414
+ found = @document.first_or_create(:first_name => 'John', :last_name => 'Nunemaker')
415
+ found.should == created
416
+ }.should_not change { @document.count }
417
+ end
418
+
419
+ should "create if not found" do
420
+ lambda {
421
+ created = @document.first_or_create(:first_name => 'John', :last_name => 'Nunemaker')
422
+ created.first_name.should == 'John'
423
+ created.last_name.should == 'Nunemaker'
424
+ }.should change { @document.count }.by(1)
425
+ end
426
+ end
427
+
428
+ context "first_or_new" do
429
+ should "find if exists" do
430
+ created = @document.create(:first_name => 'John', :last_name => 'Nunemaker')
431
+ lambda {
432
+ found = @document.first_or_new(:first_name => 'John', :last_name => 'Nunemaker')
433
+ found.should == created
434
+ }.should_not change { @document.count }
435
+ end
436
+
437
+ should "initialize if not found" do
438
+ lambda {
439
+ created = @document.first_or_new(:first_name => 'John', :last_name => 'Nunemaker')
440
+ created.first_name.should == 'John'
441
+ created.last_name.should == 'Nunemaker'
442
+ created.should be_new
443
+ }.should_not change { @document.count }
404
444
  end
405
445
  end
406
446
 
@@ -541,11 +581,9 @@ class DocumentTest < Test::Unit::TestCase
541
581
  end
542
582
 
543
583
  should "return 0 if the collection does not exist" do
544
- klass = Class.new do
545
- include MongoMapper::Document
584
+ klass = Doc do
546
585
  set_collection_name 'foobarbazwickdoesnotexist'
547
586
  end
548
- @document.collection.remove
549
587
 
550
588
  klass.count.should == 0
551
589
  end
@@ -567,10 +605,10 @@ class DocumentTest < Test::Unit::TestCase
567
605
  @document.new.database.should == @document.database
568
606
  end
569
607
 
570
- context "#save (new document)" do
608
+ context "#update_attributes (new document)" do
571
609
  setup do
572
610
  @doc = @document.new(:first_name => 'John', :age => '27')
573
- @doc.save
611
+ @doc.update_attributes(:first_name => 'Johnny', :age => 30)
574
612
  end
575
613
 
576
614
  should "insert document into the collection" do
@@ -582,46 +620,27 @@ class DocumentTest < Test::Unit::TestCase
582
620
  end
583
621
 
584
622
  should "save attributes" do
585
- @doc.first_name.should == 'John'
586
- @doc.age.should == 27
623
+ @doc.first_name.should == 'Johnny'
624
+ @doc.age.should == 30
587
625
  end
588
626
 
589
627
  should "update attributes in the database" do
590
628
  doc = @doc.reload
591
629
  doc.should == @doc
592
- doc.first_name.should == 'John'
593
- doc.age.should == 27
594
- end
595
-
596
- should "allow to add custom attributes to the document" do
597
- @doc = @document.new(:first_name => 'David', :age => '26', :gender => 'male', :tags => [1, "2"])
598
- @doc.save
599
- doc = @doc.reload
600
- doc.gender.should == 'male'
601
- doc.tags.should == [1, "2"]
602
- end
603
-
604
- should "allow to use custom methods to assign properties" do
605
- person = RealPerson.new(:realname => 'David')
606
- person.save
607
- person.reload.name.should == 'David'
630
+ doc.first_name.should == 'Johnny'
631
+ doc.age.should == 30
608
632
  end
609
633
 
610
- context "with key of type date" do
611
- should "save the date value as a Time object" do
612
- doc = @document.new(:first_name => 'John', :age => '27', :date => "12/01/2009")
613
- doc.save
614
- doc.date.should == Date.new(2009, 12, 1)
615
- end
634
+ should "allow updating custom attributes" do
635
+ @doc.update_attributes(:gender => 'mALe')
636
+ @doc.reload.gender.should == 'mALe'
616
637
  end
617
638
  end
618
639
 
619
- context "#save (existing document)" do
640
+ context "#update_attributes (existing document)" do
620
641
  setup do
621
642
  @doc = @document.create(:first_name => 'John', :age => '27')
622
- @doc.first_name = 'Johnny'
623
- @doc.age = 30
624
- @doc.save
643
+ @doc.update_attributes(:first_name => 'Johnny', :age => 30)
625
644
  end
626
645
 
627
646
  should "not insert document into collection" do
@@ -638,19 +657,26 @@ class DocumentTest < Test::Unit::TestCase
638
657
  doc.first_name.should == 'Johnny'
639
658
  doc.age.should == 30
640
659
  end
660
+ end
641
661
 
642
- should "allow updating custom attributes" do
643
- @doc = @document.new(:first_name => 'David', :age => '26', :gender => 'male')
644
- @doc.gender = 'Male'
645
- @doc.save
646
- @doc.reload.gender.should == 'Male'
662
+ context "#update_attributes (return value)" do
663
+ setup do
664
+ @document.key :foo, String, :required => true
647
665
  end
648
- end
649
666
 
650
- context "#update_attributes (new document)" do
667
+ should "be true if document valid" do
668
+ @document.new.update_attributes(:foo => 'bar').should be_true
669
+ end
670
+
671
+ should "be false if document not valid" do
672
+ @document.new.update_attributes({}).should be_false
673
+ end
674
+ end
675
+
676
+ context "#save (new document)" do
651
677
  setup do
652
678
  @doc = @document.new(:first_name => 'John', :age => '27')
653
- @doc.update_attributes(:first_name => 'Johnny', :age => 30)
679
+ @doc.save
654
680
  end
655
681
 
656
682
  should "insert document into the collection" do
@@ -662,27 +688,54 @@ class DocumentTest < Test::Unit::TestCase
662
688
  end
663
689
 
664
690
  should "save attributes" do
665
- @doc.first_name.should == 'Johnny'
666
- @doc.age.should == 30
691
+ @doc.first_name.should == 'John'
692
+ @doc.age.should == 27
667
693
  end
668
694
 
669
695
  should "update attributes in the database" do
670
696
  doc = @doc.reload
671
697
  doc.should == @doc
672
- doc.first_name.should == 'Johnny'
673
- doc.age.should == 30
698
+ doc.first_name.should == 'John'
699
+ doc.age.should == 27
674
700
  end
675
701
 
676
- should "allow updating custom attributes" do
677
- @doc.update_attributes(:gender => 'mALe')
678
- @doc.reload.gender.should == 'mALe'
702
+ should "allow to add custom attributes to the document" do
703
+ @doc = @document.new(:first_name => 'David', :age => '26', :gender => 'male', :tags => [1, "2"])
704
+ @doc.save
705
+ doc = @doc.reload
706
+ doc.gender.should == 'male'
707
+ doc.tags.should == [1, "2"]
708
+ end
709
+
710
+ should "allow to use custom methods to assign properties" do
711
+ klass = Doc do
712
+ key :name, String
713
+
714
+ def realname=(value)
715
+ self.name = value
716
+ end
717
+ end
718
+
719
+ person = klass.new(:realname => 'David')
720
+ person.save
721
+ person.reload.name.should == 'David'
722
+ end
723
+
724
+ context "with key of type date" do
725
+ should "save the date value as a Time object" do
726
+ doc = @document.new(:first_name => 'John', :age => '27', :date => "12/01/2009")
727
+ doc.save
728
+ doc.date.should == Date.new(2009, 12, 1)
729
+ end
679
730
  end
680
731
  end
681
732
 
682
- context "#update_attributes (existing document)" do
733
+ context "#save (existing document)" do
683
734
  setup do
684
735
  @doc = @document.create(:first_name => 'John', :age => '27')
685
- @doc.update_attributes(:first_name => 'Johnny', :age => 30)
736
+ @doc.first_name = 'Johnny'
737
+ @doc.age = 30
738
+ @doc.save
686
739
  end
687
740
 
688
741
  should "not insert document into collection" do
@@ -699,68 +752,82 @@ class DocumentTest < Test::Unit::TestCase
699
752
  doc.first_name.should == 'Johnny'
700
753
  doc.age.should == 30
701
754
  end
702
- end
703
-
704
- context "#update_attributes" do
705
- setup do
706
- @document.key :foo, String, :required => true
707
- end
708
-
709
- should "return true if document valid" do
710
- @document.new.update_attributes(:foo => 'bar').should be_true
711
- end
712
755
 
713
- should "return false if document not valid" do
714
- @document.new.update_attributes({}).should be_false
756
+ should "allow updating custom attributes" do
757
+ @doc = @document.new(:first_name => 'David', :age => '26', :gender => 'male')
758
+ @doc.gender = 'Male'
759
+ @doc.save
760
+ @doc.reload.gender.should == 'Male'
715
761
  end
716
762
  end
717
-
763
+
718
764
  context "#save (with validations off)" do
719
765
  setup do
720
- @document = Class.new do
721
- include MongoMapper::Document
722
- set_collection_name 'test'
766
+ @document = Doc do
723
767
  key :name, String, :required => true
724
768
  end
725
- @document.collection.remove
726
769
  end
727
770
 
728
- should "insert document" do
771
+ should "insert invalid document" do
729
772
  doc = @document.new
773
+ doc.expects(:valid?).never
730
774
  doc.save(:validate => false)
731
775
  @document.count.should == 1
732
776
  end
733
-
734
- should "work with false passed to save" do
735
- doc = @document.new
736
- doc.save(false)
737
- @document.count.should == 1
738
- end
739
777
  end
740
778
 
741
779
  context "#save (with options)" do
742
780
  setup do
743
- MongoMapper.ensured_indexes = []
744
-
745
- @document = Class.new do
746
- include MongoMapper::Document
747
- set_collection_name 'test'
781
+ @document = Doc do
748
782
  key :name, String
749
- ensure_index :name, :unique => true
783
+ set_collection_name 'test_indexes'
750
784
  end
751
- @document.collection.drop_indexes
752
-
753
- MongoMapper.ensure_indexes!
785
+ drop_indexes(@document)
786
+ @document.ensure_index :name, :unique => true
754
787
  end
755
-
788
+
756
789
  should "allow passing safe" do
757
- doc = @document.new(:name => 'John')
758
- doc.save
759
-
790
+ @document.create(:name => 'John')
760
791
  assert_raises(Mongo::OperationFailure) do
761
792
  @document.new(:name => 'John').save(:safe => true)
762
793
  end
763
794
  end
795
+
796
+ should "raise argument error if options has unsupported key" do
797
+ assert_raises(ArgumentError) do
798
+ @document.new.save(:foo => true)
799
+ end
800
+ end
801
+ end
802
+
803
+ context "#save! (with options)" do
804
+ setup do
805
+ @document = Doc do
806
+ key :name, String
807
+ set_collection_name 'test_indexes'
808
+ end
809
+ drop_indexes(@document)
810
+ @document.ensure_index :name, :unique => true
811
+ end
812
+
813
+ should "allow passing safe" do
814
+ @document.create(:name => 'John')
815
+ assert_raises(Mongo::OperationFailure) do
816
+ @document.new(:name => 'John').save!(:safe => true)
817
+ end
818
+ end
819
+
820
+ should "raise argument error if options has unsupported key" do
821
+ assert_raises(ArgumentError) do
822
+ @document.new.save!(:foo => true)
823
+ end
824
+ end
825
+
826
+ should "raise argument error if using validate as that would be pointless with save!" do
827
+ assert_raises(ArgumentError) do
828
+ @document.new.save!(:validate => false)
829
+ end
830
+ end
764
831
  end
765
832
 
766
833
  context "#destroy" do
@@ -843,7 +910,7 @@ class DocumentTest < Test::Unit::TestCase
843
910
  @parent.save
844
911
  @daughter.save
845
912
 
846
- collection = DocParent.find(:all)
913
+ collection = DocParent.all
847
914
  collection.size.should == 2
848
915
  collection.first.should be_kind_of(DocParent)
849
916
  collection.first.name.should == "Daddy Warbucks"
@@ -967,11 +1034,19 @@ class DocumentTest < Test::Unit::TestCase
967
1034
 
968
1035
  context "timestamping" do
969
1036
  setup do
970
- @document.timestamps!
1037
+ @klass = Doc do
1038
+ set_collection_name 'users'
1039
+
1040
+ key :first_name, String
1041
+ key :last_name, String
1042
+ key :age, Integer
1043
+ key :date, Date
1044
+ end
1045
+ @klass.timestamps!
971
1046
  end
972
1047
 
973
1048
  should "set created_at and updated_at on create" do
974
- doc = @document.new(:first_name => 'John', :age => 27)
1049
+ doc = @klass.new(:first_name => 'John', :age => 27)
975
1050
  doc.created_at.should be(nil)
976
1051
  doc.updated_at.should be(nil)
977
1052
  doc.save
@@ -981,7 +1056,7 @@ class DocumentTest < Test::Unit::TestCase
981
1056
 
982
1057
  should "not overwrite created_at if it already exists" do
983
1058
  original_created_at = 1.month.ago
984
- doc = @document.new(:first_name => 'John', :age => 27, :created_at => original_created_at)
1059
+ doc = @klass.new(:first_name => 'John', :age => 27, :created_at => original_created_at)
985
1060
  doc.created_at.to_i.should == original_created_at.to_i
986
1061
  doc.updated_at.should be_nil
987
1062
  doc.save
@@ -990,7 +1065,7 @@ class DocumentTest < Test::Unit::TestCase
990
1065
  end
991
1066
 
992
1067
  should "set updated_at on field update but leave created_at alone" do
993
- doc = @document.create(:first_name => 'John', :age => 27)
1068
+ doc = @klass.create(:first_name => 'John', :age => 27)
994
1069
  old_created_at = doc.created_at
995
1070
  old_updated_at = doc.updated_at
996
1071
  doc.first_name = 'Johnny'
@@ -1004,12 +1079,12 @@ class DocumentTest < Test::Unit::TestCase
1004
1079
  end
1005
1080
 
1006
1081
  should "set updated_at on document update but leave created_at alone" do
1007
- doc = @document.create(:first_name => 'John', :age => 27)
1082
+ doc = @klass.create(:first_name => 'John', :age => 27)
1008
1083
  old_created_at = doc.created_at
1009
1084
  old_updated_at = doc.updated_at
1010
1085
 
1011
1086
  Timecop.freeze(Time.now + 5.seconds) do
1012
- @document.update(doc._id, { :first_name => 'Johnny' })
1087
+ @klass.update(doc._id, { :first_name => 'Johnny' })
1013
1088
  end
1014
1089
 
1015
1090
  doc = doc.reload
@@ -1040,7 +1115,7 @@ class DocumentTest < Test::Unit::TestCase
1040
1115
  end
1041
1116
  end
1042
1117
 
1043
- context "#exist?" do
1118
+ context "#exists?" do
1044
1119
  setup do
1045
1120
  @doc = @document.create(:first_name => "James", :age => 27)
1046
1121
  end
@@ -1065,14 +1140,11 @@ class DocumentTest < Test::Unit::TestCase
1065
1140
 
1066
1141
  context "#reload" do
1067
1142
  setup do
1068
- @foo_class = Class.new do
1069
- include MongoMapper::Document
1143
+ @foo_class = Doc do
1070
1144
  key :name
1071
1145
  end
1072
- @foo_class.collection.remove
1073
1146
 
1074
- @bar_class = Class.new do
1075
- include MongoMapper::EmbeddedDocument
1147
+ @bar_class = EDoc do
1076
1148
  key :name
1077
1149
  end
1078
1150
 
@@ -1107,19 +1179,14 @@ class DocumentTest < Test::Unit::TestCase
1107
1179
  should "return self" do
1108
1180
  @instance.reload.object_id.should == @instance.object_id
1109
1181
  end
1110
- end
1111
-
1112
- context "Saving a document with a custom id" do
1113
- should "clear custom id flag when saved" do
1114
- @document.key :_id, String
1115
- doc = @document.new(:id => '1234')
1116
- doc.using_custom_id?.should be_true
1117
- doc.save.should be_true
1118
- doc.using_custom_id?.should be_false
1182
+
1183
+ should "raise DocumentNotFound if not found" do
1184
+ @instance.destroy
1185
+ assert_raises(MongoMapper::DocumentNotFound) { @instance.reload }
1119
1186
  end
1120
1187
  end
1121
1188
 
1122
- context "Loading a document from the database with keys that are not defined" do
1189
+ context "database has keys not defined in model" do
1123
1190
  setup do
1124
1191
  @id = Mongo::ObjectID.new
1125
1192
  @document.collection.insert({
@@ -1144,28 +1211,22 @@ class DocumentTest < Test::Unit::TestCase
1144
1211
 
1145
1212
  context "Indexing" do
1146
1213
  setup do
1147
- MongoMapper.ensured_indexes = []
1148
- @document.collection.drop_indexes
1214
+ drop_indexes(@document)
1149
1215
  end
1150
1216
 
1151
1217
  should "allow creating index for a key" do
1152
1218
  @document.ensure_index :first_name
1153
- MongoMapper.ensure_indexes!
1154
-
1155
1219
  @document.should have_index('first_name_1')
1156
1220
  end
1157
1221
 
1158
1222
  should "allow creating unique index for a key" do
1159
1223
  @document.ensure_index :first_name, :unique => true
1160
- MongoMapper.ensure_indexes!
1161
-
1162
1224
  @document.should have_index('first_name_1')
1163
1225
  end
1164
1226
 
1165
1227
  should "allow creating index on multiple keys" do
1166
1228
  @document.ensure_index [[:first_name, 1], [:last_name, -1]]
1167
- MongoMapper.ensure_indexes!
1168
-
1229
+
1169
1230
  # order is different for different versions of ruby so instead of
1170
1231
  # just checking have_index('first_name_1_last_name_-1') I'm checking
1171
1232
  # the values of the indexes to make sure the index creation was successful
@@ -1177,8 +1238,6 @@ class DocumentTest < Test::Unit::TestCase
1177
1238
 
1178
1239
  should "work with :index shortcut when defining key" do
1179
1240
  @document.key :father, String, :index => true
1180
- MongoMapper.ensure_indexes!
1181
-
1182
1241
  @document.should have_index('father_1')
1183
1242
  end
1184
1243
  end