mongoid 2.0.0.beta.20 → 2.0.0.rc.1

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 (134) hide show
  1. data/README.rdoc +8 -0
  2. data/Rakefile +51 -0
  3. data/lib/config/locales/nl.yml +39 -0
  4. data/lib/config/locales/ro.yml +1 -1
  5. data/lib/mongoid.rb +17 -17
  6. data/lib/mongoid/atomicity.rb +54 -22
  7. data/lib/mongoid/attributes.rb +145 -125
  8. data/lib/mongoid/callbacks.rb +7 -2
  9. data/lib/mongoid/collection.rb +49 -32
  10. data/lib/mongoid/collections.rb +0 -1
  11. data/lib/mongoid/components.rb +34 -29
  12. data/lib/mongoid/config.rb +207 -193
  13. data/lib/mongoid/config/database.rb +167 -0
  14. data/lib/mongoid/contexts.rb +2 -5
  15. data/lib/mongoid/contexts/enumerable.rb +30 -4
  16. data/lib/mongoid/contexts/ids.rb +2 -2
  17. data/lib/mongoid/contexts/mongo.rb +30 -5
  18. data/lib/mongoid/copyable.rb +44 -0
  19. data/lib/mongoid/criteria.rb +110 -56
  20. data/lib/mongoid/criterion/creational.rb +34 -0
  21. data/lib/mongoid/criterion/destructive.rb +37 -0
  22. data/lib/mongoid/criterion/exclusion.rb +3 -1
  23. data/lib/mongoid/criterion/inclusion.rb +59 -64
  24. data/lib/mongoid/criterion/inspection.rb +22 -0
  25. data/lib/mongoid/criterion/optional.rb +42 -54
  26. data/lib/mongoid/criterion/selector.rb +9 -0
  27. data/lib/mongoid/default_scope.rb +28 -0
  28. data/lib/mongoid/deprecation.rb +5 -5
  29. data/lib/mongoid/dirty.rb +4 -5
  30. data/lib/mongoid/document.rb +161 -114
  31. data/lib/mongoid/extensions.rb +7 -11
  32. data/lib/mongoid/extensions/array/parentization.rb +2 -2
  33. data/lib/mongoid/extensions/date/conversions.rb +1 -1
  34. data/lib/mongoid/extensions/hash/conversions.rb +0 -23
  35. data/lib/mongoid/extensions/nil/collectionization.rb +12 -0
  36. data/lib/mongoid/extensions/object/reflections.rb +17 -0
  37. data/lib/mongoid/extensions/object/yoda.rb +27 -0
  38. data/lib/mongoid/extensions/string/conversions.rb +23 -4
  39. data/lib/mongoid/extensions/time_conversions.rb +4 -4
  40. data/lib/mongoid/field.rb +30 -19
  41. data/lib/mongoid/fields.rb +15 -5
  42. data/lib/mongoid/finders.rb +19 -11
  43. data/lib/mongoid/hierarchy.rb +34 -28
  44. data/lib/mongoid/identity.rb +62 -20
  45. data/lib/mongoid/inspection.rb +58 -0
  46. data/lib/mongoid/matchers.rb +20 -0
  47. data/lib/mongoid/multi_database.rb +11 -0
  48. data/lib/mongoid/nested_attributes.rb +41 -0
  49. data/lib/mongoid/paranoia.rb +3 -4
  50. data/lib/mongoid/paths.rb +1 -1
  51. data/lib/mongoid/persistence.rb +89 -90
  52. data/lib/mongoid/persistence/command.rb +20 -4
  53. data/lib/mongoid/persistence/insert.rb +13 -11
  54. data/lib/mongoid/persistence/insert_embedded.rb +8 -6
  55. data/lib/mongoid/persistence/remove.rb +6 -4
  56. data/lib/mongoid/persistence/remove_all.rb +6 -4
  57. data/lib/mongoid/persistence/remove_embedded.rb +8 -6
  58. data/lib/mongoid/persistence/update.rb +12 -10
  59. data/lib/mongoid/railtie.rb +2 -2
  60. data/lib/mongoid/railties/database.rake +10 -9
  61. data/lib/mongoid/relations.rb +104 -0
  62. data/lib/mongoid/relations/accessors.rb +154 -0
  63. data/lib/mongoid/relations/auto_save.rb +34 -0
  64. data/lib/mongoid/relations/binding.rb +24 -0
  65. data/lib/mongoid/relations/bindings.rb +9 -0
  66. data/lib/mongoid/relations/bindings/embedded/in.rb +77 -0
  67. data/lib/mongoid/relations/bindings/embedded/many.rb +93 -0
  68. data/lib/mongoid/relations/bindings/embedded/one.rb +65 -0
  69. data/lib/mongoid/relations/bindings/referenced/in.rb +78 -0
  70. data/lib/mongoid/relations/bindings/referenced/many.rb +93 -0
  71. data/lib/mongoid/relations/bindings/referenced/many_to_many.rb +94 -0
  72. data/lib/mongoid/relations/bindings/referenced/one.rb +63 -0
  73. data/lib/mongoid/relations/builder.rb +41 -0
  74. data/lib/mongoid/relations/builders.rb +79 -0
  75. data/lib/mongoid/relations/builders/embedded/in.rb +25 -0
  76. data/lib/mongoid/relations/builders/embedded/many.rb +32 -0
  77. data/lib/mongoid/relations/builders/embedded/one.rb +26 -0
  78. data/lib/mongoid/relations/builders/nested_attributes/many.rb +116 -0
  79. data/lib/mongoid/relations/builders/nested_attributes/one.rb +135 -0
  80. data/lib/mongoid/relations/builders/referenced/in.rb +32 -0
  81. data/lib/mongoid/relations/builders/referenced/many.rb +26 -0
  82. data/lib/mongoid/relations/builders/referenced/many_to_many.rb +29 -0
  83. data/lib/mongoid/relations/builders/referenced/one.rb +30 -0
  84. data/lib/mongoid/relations/cascading.rb +55 -0
  85. data/lib/mongoid/relations/cascading/delete.rb +19 -0
  86. data/lib/mongoid/relations/cascading/destroy.rb +19 -0
  87. data/lib/mongoid/relations/cascading/nullify.rb +18 -0
  88. data/lib/mongoid/relations/cascading/strategy.rb +26 -0
  89. data/lib/mongoid/relations/cyclic.rb +97 -0
  90. data/lib/mongoid/relations/embedded/in.rb +172 -0
  91. data/lib/mongoid/relations/embedded/many.rb +450 -0
  92. data/lib/mongoid/relations/embedded/one.rb +169 -0
  93. data/lib/mongoid/relations/macros.rb +302 -0
  94. data/lib/mongoid/relations/many.rb +185 -0
  95. data/lib/mongoid/relations/metadata.rb +529 -0
  96. data/lib/mongoid/relations/nested_builder.rb +52 -0
  97. data/lib/mongoid/relations/one.rb +29 -0
  98. data/lib/mongoid/relations/polymorphic.rb +54 -0
  99. data/lib/mongoid/relations/proxy.rb +122 -0
  100. data/lib/mongoid/relations/referenced/in.rb +214 -0
  101. data/lib/mongoid/relations/referenced/many.rb +358 -0
  102. data/lib/mongoid/relations/referenced/many_to_many.rb +379 -0
  103. data/lib/mongoid/relations/referenced/one.rb +204 -0
  104. data/lib/mongoid/relations/reflections.rb +45 -0
  105. data/lib/mongoid/safe.rb +11 -1
  106. data/lib/mongoid/safety.rb +122 -97
  107. data/lib/mongoid/scope.rb +14 -9
  108. data/lib/mongoid/state.rb +37 -3
  109. data/lib/mongoid/timestamps.rb +11 -0
  110. data/lib/mongoid/validations.rb +42 -3
  111. data/lib/mongoid/validations/associated.rb +8 -5
  112. data/lib/mongoid/validations/uniqueness.rb +23 -2
  113. data/lib/mongoid/version.rb +1 -1
  114. data/lib/mongoid/versioning.rb +25 -16
  115. data/lib/rails/generators/mongoid/model/templates/model.rb +3 -1
  116. metadata +95 -80
  117. data/lib/mongoid/associations.rb +0 -364
  118. data/lib/mongoid/associations/embedded_in.rb +0 -74
  119. data/lib/mongoid/associations/embeds_many.rb +0 -299
  120. data/lib/mongoid/associations/embeds_one.rb +0 -111
  121. data/lib/mongoid/associations/foreign_key.rb +0 -35
  122. data/lib/mongoid/associations/meta_data.rb +0 -38
  123. data/lib/mongoid/associations/options.rb +0 -78
  124. data/lib/mongoid/associations/proxy.rb +0 -60
  125. data/lib/mongoid/associations/referenced_in.rb +0 -70
  126. data/lib/mongoid/associations/references_many.rb +0 -254
  127. data/lib/mongoid/associations/references_many_as_array.rb +0 -128
  128. data/lib/mongoid/associations/references_one.rb +0 -104
  129. data/lib/mongoid/extensions/array/accessors.rb +0 -17
  130. data/lib/mongoid/extensions/array/assimilation.rb +0 -26
  131. data/lib/mongoid/extensions/hash/accessors.rb +0 -42
  132. data/lib/mongoid/extensions/hash/assimilation.rb +0 -40
  133. data/lib/mongoid/extensions/nil/assimilation.rb +0 -17
  134. data/lib/mongoid/memoization.rb +0 -33
@@ -0,0 +1,358 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Relations #:nodoc:
4
+ module Referenced #:nodoc:
5
+
6
+ # This class defines the behaviour for all relations that are a
7
+ # one-to-many between documents in different collections.
8
+ class Many < Relations::Many
9
+
10
+ # Binds the base object to the inverse of the relation. This is so we
11
+ # are referenced to the actual objects themselves and dont hit the
12
+ # database twice when setting the relations up.
13
+ #
14
+ # This is called after first creating the relation, or if a new object
15
+ # is set on the relation.
16
+ #
17
+ # @example Bind the relation.
18
+ # person.posts.bind
19
+ #
20
+ # @param [ Hash ] options The options to bind with.
21
+ #
22
+ # @option options [ true, false ] :building Are we in build mode?
23
+ # @option options [ true, false ] :continue Continue binding the
24
+ # inverse?
25
+ #
26
+ # @since 2.0.0.rc.1
27
+ def bind(options = {})
28
+ binding.bind(options)
29
+ target.map(&:save) if base.persisted? && !options[:building]
30
+ end
31
+
32
+ # Clear the relation. Will delete the documents from the db if they are
33
+ # already persisted.
34
+ #
35
+ # @example Clear the relation.
36
+ # person.posts.clear
37
+ #
38
+ # @return [ Many ] The relation emptied.
39
+ def clear
40
+ tap do |relation|
41
+ relation.unbind(default_options)
42
+ target.clear
43
+ end
44
+ end
45
+
46
+ # Deletes all related documents from the database given the supplied
47
+ # conditions.
48
+ #
49
+ # @example Delete all documents in the relation.
50
+ # person.posts.delete_all
51
+ #
52
+ # @example Conditonally delete all documents in the relation.
53
+ # person.posts.delete_all(:conditions => { :title => "Testing" })
54
+ #
55
+ # @param [ Hash ] conditions Optional conditions to delete with.
56
+ #
57
+ # @return [ Integer ] The number of documents deleted.
58
+ def delete_all(conditions = nil)
59
+ selector = (conditions || {})[:conditions] || {}
60
+ target.delete_if { |doc| doc.matches?(selector) }
61
+ metadata.klass.delete_all(
62
+ :conditions => selector.merge(metadata.foreign_key => base.id)
63
+ )
64
+ end
65
+
66
+ # Destroys all related documents from the database given the supplied
67
+ # conditions.
68
+ #
69
+ # @example Destroy all documents in the relation.
70
+ # person.posts.destroy_all
71
+ #
72
+ # @example Conditonally destroy all documents in the relation.
73
+ # person.posts.destroy_all(:conditions => { :title => "Testing" })
74
+ #
75
+ # @param [ Hash ] conditions Optional conditions to destroy with.
76
+ #
77
+ # @return [ Integer ] The number of documents destroyd.
78
+ def destroy_all(conditions = nil)
79
+ selector = (conditions || {})[:conditions] || {}
80
+ target.delete_if { |doc| doc.matches?(selector) }
81
+ metadata.klass.destroy_all(
82
+ :conditions => selector.merge(metadata.foreign_key => base.id)
83
+ )
84
+ end
85
+
86
+ # Find the matchind document on the association, either based on id or
87
+ # conditions.
88
+ #
89
+ # @example Find by an id.
90
+ # person.posts.find(BSON::ObjectId.new)
91
+ #
92
+ # @example Find by multiple ids.
93
+ # person.posts.find([ BSON::ObjectId.new, BSON::ObjectId.new ])
94
+ #
95
+ # @example Conditionally find all matching documents.
96
+ # person.posts.find(:all, :conditions => { :title => "Sir" })
97
+ #
98
+ # @example Conditionally find the first document.
99
+ # person.posts.find(:first, :conditions => { :title => "Sir" })
100
+ #
101
+ # @example Conditionally find the last document.
102
+ # person.posts.find(:last, :conditions => { :title => "Sir" })
103
+ #
104
+ # @param [ Symbol, BSON::ObjectId, Array<BSON::ObjectId> ] arg The
105
+ # argument to search with.
106
+ # @param [ Hash ] options The options to search with.
107
+ #
108
+ # @return [ Document, Criteria ] The matching document(s).
109
+ def find(arg, options = {})
110
+ klass = metadata.klass
111
+ return klass.criteria.id_criteria(arg) unless arg.is_a?(Symbol)
112
+ selector = (options[:conditions] || {}).merge(
113
+ metadata.foreign_key => base.id
114
+ )
115
+ klass.find(arg, :conditions => selector)
116
+ end
117
+
118
+ # Instantiate a new references_many relation. Will set the foreign key
119
+ # and the base on the inverse object.
120
+ #
121
+ # @example Create the new relation.
122
+ # Referenced::Many.new(base, target, metadata)
123
+ #
124
+ # @param [ Document ] base The document this relation hangs off of.
125
+ # @param [ Array<Document> ] target The target of the relation.
126
+ # @param [ Metadata ] metadata The relation's metadata.
127
+ def initialize(base, target, metadata)
128
+ init(base, target, metadata)
129
+ end
130
+
131
+ # Removes all associations between the base document and the target
132
+ # documents by deleting the foreign keys and the references, orphaning
133
+ # the target documents in the process.
134
+ #
135
+ # @example Nullify the relation.
136
+ # person.posts.nullify
137
+ #
138
+ # @since 2.0.0.rc.1
139
+ def nullify
140
+ loaded and target.each do |doc|
141
+ doc.send(metadata.foreign_key_setter, nil)
142
+ doc.send(
143
+ :remove_instance_variable, "@#{metadata.inverse(doc)}"
144
+ )
145
+ doc.save
146
+ end
147
+ end
148
+ alias :nullify_all :nullify
149
+
150
+ # Substitutes the supplied target documents for the existing documents
151
+ # in the relation. If the new target is nil, perform the necessary
152
+ # deletion.
153
+ #
154
+ # @example Replace the relation.
155
+ # person.posts.substitute(new_name)
156
+ #
157
+ # @param [ Array<Document> ] target The replacement target.
158
+ # @param [ Hash ] options The options to bind with.
159
+ #
160
+ # @option options [ true, false ] :building Are we in build mode?
161
+ # @option options [ true, false ] :continue Continue binding the
162
+ # inverse?
163
+ #
164
+ # @return [ Many ] The relation.
165
+ #
166
+ # @since 2.0.0.rc.1
167
+ def substitute(target, options = {})
168
+ tap { target ? (@target = target.to_a; bind(options)) : (@target = unbind(options)) }
169
+ end
170
+
171
+ # Unbinds the base object to the inverse of the relation. This occurs
172
+ # when setting a side of the relation to nil.
173
+ #
174
+ # Will delete the object if necessary.
175
+ #
176
+ # @example Unbind the target.
177
+ # person.posts.unbind
178
+ #
179
+ # @param [ Hash ] options The options to bind with.
180
+ #
181
+ # @option options [ true, false ] :building Are we in build mode?
182
+ # @option options [ true, false ] :continue Continue binding the
183
+ # inverse?
184
+ #
185
+ # @since 2.0.0.rc.1
186
+ def unbind(options = {})
187
+ binding.unbind(options)
188
+ target.each(&:delete) if base.persisted?
189
+ []
190
+ end
191
+
192
+ private
193
+
194
+ # Appends the document to the target array, updating the index on the
195
+ # document at the same time.
196
+ #
197
+ # @example Append the document to the relation.
198
+ # relation.append(document)
199
+ #
200
+ # @param [ Document ] document The document to append to the target.
201
+ #
202
+ # @since 2.0.0.rc.1
203
+ def append(document, options = {})
204
+ loaded and target.push(document)
205
+ metadatafy(document)
206
+ binding.bind_one(document, options)
207
+ end
208
+
209
+ # Instantiate the binding associated with this relation.
210
+ #
211
+ # @example Get the binding.
212
+ # relation.binding([ address ])
213
+ #
214
+ # @param [ Array<Document> ] new_target The new documents to bind with.
215
+ #
216
+ # @return [ Binding ] The binding.
217
+ #
218
+ # @since 2.0.0.rc.1
219
+ def binding(new_target = nil)
220
+ Bindings::Referenced::Many.new(base, new_target || target, metadata)
221
+ end
222
+
223
+ # Returns the criteria object for the target class with its documents set
224
+ # to target.
225
+ #
226
+ # @example Get a criteria for the relation.
227
+ # relation.criteria
228
+ #
229
+ # @return [ Criteria ] A new criteria.
230
+ def criteria
231
+ metadata.klass.criteria(false)
232
+ end
233
+
234
+ # Will load the target into an array if the target had not already been
235
+ # loaded.
236
+ #
237
+ # @example Load the relation into memory.
238
+ # relation.loaded
239
+ #
240
+ # @return [ Many ] The relation.
241
+ #
242
+ # @since 2.0.0.rc.1
243
+ def loaded
244
+ tap do |relation|
245
+ relation.target = target.entries if target.is_a?(Mongoid::Criteria)
246
+ end
247
+ end
248
+
249
+ class << self
250
+
251
+ # Return the builder that is responsible for generating the documents
252
+ # that will be used by this relation.
253
+ #
254
+ # @example Get the builder.
255
+ # Referenced::Many.builder(meta, object)
256
+ #
257
+ # @param [ Metadata ] meta The metadata of the relation.
258
+ # @param [ Document, Hash ] object A document or attributes to build
259
+ # with.
260
+ #
261
+ # @return [ Builder ] A new builder object.
262
+ #
263
+ # @since 2.0.0.rc.1
264
+ def builder(meta, object)
265
+ Builders::Referenced::Many.new(meta, object)
266
+ end
267
+
268
+ # Returns true if the relation is an embedded one. In this case
269
+ # always false.
270
+ #
271
+ # @example Is this relation embedded?
272
+ # Referenced::Many.embedded?
273
+ #
274
+ # @return [ false ] Always false.
275
+ #
276
+ # @since 2.0.0.rc.1
277
+ def embedded?
278
+ false
279
+ end
280
+
281
+ # Get the default value for the foreign key.
282
+ #
283
+ # @example Get the default.
284
+ # Referenced::Many.foreign_key_default
285
+ #
286
+ # @return [ nil ] Always nil.
287
+ #
288
+ # @since 2.0.0.rc.1
289
+ def foreign_key_default
290
+ nil
291
+ end
292
+
293
+ # Returns the suffix of the foreign key field, either "_id" or "_ids".
294
+ #
295
+ # @example Get the suffix for the foreign key.
296
+ # Referenced::Many.foreign_key_suffix
297
+ #
298
+ # @return [ String ] "_id"
299
+ #
300
+ # @since 2.0.0.rc.1
301
+ def foreign_key_suffix
302
+ "_id"
303
+ end
304
+
305
+ # Returns the macro for this relation. Used mostly as a helper in
306
+ # reflection.
307
+ #
308
+ # @example Get the macro.
309
+ # Referenced::Many.macro
310
+ #
311
+ # @return [ Symbol ] :references_many
312
+ def macro
313
+ :references_many
314
+ end
315
+
316
+ # Return the nested builder that is responsible for generating the documents
317
+ # that will be used by this relation.
318
+ #
319
+ # @example Get the nested builder.
320
+ # Referenced::Many.builder(attributes, options)
321
+ #
322
+ # @param [ Metadata ] metadata The relation metadata.
323
+ # @param [ Hash ] attributes The attributes to build with.
324
+ # @param [ Hash ] options The options for the builder.
325
+ #
326
+ # @option options [ true, false ] :allow_destroy Can documents be
327
+ # deleted?
328
+ # @option options [ Integer ] :limit Max number of documents to
329
+ # create at once.
330
+ # @option options [ Proc, Symbol ] :reject_if If documents match this
331
+ # option then they are ignored.
332
+ # @option options [ true, false ] :update_only Only existing documents
333
+ # can be modified.
334
+ #
335
+ # @return [ NestedBuilder ] A newly instantiated nested builder object.
336
+ #
337
+ # @since 2.0.0.rc.1
338
+ def nested_builder(metadata, attributes, options)
339
+ Builders::NestedAttributes::Many.new(metadata, attributes, options)
340
+ end
341
+
342
+ # Tells the caller if this relation is one that stores the foreign
343
+ # key on its own objects.
344
+ #
345
+ # @example Does this relation store a foreign key?
346
+ # Referenced::Many.stores_foreign_key?
347
+ #
348
+ # @return [ false ] Always false.
349
+ #
350
+ # @since 2.0.0.rc.1
351
+ def stores_foreign_key?
352
+ false
353
+ end
354
+ end
355
+ end
356
+ end
357
+ end
358
+ end
@@ -0,0 +1,379 @@
1
+ # encoding: utf-8
2
+ module Mongoid # :nodoc:
3
+ module Relations #:nodoc:
4
+ module Referenced #:nodoc:
5
+
6
+ # This class defines the behaviour for all relations that are a
7
+ # many-to-many between documents in different collections.
8
+ class ManyToMany < Relations::Many
9
+
10
+ # Binds the base object to the inverse of the relation. This is so we
11
+ # are referenced to the actual objects themselves and dont hit the
12
+ # database twice when setting the relations up.
13
+ #
14
+ # This is called after first creating the relation, or if a new object
15
+ # is set on the relation.
16
+ #
17
+ # @example Bind the relation.
18
+ # person.preferences.bind
19
+ #
20
+ # @param [ Hash ] options The options to bind with.
21
+ #
22
+ # @option options [ true, false ] :building Are we in build mode?
23
+ # @option options [ true, false ] :continue Continue binding the
24
+ # inverse?
25
+ #
26
+ # @since 2.0.0.rc.1
27
+ def bind(options = {})
28
+ binding.bind(options)
29
+ target.map(&:save) if base.persisted? && !options[:building]
30
+ end
31
+
32
+ # Clear the relation. Will delete the documents from the db if they are
33
+ # already persisted.
34
+ #
35
+ # @example Clear the relation.
36
+ # person.preferences.clear
37
+ #
38
+ # @return [ Many ] The relation emptied.
39
+ def clear
40
+ tap do |relation|
41
+ relation.unbind(default_options)
42
+ target.clear
43
+ end
44
+ end
45
+
46
+ # Delete a single document from the relation.
47
+ #
48
+ # @example Delete a document.
49
+ # person.preferences.delete(preference)
50
+ #
51
+ # @param [ Document ] document The document to delete.
52
+ #
53
+ # @since 2.0.0.rc.1
54
+ def delete(document, options = {})
55
+ target.delete(document)
56
+ binding.unbind_one(document, options)
57
+ end
58
+
59
+ # Deletes all related documents from the database given the supplied
60
+ # conditions.
61
+ #
62
+ # @example Delete all documents in the relation.
63
+ # person.preferences.delete_all
64
+ #
65
+ # @example Conditonally delete all documents in the relation.
66
+ # person.preferences.delete_all(:conditions => { :title => "Testing" })
67
+ #
68
+ # @param [ Hash ] conditions Optional conditions to delete with.
69
+ #
70
+ # @return [ Integer ] The number of documents deleted.
71
+ def delete_all(conditions = nil)
72
+ selector = (conditions || {})[:conditions] || {}
73
+ target.delete_if { |doc| doc.matches?(selector) }
74
+ scoping = { :_id => { "$in" => base.send(metadata.foreign_key) } }
75
+ metadata.klass.delete_all(:conditions => selector.merge(scoping))
76
+ end
77
+
78
+ # Destroys all related documents from the database given the supplied
79
+ # conditions.
80
+ #
81
+ # @example Destroy all documents in the relation.
82
+ # person.preferences.destroy_all
83
+ #
84
+ # @example Conditonally destroy all documents in the relation.
85
+ # person.preferences.destroy_all(:conditions => { :title => "Testing" })
86
+ #
87
+ # @param [ Hash ] conditions Optional conditions to destroy with.
88
+ #
89
+ # @return [ Integer ] The number of documents destroyd.
90
+ def destroy_all(conditions = nil)
91
+ selector = (conditions || {})[:conditions] || {}
92
+ target.delete_if { |doc| doc.matches?(selector) }
93
+ scoping = { :_id => { "$in" => base.send(metadata.foreign_key) } }
94
+ metadata.klass.destroy_all(:conditions => selector.merge(scoping))
95
+ end
96
+
97
+ # Find the matchind document on the association, either based on id or
98
+ # conditions.
99
+ #
100
+ # @example Find by an id.
101
+ # person.preferences.find(BSON::ObjectId.new)
102
+ #
103
+ # @example Find by multiple ids.
104
+ # person.preferences.find([ BSON::ObjectId.new, BSON::ObjectId.new ])
105
+ #
106
+ # @example Conditionally find all matching documents.
107
+ # person.preferences.find(:all, :conditions => { :title => "Sir" })
108
+ #
109
+ # @example Conditionally find the first document.
110
+ # person.preferences.find(:first, :conditions => { :title => "Sir" })
111
+ #
112
+ # @example Conditionally find the last document.
113
+ # person.preferences.find(:last, :conditions => { :title => "Sir" })
114
+ #
115
+ # @param [ Symbol, BSON::ObjectId, Array<BSON::ObjectId> ] arg The
116
+ # argument to search with.
117
+ # @param [ Hash ] options The options to search with.
118
+ #
119
+ # @return [ Document, Criteria ] The matching document(s).
120
+ def find(arg, options = {})
121
+ klass = metadata.klass
122
+ return klass.criteria.id_criteria(arg) unless arg.is_a?(Symbol)
123
+ selector = (options[:conditions] || {}).merge(
124
+ "_id" => { "$in" => base.send(metadata.foreign_key) }
125
+ )
126
+ klass.find(arg, :conditions => selector)
127
+ end
128
+
129
+ # Instantiate a new references_many relation. Will set the foreign key
130
+ # and the base on the inverse object.
131
+ #
132
+ # @example Create the new relation.
133
+ # Referenced::ManyToMany.new(base, target, metadata)
134
+ #
135
+ # @param [ Document ] base The document this relation hangs off of.
136
+ # @param [ Array<Document> ] target The target of the relation.
137
+ # @param [ Metadata ] metadata The relation's metadata.
138
+ def initialize(base, target, metadata)
139
+ init(base, target, metadata)
140
+ end
141
+
142
+ # Removes all associations between the base document and the target
143
+ # documents by deleting the foreign keys and the references, orphaning
144
+ # the target documents in the process.
145
+ #
146
+ # @example Nullify the relation.
147
+ # person.preferences.nullify
148
+ #
149
+ # @since 2.0.0.rc.1
150
+ def nullify
151
+ loaded and target.each do |doc|
152
+ base.send(metadata.foreign_key).delete(doc.id)
153
+ dereference(doc)
154
+ end
155
+ target.clear
156
+ end
157
+ alias :nullify_all :nullify
158
+
159
+ # Substitutes the supplied target documents for the existing documents
160
+ # in the relation. If the new target is nil, perform the necessary
161
+ # deletion.
162
+ #
163
+ # @example Replace the relation.
164
+ # person.posts.substitute(new_name)
165
+ #
166
+ # @param [ Array<Document> ] target The replacement target.
167
+ # @param [ Hash ] options The options to bind with.
168
+ #
169
+ # @option options [ true, false ] :building Are we in build mode?
170
+ # @option options [ true, false ] :continue Continue binding the
171
+ # inverse?
172
+ #
173
+ # @return [ Many ] The relation.
174
+ #
175
+ # @since 2.0.0.rc.1
176
+ def substitute(target, options = {})
177
+ tap { target ? (@target = target.to_a; bind(options)) : (@target = unbind(options)) }
178
+ end
179
+
180
+ # Unbinds the base object to the inverse of the relation. This occurs
181
+ # when setting a side of the relation to nil.
182
+ #
183
+ # Will delete the object if necessary.
184
+ #
185
+ # @example Unbind the target.
186
+ # person.posts.unbind
187
+ #
188
+ # @param [ Hash ] options The options to bind with.
189
+ #
190
+ # @option options [ true, false ] :building Are we in build mode?
191
+ # @option options [ true, false ] :continue Continue binding the
192
+ # inverse?
193
+ #
194
+ # @since 2.0.0.rc.1
195
+ def unbind(options = {})
196
+ target.each(&:delete) if base.persisted?
197
+ binding.unbind(options)
198
+ []
199
+ end
200
+
201
+ private
202
+
203
+ # Appends the document to the target array, updating the index on the
204
+ # document at the same time.
205
+ #
206
+ # @example Append the document to the relation.
207
+ # relation.append(document)
208
+ #
209
+ # @param [ Document ] document The document to append to the target.
210
+ #
211
+ # @since 2.0.0.rc.1
212
+ def append(document, options = {})
213
+ loaded and target.push(document)
214
+ metadatafy(document)
215
+ binding.bind_one(document, options)
216
+ end
217
+
218
+ # Instantiate the binding associated with this relation.
219
+ #
220
+ # @example Get the binding.
221
+ # relation.binding([ address ])
222
+ #
223
+ # @param [ Array<Document> ] new_target The new documents to bind with.
224
+ #
225
+ # @return [ Binding ] The binding.
226
+ #
227
+ # @since 2.0.0.rc.1
228
+ def binding(new_target = nil)
229
+ Bindings::Referenced::ManyToMany.new(base, new_target || target, metadata)
230
+ end
231
+
232
+ # Returns the criteria object for the target class with its documents set
233
+ # to target.
234
+ #
235
+ # @example Get a criteria for the relation.
236
+ # relation.criteria
237
+ #
238
+ # @return [ Criteria ] A new criteria.
239
+ def criteria
240
+ metadata.klass.criteria(false)
241
+ end
242
+
243
+ # Dereferences the supplied document from the base of the relation.
244
+ #
245
+ # @example Dereference the document.
246
+ # person.preferences.dereference(preference)
247
+ #
248
+ # @param [ Document ] document The document to dereference.
249
+ def dereference(document)
250
+ document.send(metadata.inverse_foreign_key).delete(base.id)
251
+ document.send(metadata.inverse(document)).target.delete(base)
252
+ document.save
253
+ end
254
+
255
+ # Will load the target into an array if the target had not already been
256
+ # loaded.
257
+ #
258
+ # @example Load the relation into memory.
259
+ # relation.loaded
260
+ #
261
+ # @return [ ManyToMany ] The relation.
262
+ #
263
+ # @since 2.0.0.rc.1
264
+ def loaded
265
+ tap do |relation|
266
+ relation.target = target.entries if target.is_a?(Mongoid::Criteria)
267
+ end
268
+ end
269
+
270
+ class << self
271
+
272
+ # Return the builder that is responsible for generating the documents
273
+ # that will be used by this relation.
274
+ #
275
+ # @example Get the builder.
276
+ # Referenced::ManyToMany.builder(meta, object)
277
+ #
278
+ # @param [ Metadata ] meta The metadata of the relation.
279
+ # @param [ Document, Hash ] object A document or attributes to build
280
+ # with.
281
+ #
282
+ # @return [ Builder ] A new builder object.
283
+ #
284
+ # @since 2.0.0.rc.1
285
+ def builder(meta, object)
286
+ Builders::Referenced::ManyToMany.new(meta, object)
287
+ end
288
+
289
+ # Returns true if the relation is an embedded one. In this case
290
+ # always false.
291
+ #
292
+ # @example Is this relation embedded?
293
+ # Referenced::ManyToMany.embedded?
294
+ #
295
+ # @return [ false ] Always false.
296
+ #
297
+ # @since 2.0.0.rc.1
298
+ def embedded?
299
+ false
300
+ end
301
+
302
+ # Get the default value for the foreign key.
303
+ #
304
+ # @example Get the default.
305
+ # Referenced::ManyToMany.foreign_key_default
306
+ #
307
+ # @return [ Array ] Always an empty array.
308
+ #
309
+ # @since 2.0.0.rc.1
310
+ def foreign_key_default
311
+ []
312
+ end
313
+
314
+ # Returns the suffix of the foreign key field, either "_id" or "_ids".
315
+ #
316
+ # @example Get the suffix for the foreign key.
317
+ # Referenced::ManyToMany.foreign_key_suffix
318
+ #
319
+ # @return [ String ] "_ids"
320
+ #
321
+ # @since 2.0.0.rc.1
322
+ def foreign_key_suffix
323
+ "_ids"
324
+ end
325
+
326
+ # Returns the macro for this relation. Used mostly as a helper in
327
+ # reflection.
328
+ #
329
+ # @example Get the macro.
330
+ # Referenced::ManyToMany.macro
331
+ #
332
+ # @return [ Symbol ] :references_and_referenced_in_many
333
+ def macro
334
+ :references_and_referenced_in_many
335
+ end
336
+
337
+ # Return the nested builder that is responsible for generating the documents
338
+ # that will be used by this relation.
339
+ #
340
+ # @example Get the nested builder.
341
+ # Referenced::ManyToMany.builder(attributes, options)
342
+ #
343
+ # @param [ Metadata ] metadata The relation metadata.
344
+ # @param [ Hash ] attributes The attributes to build with.
345
+ # @param [ Hash ] options The options for the builder.
346
+ #
347
+ # @option options [ true, false ] :allow_destroy Can documents be
348
+ # deleted?
349
+ # @option options [ Integer ] :limit Max number of documents to
350
+ # create at once.
351
+ # @option options [ Proc, Symbol ] :reject_if If documents match this
352
+ # option then they are ignored.
353
+ # @option options [ true, false ] :update_only Only existing documents
354
+ # can be modified.
355
+ #
356
+ # @return [ NestedBuilder ] A newly instantiated nested builder object.
357
+ #
358
+ # @since 2.0.0.rc.1
359
+ def nested_builder(metadata, attributes, options)
360
+ Builders::NestedAttributes::Many.new(metadata, attributes, options)
361
+ end
362
+
363
+ # Tells the caller if this relation is one that stores the foreign
364
+ # key on its own objects.
365
+ #
366
+ # @example Does this relation store a foreign key?
367
+ # Referenced::Many.stores_foreign_key?
368
+ #
369
+ # @return [ true ] Always true.
370
+ #
371
+ # @since 2.0.0.rc.1
372
+ def stores_foreign_key?
373
+ true
374
+ end
375
+ end
376
+ end
377
+ end
378
+ end
379
+ end