lookout-mongo_mapper 0.11.3

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 (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 +30 -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 +52 -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 +54 -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 +118 -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 +134 -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 +165 -0
  84. data/lib/mongo_mapper/plugins/querying/decorator.rb +36 -0
  85. data/lib/mongo_mapper/plugins/rails.rb +58 -0
  86. data/lib/mongo_mapper/plugins/rails/active_record_association_adapter.rb +33 -0
  87. data/lib/mongo_mapper/plugins/safe.rb +28 -0
  88. data/lib/mongo_mapper/plugins/sci.rb +36 -0
  89. data/lib/mongo_mapper/plugins/scopes.rb +27 -0
  90. data/lib/mongo_mapper/plugins/serialization.rb +109 -0
  91. data/lib/mongo_mapper/plugins/timestamps.rb +22 -0
  92. data/lib/mongo_mapper/plugins/touch.rb +18 -0
  93. data/lib/mongo_mapper/plugins/userstamps.rb +18 -0
  94. data/lib/mongo_mapper/plugins/validations.rb +86 -0
  95. data/lib/mongo_mapper/railtie.rb +48 -0
  96. data/lib/mongo_mapper/railtie/database.rake +65 -0
  97. data/lib/mongo_mapper/translation.rb +10 -0
  98. data/lib/mongo_mapper/version.rb +4 -0
  99. data/lib/rails/generators/mongo_mapper/config/config_generator.rb +24 -0
  100. data/lib/rails/generators/mongo_mapper/config/templates/mongo.yml +18 -0
  101. data/lib/rails/generators/mongo_mapper/model/model_generator.rb +23 -0
  102. data/lib/rails/generators/mongo_mapper/model/templates/model.rb +13 -0
  103. data/test/_NOTE_ON_TESTING +1 -0
  104. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +64 -0
  105. data/test/functional/associations/test_belongs_to_proxy.rb +238 -0
  106. data/test/functional/associations/test_in_array_proxy.rb +349 -0
  107. data/test/functional/associations/test_many_documents_as_proxy.rb +231 -0
  108. data/test/functional/associations/test_many_documents_proxy.rb +866 -0
  109. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +239 -0
  110. data/test/functional/associations/test_many_embedded_proxy.rb +289 -0
  111. data/test/functional/associations/test_many_polymorphic_proxy.rb +303 -0
  112. data/test/functional/associations/test_one_as_proxy.rb +491 -0
  113. data/test/functional/associations/test_one_embedded_polymorphic_proxy.rb +208 -0
  114. data/test/functional/associations/test_one_embedded_proxy.rb +100 -0
  115. data/test/functional/associations/test_one_proxy.rb +383 -0
  116. data/test/functional/test_accessible.rb +198 -0
  117. data/test/functional/test_associations.rb +46 -0
  118. data/test/functional/test_binary.rb +27 -0
  119. data/test/functional/test_caching.rb +77 -0
  120. data/test/functional/test_callbacks.rb +232 -0
  121. data/test/functional/test_dirty.rb +301 -0
  122. data/test/functional/test_document.rb +282 -0
  123. data/test/functional/test_dynamic_querying.rb +75 -0
  124. data/test/functional/test_embedded_document.rb +288 -0
  125. data/test/functional/test_equality.rb +20 -0
  126. data/test/functional/test_identity_map.rb +513 -0
  127. data/test/functional/test_indexes.rb +50 -0
  128. data/test/functional/test_logger.rb +20 -0
  129. data/test/functional/test_modifiers.rb +537 -0
  130. data/test/functional/test_pagination.rb +91 -0
  131. data/test/functional/test_protected.rb +201 -0
  132. data/test/functional/test_querying.rb +935 -0
  133. data/test/functional/test_safe.rb +76 -0
  134. data/test/functional/test_sci.rb +240 -0
  135. data/test/functional/test_scopes.rb +171 -0
  136. data/test/functional/test_timestamps.rb +62 -0
  137. data/test/functional/test_touch.rb +125 -0
  138. data/test/functional/test_userstamps.rb +44 -0
  139. data/test/functional/test_validations.rb +414 -0
  140. data/test/models.rb +261 -0
  141. data/test/support/railtie.rb +4 -0
  142. data/test/support/railtie/autoloaded.rb +2 -0
  143. data/test/support/railtie/not_autoloaded.rb +3 -0
  144. data/test/support/railtie/parent.rb +3 -0
  145. data/test/test_active_model_lint.rb +18 -0
  146. data/test/test_helper.rb +93 -0
  147. data/test/unit/associations/test_base.rb +146 -0
  148. data/test/unit/associations/test_belongs_to_association.rb +29 -0
  149. data/test/unit/associations/test_many_association.rb +63 -0
  150. data/test/unit/associations/test_one_association.rb +47 -0
  151. data/test/unit/associations/test_proxy.rb +100 -0
  152. data/test/unit/serializers/test_json_serializer.rb +216 -0
  153. data/test/unit/serializers/test_xml_serializer.rb +196 -0
  154. data/test/unit/test_clone.rb +69 -0
  155. data/test/unit/test_document.rb +249 -0
  156. data/test/unit/test_dynamic_finder.rb +125 -0
  157. data/test/unit/test_embedded_document.rb +682 -0
  158. data/test/unit/test_equality.rb +38 -0
  159. data/test/unit/test_exceptions.rb +12 -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 +66 -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 +285 -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