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

Sign up to get free protection for your applications and to get access to all the features.
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