jamieorc-mongo_mapper 0.11.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +33 -0
  3. data/UPGRADES +26 -0
  4. data/bin/mmconsole +59 -0
  5. data/examples/attr_accessible.rb +22 -0
  6. data/examples/attr_protected.rb +22 -0
  7. data/examples/cache_key.rb +24 -0
  8. data/examples/custom_types.rb +24 -0
  9. data/examples/identity_map.rb +33 -0
  10. data/examples/identity_map/automatic.rb +2 -0
  11. data/examples/keys.rb +40 -0
  12. data/examples/modifiers/set.rb +25 -0
  13. data/examples/plugins.rb +38 -0
  14. data/examples/querying.rb +35 -0
  15. data/examples/safe.rb +43 -0
  16. data/examples/scopes.rb +52 -0
  17. data/examples/validating/embedded_docs.rb +29 -0
  18. data/lib/mongo_mapper.rb +94 -0
  19. data/lib/mongo_mapper/connection.rb +96 -0
  20. data/lib/mongo_mapper/document.rb +42 -0
  21. data/lib/mongo_mapper/embedded_document.rb +32 -0
  22. data/lib/mongo_mapper/exceptions.rb +27 -0
  23. data/lib/mongo_mapper/extensions/array.rb +19 -0
  24. data/lib/mongo_mapper/extensions/binary.rb +22 -0
  25. data/lib/mongo_mapper/extensions/boolean.rb +44 -0
  26. data/lib/mongo_mapper/extensions/date.rb +25 -0
  27. data/lib/mongo_mapper/extensions/float.rb +14 -0
  28. data/lib/mongo_mapper/extensions/hash.rb +14 -0
  29. data/lib/mongo_mapper/extensions/integer.rb +19 -0
  30. data/lib/mongo_mapper/extensions/kernel.rb +9 -0
  31. data/lib/mongo_mapper/extensions/nil_class.rb +18 -0
  32. data/lib/mongo_mapper/extensions/object.rb +26 -0
  33. data/lib/mongo_mapper/extensions/object_id.rb +32 -0
  34. data/lib/mongo_mapper/extensions/set.rb +20 -0
  35. data/lib/mongo_mapper/extensions/string.rb +18 -0
  36. data/lib/mongo_mapper/extensions/time.rb +28 -0
  37. data/lib/mongo_mapper/locale/en.yml +5 -0
  38. data/lib/mongo_mapper/middleware/identity_map.rb +16 -0
  39. data/lib/mongo_mapper/plugins.rb +22 -0
  40. data/lib/mongo_mapper/plugins/accessible.rb +36 -0
  41. data/lib/mongo_mapper/plugins/active_model.rb +18 -0
  42. data/lib/mongo_mapper/plugins/associations.rb +90 -0
  43. data/lib/mongo_mapper/plugins/associations/base.rb +92 -0
  44. data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +53 -0
  45. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +34 -0
  46. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +52 -0
  47. data/lib/mongo_mapper/plugins/associations/collection.rb +27 -0
  48. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +44 -0
  49. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +133 -0
  50. data/lib/mongo_mapper/plugins/associations/many_association.rb +63 -0
  51. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  52. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +117 -0
  53. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +32 -0
  54. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +24 -0
  55. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +14 -0
  56. data/lib/mongo_mapper/plugins/associations/one_as_proxy.rb +22 -0
  57. data/lib/mongo_mapper/plugins/associations/one_association.rb +48 -0
  58. data/lib/mongo_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb +30 -0
  59. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +44 -0
  60. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +95 -0
  61. data/lib/mongo_mapper/plugins/associations/proxy.rb +134 -0
  62. data/lib/mongo_mapper/plugins/associations/single_association.rb +46 -0
  63. data/lib/mongo_mapper/plugins/caching.rb +21 -0
  64. data/lib/mongo_mapper/plugins/callbacks.rb +29 -0
  65. data/lib/mongo_mapper/plugins/clone.rb +22 -0
  66. data/lib/mongo_mapper/plugins/dirty.rb +60 -0
  67. data/lib/mongo_mapper/plugins/document.rb +41 -0
  68. data/lib/mongo_mapper/plugins/dynamic_querying.rb +45 -0
  69. data/lib/mongo_mapper/plugins/dynamic_querying/dynamic_finder.rb +44 -0
  70. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +56 -0
  71. data/lib/mongo_mapper/plugins/embedded_document.rb +53 -0
  72. data/lib/mongo_mapper/plugins/equality.rb +23 -0
  73. data/lib/mongo_mapper/plugins/identity_map.rb +128 -0
  74. data/lib/mongo_mapper/plugins/indexes.rb +13 -0
  75. data/lib/mongo_mapper/plugins/inspect.rb +16 -0
  76. data/lib/mongo_mapper/plugins/keys.rb +313 -0
  77. data/lib/mongo_mapper/plugins/keys/key.rb +61 -0
  78. data/lib/mongo_mapper/plugins/logger.rb +18 -0
  79. data/lib/mongo_mapper/plugins/modifiers.rb +132 -0
  80. data/lib/mongo_mapper/plugins/pagination.rb +16 -0
  81. data/lib/mongo_mapper/plugins/persistence.rb +69 -0
  82. data/lib/mongo_mapper/plugins/protected.rb +45 -0
  83. data/lib/mongo_mapper/plugins/querying.rb +171 -0
  84. data/lib/mongo_mapper/plugins/querying/decorator.rb +34 -0
  85. data/lib/mongo_mapper/plugins/querying/plucky_methods.rb +21 -0
  86. data/lib/mongo_mapper/plugins/rails.rb +58 -0
  87. data/lib/mongo_mapper/plugins/rails/active_record_association_adapter.rb +33 -0
  88. data/lib/mongo_mapper/plugins/safe.rb +28 -0
  89. data/lib/mongo_mapper/plugins/sci.rb +36 -0
  90. data/lib/mongo_mapper/plugins/scopes.rb +27 -0
  91. data/lib/mongo_mapper/plugins/serialization.rb +109 -0
  92. data/lib/mongo_mapper/plugins/timestamps.rb +22 -0
  93. data/lib/mongo_mapper/plugins/touch.rb +18 -0
  94. data/lib/mongo_mapper/plugins/userstamps.rb +18 -0
  95. data/lib/mongo_mapper/plugins/validations.rb +86 -0
  96. data/lib/mongo_mapper/railtie.rb +48 -0
  97. data/lib/mongo_mapper/railtie/database.rake +65 -0
  98. data/lib/mongo_mapper/translation.rb +10 -0
  99. data/lib/mongo_mapper/version.rb +4 -0
  100. data/lib/rails/generators/mongo_mapper/config/config_generator.rb +24 -0
  101. data/lib/rails/generators/mongo_mapper/config/templates/mongo.yml +18 -0
  102. data/lib/rails/generators/mongo_mapper/model/model_generator.rb +23 -0
  103. data/lib/rails/generators/mongo_mapper/model/templates/model.rb +13 -0
  104. data/test/_NOTE_ON_TESTING +1 -0
  105. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +64 -0
  106. data/test/functional/associations/test_belongs_to_proxy.rb +238 -0
  107. data/test/functional/associations/test_in_array_proxy.rb +349 -0
  108. data/test/functional/associations/test_many_documents_as_proxy.rb +229 -0
  109. data/test/functional/associations/test_many_documents_proxy.rb +866 -0
  110. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +239 -0
  111. data/test/functional/associations/test_many_embedded_proxy.rb +289 -0
  112. data/test/functional/associations/test_many_polymorphic_proxy.rb +303 -0
  113. data/test/functional/associations/test_one_as_proxy.rb +491 -0
  114. data/test/functional/associations/test_one_embedded_polymorphic_proxy.rb +208 -0
  115. data/test/functional/associations/test_one_embedded_proxy.rb +100 -0
  116. data/test/functional/associations/test_one_proxy.rb +383 -0
  117. data/test/functional/test_accessible.rb +198 -0
  118. data/test/functional/test_associations.rb +46 -0
  119. data/test/functional/test_binary.rb +27 -0
  120. data/test/functional/test_caching.rb +77 -0
  121. data/test/functional/test_callbacks.rb +232 -0
  122. data/test/functional/test_dirty.rb +301 -0
  123. data/test/functional/test_document.rb +282 -0
  124. data/test/functional/test_dynamic_querying.rb +75 -0
  125. data/test/functional/test_embedded_document.rb +288 -0
  126. data/test/functional/test_equality.rb +20 -0
  127. data/test/functional/test_identity_map.rb +513 -0
  128. data/test/functional/test_indexes.rb +50 -0
  129. data/test/functional/test_logger.rb +20 -0
  130. data/test/functional/test_modifiers.rb +483 -0
  131. data/test/functional/test_pagination.rb +91 -0
  132. data/test/functional/test_protected.rb +201 -0
  133. data/test/functional/test_querying.rb +935 -0
  134. data/test/functional/test_safe.rb +76 -0
  135. data/test/functional/test_sci.rb +240 -0
  136. data/test/functional/test_scopes.rb +171 -0
  137. data/test/functional/test_timestamps.rb +62 -0
  138. data/test/functional/test_touch.rb +125 -0
  139. data/test/functional/test_userstamps.rb +44 -0
  140. data/test/functional/test_validations.rb +405 -0
  141. data/test/models.rb +261 -0
  142. data/test/support/railtie.rb +4 -0
  143. data/test/support/railtie/autoloaded.rb +2 -0
  144. data/test/support/railtie/not_autoloaded.rb +3 -0
  145. data/test/support/railtie/parent.rb +3 -0
  146. data/test/test_active_model_lint.rb +18 -0
  147. data/test/test_helper.rb +94 -0
  148. data/test/unit/associations/test_base.rb +146 -0
  149. data/test/unit/associations/test_belongs_to_association.rb +29 -0
  150. data/test/unit/associations/test_many_association.rb +63 -0
  151. data/test/unit/associations/test_one_association.rb +47 -0
  152. data/test/unit/associations/test_proxy.rb +100 -0
  153. data/test/unit/serializers/test_json_serializer.rb +216 -0
  154. data/test/unit/serializers/test_xml_serializer.rb +196 -0
  155. data/test/unit/test_clone.rb +69 -0
  156. data/test/unit/test_document.rb +249 -0
  157. data/test/unit/test_dynamic_finder.rb +125 -0
  158. data/test/unit/test_embedded_document.rb +682 -0
  159. data/test/unit/test_equality.rb +38 -0
  160. data/test/unit/test_extensions.rb +380 -0
  161. data/test/unit/test_identity_map_middleware.rb +34 -0
  162. data/test/unit/test_inspect.rb +47 -0
  163. data/test/unit/test_key.rb +205 -0
  164. data/test/unit/test_keys.rb +65 -0
  165. data/test/unit/test_mongo_mapper.rb +143 -0
  166. data/test/unit/test_pagination.rb +11 -0
  167. data/test/unit/test_plugins.rb +89 -0
  168. data/test/unit/test_rails.rb +183 -0
  169. data/test/unit/test_rails_compatibility.rb +38 -0
  170. data/test/unit/test_rails_reflect_on_association.rb +118 -0
  171. data/test/unit/test_railtie.rb +61 -0
  172. data/test/unit/test_serialization.rb +166 -0
  173. data/test/unit/test_time_zones.rb +44 -0
  174. data/test/unit/test_translation.rb +27 -0
  175. data/test/unit/test_validations.rb +562 -0
  176. metadata +262 -0
@@ -0,0 +1,866 @@
1
+ require 'test_helper.rb'
2
+ require 'models'
3
+
4
+ class ManyDocumentsProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ Project.collection.remove
7
+ Status.collection.remove
8
+
9
+ @pet_class = Doc do
10
+ key :name, String
11
+ key :owner_id, ObjectId
12
+ end
13
+
14
+ @owner_class = Doc do
15
+ key :name, String
16
+ end
17
+ @owner_class.many :pets, :class => @pet_class, :foreign_key => :owner_id, :order => 'name'
18
+ end
19
+
20
+ should "default reader to empty array" do
21
+ project = Project.new
22
+ project.statuses.should == []
23
+ end
24
+
25
+ should "allow overriding association methods" do
26
+ @owner_class.class_eval do
27
+ def pets
28
+ super
29
+ end
30
+ end
31
+
32
+ instance = @owner_class.new
33
+ instance.pets.should == []
34
+ instance.pets.build
35
+ instance.pets.should_not be_empty
36
+ end
37
+
38
+ should "allow assignment of many associated documents using a hash" do
39
+ person_attributes = {
40
+ 'name' => 'Mr. Pet Lover',
41
+ 'pets' => [
42
+ {'name' => 'Jimmy', 'species' => 'Cocker Spainel'},
43
+ {'name' => 'Sasha', 'species' => 'Siberian Husky'},
44
+ ]
45
+ }
46
+
47
+ owner = @owner_class.new(person_attributes)
48
+ owner.name.should == 'Mr. Pet Lover'
49
+ owner.pets[0].name.should == 'Jimmy'
50
+ owner.pets[0].species.should == 'Cocker Spainel'
51
+ owner.pets[1].name.should == 'Sasha'
52
+ owner.pets[1].species.should == 'Siberian Husky'
53
+
54
+ owner.save.should be_true
55
+ owner.reload
56
+
57
+ owner.name.should == 'Mr. Pet Lover'
58
+ owner.pets[0].name.should == 'Jimmy'
59
+ owner.pets[0].species.should == 'Cocker Spainel'
60
+ owner.pets[1].name.should == 'Sasha'
61
+ owner.pets[1].species.should == 'Siberian Husky'
62
+ end
63
+
64
+ should "allow adding to association like it was an array" do
65
+ project = Project.new
66
+ project.statuses << Status.new(:name => 'Foo1!')
67
+ project.statuses.push Status.new(:name => 'Foo2!')
68
+ project.statuses.concat Status.new(:name => 'Foo3!')
69
+ project.statuses.size.should == 3
70
+ end
71
+
72
+ context "replacing the association" do
73
+ context "with objects of the class" do
74
+ should "work" do
75
+ project = Project.new
76
+ project.statuses = [Status.new(:name => "ready")]
77
+ project.save.should be_true
78
+
79
+ project.reload
80
+ project.statuses.size.should == 1
81
+ project.statuses[0].name.should == "ready"
82
+ end
83
+ end
84
+
85
+ context "with Hashes" do
86
+ should "convert to objects of the class and work" do
87
+ project = Project.new
88
+ project.statuses = [{ 'name' => 'ready' }]
89
+ project.save.should be_true
90
+
91
+ project.reload
92
+ project.statuses.size.should == 1
93
+ project.statuses[0].name.should == "ready"
94
+ end
95
+ end
96
+
97
+ context "with :dependent" do
98
+ setup do
99
+ @broker_class = Doc('Broker')
100
+ @property_class = Doc('Property') do
101
+ key :broker_id, ObjectId
102
+ belongs_to :broker
103
+ end
104
+ end
105
+
106
+ context "=> destroy" do
107
+ setup do
108
+ @broker_class.many :properties, :class => @property_class, :dependent => :destroy
109
+
110
+ @broker = @broker_class.create(:name => "Bob")
111
+ @property1 = @property_class.create
112
+ @property2 = @property_class.create
113
+ @property3 = @property_class.create
114
+ @broker.properties << @property1
115
+ @broker.properties << @property2
116
+ @broker.properties << @property3
117
+ end
118
+
119
+ should "call destroy the existing documents" do
120
+ @broker.properties[0].expects(:destroy).once
121
+ @broker.properties[1].expects(:destroy).once
122
+ @broker.properties[2].expects(:destroy).once
123
+ @broker.properties = [@property_class.new]
124
+ end
125
+
126
+ should "remove the existing document from the database" do
127
+ @property_class.count.should == 3
128
+ @broker.properties = []
129
+ @property_class.count.should == 0
130
+ end
131
+
132
+ should "skip over documents that are the same" do
133
+ @broker.properties[0].expects(:destroy).never
134
+ @broker.properties[1].expects(:destroy).once
135
+ @broker.properties[2].expects(:destroy).never
136
+ @broker.properties = [@property3, @property1]
137
+ end
138
+ end
139
+
140
+ context "=> delete_all" do
141
+ setup do
142
+ @broker_class.many :properties, :class => @property_class, :dependent => :delete_all
143
+
144
+ @broker = @broker_class.create(:name => "Bob")
145
+ @property1 = @property_class.create
146
+ @property2 = @property_class.create
147
+ @property3 = @property_class.create
148
+ @broker.properties << @property1
149
+ @broker.properties << @property2
150
+ @broker.properties << @property3
151
+ end
152
+
153
+ should "call delete the existing documents" do
154
+ @broker.properties[0].expects(:delete).once
155
+ @broker.properties[1].expects(:delete).once
156
+ @broker.properties[2].expects(:delete).once
157
+ @broker.properties = [@property_class.new]
158
+ end
159
+
160
+ should "remove the existing document from the database" do
161
+ @property_class.count.should == 3
162
+ @broker.properties = []
163
+ @property_class.count.should == 0
164
+ end
165
+
166
+ should "skip over documents that are the same" do
167
+ @broker.properties[0].expects(:delete).never
168
+ @broker.properties[1].expects(:delete).once
169
+ @broker.properties[2].expects(:delete).never
170
+ @broker.properties = [@property3, @property1]
171
+ end
172
+ end
173
+
174
+ context "=> nullify" do
175
+ setup do
176
+ @broker_class.many :properties, :class => @property_class, :dependent => :nullify
177
+
178
+ @broker = @broker_class.create(:name => "Bob")
179
+ @property1 = @property_class.create
180
+ @property2 = @property_class.create
181
+ @property3 = @property_class.create
182
+ @broker.properties << @property1
183
+ @broker.properties << @property2
184
+ @broker.properties << @property3
185
+ end
186
+
187
+ should "nullify the existing documents" do
188
+ @property1.reload.broker_id.should == @broker.id
189
+ @property2.reload.broker_id.should == @broker.id
190
+ @property3.reload.broker_id.should == @broker.id
191
+
192
+ @broker.properties = [@property_class.new]
193
+
194
+ @property1.reload.broker_id.should be_nil
195
+ @property2.reload.broker_id.should be_nil
196
+ @property3.reload.broker_id.should be_nil
197
+ end
198
+
199
+ should "skip over documents that are the same" do
200
+ @broker.properties = [@property3, @property1]
201
+
202
+ @property1.reload.broker_id.should == @broker.id
203
+ @property2.reload.broker_id.should be_nil
204
+ @property3.reload.broker_id.should == @broker.id
205
+ end
206
+
207
+ should "work" do
208
+ old_properties = @broker.properties
209
+ @broker.properties = [@property1, @property2, @property3]
210
+ old_properties.should == @broker.properties
211
+ end
212
+ end
213
+
214
+ context "unspecified" do
215
+ should "nullify the existing documents" do
216
+ @broker_class.many :properties, :class => @property_class
217
+
218
+ @broker = @broker_class.create(:name => "Bob")
219
+ @property1 = @property_class.create
220
+ @property2 = @property_class.create
221
+ @property3 = @property_class.create
222
+ @broker.properties << @property1
223
+ @broker.properties << @property2
224
+ @broker.properties << @property3
225
+
226
+ @broker.properties = [@property_class.new]
227
+
228
+ @property1.reload.broker_id.should be_nil
229
+ @property2.reload.broker_id.should be_nil
230
+ @property3.reload.broker_id.should be_nil
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ context "using <<, push and concat" do
237
+ context "with objects of the class" do
238
+ should "correctly assign foreign key" do
239
+ project = Project.new
240
+ project.statuses << Status.new(:name => '<<')
241
+ project.statuses.push Status.new(:name => 'push')
242
+ project.statuses.concat Status.new(:name => 'concat')
243
+
244
+ project.reload
245
+ project.statuses[0].project_id.should == project.id
246
+ project.statuses[1].project_id.should == project.id
247
+ project.statuses[2].project_id.should == project.id
248
+ end
249
+ end
250
+
251
+ context "with Hashes" do
252
+ should "correctly convert to objects and assign foreign key" do
253
+ project = Project.new
254
+ project.statuses << { 'name' => '<<' }
255
+ project.statuses.push( { 'name' => 'push' })
256
+ project.statuses.concat({ 'name' => 'concat' })
257
+
258
+ project.reload
259
+ project.statuses[0].project_id.should == project.id
260
+ project.statuses[1].project_id.should == project.id
261
+ project.statuses[2].project_id.should == project.id
262
+ end
263
+ end
264
+ end
265
+
266
+ context "build" do
267
+ should "assign foreign key" do
268
+ project = Project.create
269
+ status = project.statuses.build
270
+ status.project_id.should == project.id
271
+ end
272
+
273
+ should "allow assigning attributes" do
274
+ project = Project.create
275
+ status = project.statuses.build(:name => 'Foo')
276
+ status.name.should == 'Foo'
277
+ end
278
+
279
+ should "reset cache" do
280
+ project = Project.create
281
+ project.statuses.size.should == 0
282
+ status = project.statuses.build(:name => 'Foo')
283
+ status.save!
284
+ project.statuses.size.should == 1
285
+ end
286
+
287
+ should "update collection without save" do
288
+ project = Project.create
289
+ project.statuses.build(:name => 'Foo')
290
+ project.statuses.size.should == 1
291
+ end
292
+
293
+ should "save built document when saving parent" do
294
+ project = Project.create
295
+ status = project.statuses.build(:name => 'Foo')
296
+ project.save!
297
+ status.should_not be_new
298
+ end
299
+
300
+ should "not save the parent when building associations" do
301
+ project = Project.new
302
+ status = project.statuses.build(:name => 'Foo')
303
+ project.should be_new
304
+ end
305
+
306
+ should "not save the built object" do
307
+ project = Project.new
308
+ status = project.statuses.build(:name => 'Foo')
309
+ status.should be_new
310
+ end
311
+ end
312
+
313
+ context "create" do
314
+ should "assign foreign key" do
315
+ project = Project.create
316
+ status = project.statuses.create(:name => 'Foo!')
317
+ status.project_id.should == project.id
318
+ end
319
+
320
+ should "save record" do
321
+ project = Project.create
322
+ lambda {
323
+ project.statuses.create(:name => 'Foo!')
324
+ }.should change { Status.count }
325
+ end
326
+
327
+ should "allow passing attributes" do
328
+ project = Project.create
329
+ status = project.statuses.create(:name => 'Foo!')
330
+ status.name.should == 'Foo!'
331
+ end
332
+
333
+ should "reset cache" do
334
+ project = Project.create
335
+ project.statuses.size.should == 0
336
+ project.statuses.create(:name => 'Foo!')
337
+ project.statuses.size.should == 1
338
+ end
339
+ end
340
+
341
+ context "create!" do
342
+ should "assign foreign key" do
343
+ project = Project.create
344
+ status = project.statuses.create!(:name => 'Foo!')
345
+ status.project_id.should == project.id
346
+ end
347
+
348
+ should "save record" do
349
+ project = Project.create
350
+ lambda {
351
+ project.statuses.create!(:name => 'Foo!')
352
+ }.should change { Status.count }
353
+ end
354
+
355
+ should "allow passing attributes" do
356
+ project = Project.create
357
+ status = project.statuses.create!(:name => 'Foo!')
358
+ status.name.should == 'Foo!'
359
+ end
360
+
361
+ should "raise exception if not valid" do
362
+ project = Project.create
363
+ lambda {
364
+ project.statuses.create!(:name => nil)
365
+ }.should raise_error(MongoMapper::DocumentNotValid)
366
+ end
367
+
368
+ should "reset cache" do
369
+ project = Project.create
370
+ project.statuses.size.should == 0
371
+ project.statuses.create!(:name => 'Foo!')
372
+ project.statuses.size.should == 1
373
+ end
374
+ end
375
+
376
+ context "count" do
377
+ should "work scoped to association" do
378
+ project = Project.create
379
+ 3.times { project.statuses.create(:name => 'Foo!') }
380
+
381
+ other_project = Project.create
382
+ 2.times { other_project.statuses.create(:name => 'Foo!') }
383
+
384
+ project.statuses.count.should == 3
385
+ other_project.statuses.count.should == 2
386
+ end
387
+
388
+ should "work with conditions" do
389
+ project = Project.create
390
+ project.statuses.create(:name => 'Foo')
391
+ project.statuses.create(:name => 'Other 1')
392
+ project.statuses.create(:name => 'Other 2')
393
+
394
+ project.statuses.count(:name => 'Foo').should == 1
395
+ end
396
+
397
+ should "ignore unpersisted documents" do
398
+ project = Project.create
399
+ project.statuses.build(:name => 'Foo')
400
+ project.statuses.count.should == 0
401
+ end
402
+ end
403
+
404
+ context "size" do
405
+ should "reflect both persisted and new documents" do
406
+ project = Project.create
407
+ 3.times { project.statuses.create(:name => 'Foo!') }
408
+ 2.times { project.statuses.build(:name => 'Foo!') }
409
+ project.statuses.size.should == 5
410
+ end
411
+ end
412
+
413
+ context "empty?" do
414
+ should "be true with no associated docs" do
415
+ project = Project.create
416
+ project.statuses.empty?.should be_true
417
+ end
418
+
419
+ should "be false if a document is built" do
420
+ project = Project.create
421
+ project.statuses.build(:name => 'Foo!')
422
+ project.statuses.empty?.should be_false
423
+ end
424
+
425
+ should "be false if a document is created" do
426
+ project = Project.create
427
+ project.statuses.create(:name => 'Foo!')
428
+ project.statuses.empty?.should be_false
429
+ end
430
+ end
431
+
432
+ context "to_a" do
433
+ should "include persisted and new documents" do
434
+ project = Project.create
435
+ 3.times { project.statuses.create(:name => 'Foo!') }
436
+ 2.times { project.statuses.build(:name => 'Foo!') }
437
+ project.statuses.to_a.size.should == 5
438
+ end
439
+ end
440
+
441
+ context "to_json" do
442
+ should "work on association" do
443
+ project = Project.create
444
+ 3.times { |i| project.statuses.create(:name => i.to_s) }
445
+
446
+ JSON.parse(project.statuses.to_json).collect{|status| status["name"] }.sort.should == ["0","1","2"]
447
+ end
448
+ end
449
+
450
+ context "as_json" do
451
+ should "work on association" do
452
+ project = Project.create
453
+ 3.times { |i| project.statuses.create(:name => i.to_s) }
454
+
455
+ project.statuses.as_json.collect{|status| status["name"] }.sort.should == ["0","1","2"]
456
+ end
457
+ end
458
+
459
+ context "Unassociating documents" do
460
+ setup do
461
+ @project = Project.create
462
+ @project.statuses << Status.create(:name => '1')
463
+ @project.statuses << Status.create(:name => '2')
464
+
465
+ @project2 = Project.create
466
+ @project2.statuses << Status.create(:name => '1')
467
+ @project2.statuses << Status.create(:name => '2')
468
+ end
469
+
470
+ should "work with destroy all" do
471
+ @project.statuses.count.should == 2
472
+ @project.statuses.destroy_all
473
+ @project.statuses.count.should == 0
474
+
475
+ @project2.statuses.count.should == 2
476
+ Status.count.should == 2
477
+ end
478
+
479
+ should "work with destroy all and conditions" do
480
+ @project.statuses.count.should == 2
481
+ @project.statuses.destroy_all(:name => '1')
482
+ @project.statuses.count.should == 1
483
+
484
+ @project2.statuses.count.should == 2
485
+ Status.count.should == 3
486
+ end
487
+
488
+ should "work with delete all" do
489
+ @project.statuses.count.should == 2
490
+ @project.statuses.delete_all
491
+ @project.statuses.count.should == 0
492
+
493
+ @project2.statuses.count.should == 2
494
+ Status.count.should == 2
495
+ end
496
+
497
+ should "work with delete all and conditions" do
498
+ @project.statuses.count.should == 2
499
+ @project.statuses.delete_all(:name => '1')
500
+ @project.statuses.count.should == 1
501
+
502
+ @project2.statuses.count.should == 2
503
+ Status.count.should == 3
504
+ end
505
+
506
+ should "work with nullify" do
507
+ @project.statuses.count.should == 2
508
+ @project.statuses.nullify
509
+ @project.statuses.count.should == 0
510
+
511
+ @project2.statuses.count.should == 2
512
+ Status.count.should == 4
513
+ Status.count(:name => '1').should == 2
514
+ Status.count(:name => '2').should == 2
515
+ end
516
+ end
517
+
518
+ context "Finding scoped to association" do
519
+ setup do
520
+ @project1 = Project.new(:name => 'Project 1')
521
+ @brand_new = Status.create(:name => 'New', :position => 1 )
522
+ @complete = Status.create(:name => 'Complete', :position => 2)
523
+ @project1.statuses = [@brand_new, @complete]
524
+ @project1.save
525
+
526
+ @project2 = Project.create(:name => 'Project 2')
527
+ @in_progress = Status.create(:name => 'In Progress')
528
+ @archived = Status.create(:name => 'Archived')
529
+ @another_complete = Status.create(:name => 'Complete')
530
+ @project2.statuses = [@in_progress, @archived, @another_complete]
531
+ @project2.save
532
+ end
533
+
534
+ context "include?" do
535
+ should "return true if in association" do
536
+ @project1.statuses.should include(@brand_new)
537
+ end
538
+
539
+ should "return false if not in association" do
540
+ @project1.statuses.should_not include(@in_progress)
541
+ end
542
+ end
543
+
544
+ context "dynamic finders" do
545
+ should "work with single key" do
546
+ @project1.statuses.find_by_name('New').should == @brand_new
547
+ @project1.statuses.find_by_name!('New').should == @brand_new
548
+ @project2.statuses.find_by_name('In Progress').should == @in_progress
549
+ @project2.statuses.find_by_name!('In Progress').should == @in_progress
550
+ end
551
+
552
+ should "work with multiple keys" do
553
+ @project1.statuses.find_by_name_and_position('New', 1).should == @brand_new
554
+ @project1.statuses.find_by_name_and_position!('New', 1).should == @brand_new
555
+ @project1.statuses.find_by_name_and_position('New', 2).should be_nil
556
+ end
557
+
558
+ should "raise error when using !" do
559
+ lambda {
560
+ @project1.statuses.find_by_name!('Fake')
561
+ }.should raise_error(MongoMapper::DocumentNotFound)
562
+ end
563
+
564
+ context "find_or_create_by" do
565
+ should "not create document if found" do
566
+ lambda {
567
+ status = @project1.statuses.find_or_create_by_name('New')
568
+ status.project.should == @project1
569
+ status.should == @brand_new
570
+ }.should_not change { Status.count }
571
+ end
572
+
573
+ should "create document if not found" do
574
+ lambda {
575
+ status = @project1.statuses.find_or_create_by_name('Delivered')
576
+ status.project.should == @project1
577
+ }.should change { Status.count }
578
+ end
579
+ end
580
+ end
581
+
582
+ context "sexy querying" do
583
+ should "work with where" do
584
+ @project1.statuses.where(:name => 'New').all.should == [@brand_new]
585
+ end
586
+
587
+ should "work with sort" do
588
+ @project1.statuses.sort(:name).all.should == [@complete, @brand_new]
589
+ end
590
+
591
+ should "work with limit" do
592
+ @project1.statuses.sort(:name).limit(1).all.should == [@complete]
593
+ end
594
+
595
+ should "work with skip" do
596
+ @project1.statuses.sort(:name).skip(1).all.should == [@brand_new]
597
+ end
598
+
599
+ should "work with fields" do
600
+ @project1.statuses.fields(:position).all.each do |status|
601
+ status.position.should_not be_nil
602
+ status.name.should be_nil
603
+ end
604
+ end
605
+
606
+ should "work with scopes" do
607
+ @project1.statuses.complete.all.should == [@complete]
608
+ end
609
+
610
+ should "work with methods on class that return query" do
611
+ @project1.statuses.by_position(1).first.should == @brand_new
612
+ end
613
+
614
+ should "not work with methods on class that do not return query" do
615
+ Status.class_eval { def self.foo; 'foo' end }
616
+ lambda { @project1.statuses.foo }.
617
+ should raise_error(NoMethodError)
618
+ end
619
+ end
620
+
621
+ context "all" do
622
+ should "work" do
623
+ @project1.statuses.all(:order => "position asc").should == [@brand_new, @complete]
624
+ end
625
+
626
+ should "work with conditions" do
627
+ @project1.statuses.all(:name => 'Complete').should == [@complete]
628
+ end
629
+ end
630
+
631
+ context "first" do
632
+ should "work" do
633
+ @project1.statuses.first(:order => 'name').should == @complete
634
+ end
635
+
636
+ should "work with conditions" do
637
+ @project1.statuses.first(:name => 'Complete').should == @complete
638
+ end
639
+ end
640
+
641
+ context "last" do
642
+ should "work" do
643
+ @project1.statuses.last(:order => "position asc").should == @complete
644
+ end
645
+
646
+ should "work with conditions" do
647
+ @project1.statuses.last(:order => 'position', :name => 'New').should == @brand_new
648
+ end
649
+ end
650
+
651
+ context "with one id" do
652
+ should "work for id in association" do
653
+ @project1.statuses.find(@complete.id).should == @complete
654
+ end
655
+
656
+ should "not work for id not in association" do
657
+ lambda {
658
+ @project1.statuses.find!(@archived.id)
659
+ }.should raise_error(MongoMapper::DocumentNotFound)
660
+ end
661
+ end
662
+
663
+ context "with multiple ids" do
664
+ should "work for ids in association" do
665
+ statuses = @project1.statuses.find(@brand_new.id, @complete.id)
666
+ statuses.should == [@brand_new, @complete]
667
+ end
668
+
669
+ should "not work for ids not in association" do
670
+ assert_raises(MongoMapper::DocumentNotFound) do
671
+ @project1.statuses.find!(@brand_new.id, @complete.id, @archived.id)
672
+ end
673
+ end
674
+ end
675
+
676
+ context "with #paginate" do
677
+ setup do
678
+ @statuses = @project2.statuses.paginate(:per_page => 2, :page => 1, :order => 'name asc')
679
+ end
680
+
681
+ should "return total pages" do
682
+ @statuses.total_pages.should == 2
683
+ end
684
+
685
+ should "return total entries" do
686
+ @statuses.total_entries.should == 3
687
+ end
688
+
689
+ should "return the subject" do
690
+ @statuses.collect(&:name).should == %w(Archived Complete)
691
+ end
692
+ end
693
+ end
694
+
695
+ context "extending the association" do
696
+ should "work using a block passed to many" do
697
+ project = Project.new(:name => "Some Project")
698
+ status1 = Status.new(:name => "New")
699
+ status2 = Status.new(:name => "Assigned")
700
+ status3 = Status.new(:name => "Closed")
701
+ project.statuses = [status1, status2, status3]
702
+ project.save
703
+
704
+ open_statuses = project.statuses.open
705
+ open_statuses.should include(status1)
706
+ open_statuses.should include(status2)
707
+ open_statuses.should_not include(status3)
708
+ end
709
+
710
+ should "work using many's :extend option" do
711
+ project = Project.new(:name => "Some Project")
712
+ collaborator1 = Collaborator.new(:name => "zing")
713
+ collaborator2 = Collaborator.new(:name => "zang")
714
+ project.collaborators = [collaborator1, collaborator2]
715
+ project.save
716
+ project.collaborators.top.should == collaborator1
717
+ end
718
+ end
719
+
720
+ context ":dependent" do
721
+ setup do
722
+ # FIXME: make use of already defined models
723
+ class ::Property
724
+ include MongoMapper::Document
725
+ end
726
+ Property.collection.remove
727
+
728
+ class ::Thing
729
+ include MongoMapper::Document
730
+ key :name, String
731
+ end
732
+ Thing.collection.remove
733
+ end
734
+
735
+ teardown do
736
+ Object.send :remove_const, 'Property' if defined?(::Property)
737
+ Object.send :remove_const, 'Thing' if defined?(::Thing)
738
+ end
739
+
740
+ context "=> destroy" do
741
+ setup do
742
+ Property.key :thing_id, ObjectId
743
+ Property.belongs_to :thing, :dependent => :destroy
744
+ Thing.many :properties, :dependent => :destroy
745
+
746
+ @thing = Thing.create(:name => "Tree")
747
+ @property1 = Property.create
748
+ @property2 = Property.create
749
+ @property3 = Property.create
750
+ @thing.properties << @property1
751
+ @thing.properties << @property2
752
+ @thing.properties << @property3
753
+ end
754
+
755
+ should "should destroy the associated documents" do
756
+ @thing.properties.count.should == 3
757
+ @thing.destroy
758
+ @thing.properties.count.should == 0
759
+ Property.count.should == 0
760
+ end
761
+ end
762
+
763
+ context "=> delete_all" do
764
+ setup do
765
+ Property.key :thing_id, ObjectId
766
+ Property.belongs_to :thing
767
+ Thing.has_many :properties, :dependent => :delete_all
768
+
769
+ @thing = Thing.create(:name => "Tree")
770
+ @property1 = Property.create
771
+ @property2 = Property.create
772
+ @property3 = Property.create
773
+ @thing.properties << @property1
774
+ @thing.properties << @property2
775
+ @thing.properties << @property3
776
+ end
777
+
778
+ should "should delete associated documents" do
779
+ @thing.properties.count.should == 3
780
+ @thing.destroy
781
+ @thing.properties.count.should == 0
782
+ Property.count.should == 0
783
+ end
784
+ end
785
+
786
+ context "=> nullify" do
787
+ setup do
788
+ Property.key :thing_id, ObjectId
789
+ Property.belongs_to :thing
790
+ Thing.has_many :properties, :dependent => :nullify
791
+
792
+ @thing = Thing.create(:name => "Tree")
793
+ @property1 = Property.create
794
+ @property2 = Property.create
795
+ @property3 = Property.create
796
+ @thing.properties << @property1
797
+ @thing.properties << @property2
798
+ @thing.properties << @property3
799
+ end
800
+
801
+ should "should nullify relationship but not destroy associated documents" do
802
+ @thing.properties.count.should == 3
803
+ @thing.destroy
804
+ @thing.properties.count.should == 0
805
+ Property.count.should == 3
806
+ end
807
+ end
808
+
809
+ context "unspecified" do
810
+ setup do
811
+ Property.key :thing_id, ObjectId
812
+ Property.belongs_to :thing
813
+ Thing.has_many :properties, :dependent => :nullify
814
+
815
+ @thing = Thing.create(:name => "Tree")
816
+ @property1 = Property.create
817
+ @property2 = Property.create
818
+ @property3 = Property.create
819
+ @thing.properties << @property1
820
+ @thing.properties << @property2
821
+ @thing.properties << @property3
822
+ end
823
+
824
+ should "should nullify relationship but not destroy associated documents" do
825
+ @thing.properties.count.should == 3
826
+ @thing.destroy
827
+ @thing.properties.count.should == 0
828
+ Property.count.should == 3
829
+ end
830
+ end
831
+ end
832
+
833
+ context "namespaced foreign keys" do
834
+ setup do
835
+ News::Paper.many :articles, :class_name => 'News::Article'
836
+ News::Article.belongs_to :paper, :class_name => 'News::Paper'
837
+
838
+ @paper = News::Paper.create
839
+ end
840
+
841
+ should "properly infer the foreign key" do
842
+ article = @paper.articles.create
843
+ article.should respond_to(:paper_id)
844
+ article.paper_id.should == @paper.id
845
+ end
846
+ end
847
+
848
+ context "criteria" do
849
+ setup do
850
+ News::Paper.many :articles, :class_name => 'News::Article'
851
+ News::Article.belongs_to :paper, :class_name => 'News::Paper'
852
+
853
+ @paper = News::Paper.create
854
+ end
855
+
856
+ should "should find associated instances by an object ID" do
857
+ article = News::Article.create(:paper_id => @paper.id)
858
+ @paper.articles.should include(article)
859
+ end
860
+
861
+ should "should find associated instances by a string" do
862
+ article = News::Article.create(:paper_id => @paper.id.to_s)
863
+ @paper.articles.should include(article)
864
+ end
865
+ end
866
+ end