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
@@ -1,364 +0,0 @@
1
- # encoding: utf-8
2
- require "mongoid/associations/proxy"
3
- require "mongoid/associations/embedded_in"
4
- require "mongoid/associations/embeds_many"
5
- require "mongoid/associations/embeds_one"
6
- require "mongoid/associations/foreign_key"
7
- require "mongoid/associations/references_many"
8
- require "mongoid/associations/references_many_as_array"
9
- require "mongoid/associations/references_one"
10
- require "mongoid/associations/referenced_in"
11
- require "mongoid/associations/options"
12
- require "mongoid/associations/meta_data"
13
-
14
- module Mongoid # :nodoc:
15
- module Associations #:nodoc:
16
- extend ActiveSupport::Concern
17
- included do
18
- include ForeignKey
19
-
20
- cattr_accessor :embedded
21
- self.embedded = false
22
-
23
- class_inheritable_accessor :associations, :cascades
24
- self.associations = {}
25
- self.cascades = {}
26
-
27
- delegate :embedded, :embedded?, :to => "self.class"
28
- end
29
-
30
- # Returns the associations for the +Document+.
31
- def associations
32
- self.class.associations
33
- end
34
-
35
- # are we in an embeds_many?
36
- def embedded_many?
37
- embedded? && _parent.associations[association_name].association == EmbedsMany
38
- end
39
-
40
- # are we in an embeds_one?
41
- def embedded_one?
42
- embedded? && !embedded_many?
43
- end
44
-
45
- # Update the one-to-one relational association for the name.
46
- def update_association(name)
47
- association = send(name)
48
- association.save if new_record? && !association.nil?
49
- end
50
-
51
- # Updates all the one-to-many relational associations for the name.
52
- def update_associations(name)
53
- send(name).each { |doc| doc.save } if new_record?
54
- end
55
-
56
- def update_foreign_keys
57
- associations.each do |name, association|
58
- next unless association.macro == :referenced_in
59
- foreign_key = association.options.foreign_key
60
- if send(foreign_key).nil?
61
- proxy = send(name)
62
- send("#{foreign_key}=", proxy && proxy.target ? proxy.id : nil)
63
- end
64
- end
65
- end
66
-
67
- module ClassMethods
68
- # Gets whether or not the document is embedded.
69
- #
70
- # Example:
71
- #
72
- # <tt>Person.embedded?</tt>
73
- #
74
- # Returns:
75
- #
76
- # <tt>true</tt> if embedded, <tt>false</tt> if not.
77
- def embedded?
78
- !!self.embedded
79
- end
80
-
81
- # Adds the association back to the parent document. This macro is
82
- # necessary to set the references from the child back to the parent
83
- # document. If a child does not define this association calling
84
- # persistence methods on the child object will cause a save to fail.
85
- #
86
- # Options:
87
- #
88
- # name: A +Symbol+ that matches the name of the parent class.
89
- #
90
- # Example:
91
- #
92
- # class Person
93
- # include Mongoid::Document
94
- # embeds_many :addresses
95
- # end
96
- #
97
- # class Address
98
- # include Mongoid::Document
99
- # embedded_in :person, :inverse_of => :addresses
100
- # end
101
- def embedded_in(name, options = {}, &block)
102
- opts = optionize(name, options, nil, &block)
103
- Associations::EmbeddedIn.validate_options(opts)
104
- self.embedded = true
105
- associate(Associations::EmbeddedIn, opts)
106
- end
107
-
108
- # Adds the association from a parent document to its children. The name
109
- # of the association needs to be a pluralized form of the child class
110
- # name.
111
- #
112
- # Options:
113
- #
114
- # name: A +Symbol+ that is the plural child class name.
115
- #
116
- # Example:
117
- #
118
- # class Person
119
- # include Mongoid::Document
120
- # embeds_many :addresses
121
- # end
122
- #
123
- # class Address
124
- # include Mongoid::Document
125
- # embedded_in :person, :inverse_of => :addresses
126
- # end
127
- def embeds_many(name, options = {}, &block)
128
- opts = optionize(name, options, nil, &block)
129
- Associations::EmbedsMany.validate_options(opts)
130
- associate(Associations::EmbedsMany, opts)
131
- end
132
-
133
- alias :embed_many :embeds_many
134
-
135
- # Adds the association from a parent document to its child. The name
136
- # of the association needs to be a singular form of the child class
137
- # name.
138
- #
139
- # Options:
140
- #
141
- # name: A +Symbol+ that is the plural child class name.
142
- #
143
- # Example:
144
- #
145
- # class Person
146
- # include Mongoid::Document
147
- # embeds_one :name
148
- # end
149
- #
150
- # class Name
151
- # include Mongoid::Document
152
- # embedded_in :person
153
- # end
154
- def embeds_one(name, options = {}, &block)
155
- opts = optionize(name, options, nil, &block)
156
- type = Associations::EmbedsOne
157
- type.validate_options(opts)
158
- associate(type, opts)
159
- add_builder(type, opts)
160
- add_creator(type, opts)
161
- end
162
-
163
- alias :embed_one :embeds_one
164
-
165
- # Adds a relational association from the child Document to a Document in
166
- # another database or collection.
167
- #
168
- # Options:
169
- #
170
- # name: A +Symbol+ that is the related class name.
171
- #
172
- # Example:
173
- #
174
- # class Game
175
- # include Mongoid::Document
176
- # referenced_in :person
177
- # end
178
- #
179
- def referenced_in(name, options = {}, &block)
180
- opts = optionize(name, options, constraint(name, options, :in), &block)
181
- Associations::ReferencedIn.validate_options(opts)
182
- associate(Associations::ReferencedIn, opts)
183
- field(opts.foreign_key, :inverse_class_name => opts.class_name, :identity => true)
184
- index(opts.foreign_key, :background => true) if !embedded? && opts.index
185
- set_callback(:save, :before) { |document| document.update_foreign_keys }
186
- end
187
-
188
- alias :belongs_to_related :referenced_in
189
-
190
- # Adds a relational association from the Document to many Documents in
191
- # another database or collection.
192
- #
193
- # Options:
194
- #
195
- # name: A +Symbol+ that is the related class name pluralized.
196
- # default_order: A +Criteria+ that specifies the default sort order for
197
- # this association. (e.g. :position.asc). If an explicit ordering is
198
- # specified on a +Criteria+ object, the default order will NOT be used.
199
- #
200
- # Example:
201
- #
202
- # class Person
203
- # include Mongoid::Document
204
- # references_many :posts
205
- # references_many :board_games, :default_order => :title.asc
206
- # end
207
- #
208
- def references_many(name, options = {}, &block)
209
- reference_many(name, options, &block)
210
- set_callback :save, :before do |document|
211
- document.update_associations(name)
212
- end
213
- add_cascade(name, options)
214
- end
215
-
216
- alias :has_many_related :references_many
217
-
218
- # Adds a relational association from the Document to one Document in
219
- # another database or collection.
220
- #
221
- # Options:
222
- #
223
- # name: A +Symbol+ that is the related class name pluralized.
224
- #
225
- # Example:
226
- #
227
- # class Person
228
- # include Mongoid::Document
229
- # references_one :game
230
- # end
231
- def references_one(name, options = {}, &block)
232
- opts = optionize(name, options, constraint(name, options, :one), &block)
233
- type = Associations::ReferencesOne
234
- associate(type, opts)
235
- add_builder(type, opts)
236
- add_creator(type, opts)
237
- set_callback :save, :before do |document|
238
- document.update_association(name)
239
- end
240
- add_cascade(name, options)
241
- end
242
-
243
- alias :has_one_related :references_one
244
-
245
- # Returns the association reflection object with the supplied association
246
- # name.
247
- #
248
- # Options:
249
- #
250
- # name: The association name.
251
- #
252
- # Example:
253
- #
254
- # <tt>Person.reflect_on_association(:addresses)</tt>
255
- def reflect_on_association(name)
256
- association = associations[name.to_s]
257
- end
258
-
259
- # Returns all association meta data for the provided type.
260
- #
261
- # Options:
262
- #
263
- # macro: The association macro.
264
- #
265
- # Example:
266
- #
267
- # <tt>Person.reflect_on_all_associations(:embeds_many)</tt>
268
- def reflect_on_all_associations(macro)
269
- associations.values.select { |meta| meta.macro == macro }
270
- end
271
-
272
- protected
273
- # Adds the association to the associations hash with the type as the key,
274
- # then adds the accessors for the association. The defined setters and
275
- # getters for the associations will perform the necessary memoization.
276
- #
277
- # Example:
278
- #
279
- # <tt>Person.associate(EmbedsMany, { :name => :addresses })</tt>
280
- def associate(type, options)
281
- name = options.name.to_s
282
- associations[name] = MetaData.new(type, options)
283
- define_method(name) do
284
- memoized(name) do
285
- proxy = type.new(self, options)
286
- case proxy
287
- when Associations::ReferencesOne,
288
- Associations::EmbedsOne,
289
- Associations::ReferencedIn,
290
- Associations::EmbeddedIn
291
- proxy.target ? proxy : nil
292
- else
293
- proxy
294
- end
295
- end
296
- end
297
- define_method("#{name}=") do |object|
298
- unmemoize(name)
299
- memoized(name) { type.update(object, self, options) }
300
- end
301
- end
302
-
303
- # Adds a builder for a has_one association. This comes in the form of
304
- # build_name(attributes)
305
- def add_builder(type, options)
306
- name = options.name.to_s
307
- define_method("build_#{name}") do |*params|
308
- attrs = params[0]
309
- attr_options = params[1] || {}
310
- reset(name) do
311
- proxy = type.new(self, options)
312
- proxy.build((attrs || {}).stringify_keys)
313
- proxy
314
- end unless type == Associations::EmbedsOne && attr_options[:update_only]
315
- end
316
- end
317
-
318
- # Adds a creator for a has_one association. This comes in the form of
319
- # create_name(attributes)
320
- def add_creator(type, options)
321
- name = options.name.to_s
322
- define_method("create_#{name}") do |*params|
323
- attrs = params[0]
324
- attr_options = params[1] || {}
325
- unless type == Associations::EmbedsOne && attr_options[:update_only]
326
- send("build_#{name}", attrs, attr_options).tap(&:save)
327
- end
328
- end
329
- end
330
-
331
- # Create the callbacks for dependent deletes and destroys.
332
- def add_cascade(name, options)
333
- dependent = options[:dependent]
334
- self.cascades[name] = dependent if dependent
335
- end
336
-
337
- # build the options given the params.
338
- def optionize(name, options, foreign_key, &block)
339
- Associations::Options.new(
340
- options.merge(:name => name, :foreign_key => foreign_key, :extend => block)
341
- )
342
- end
343
-
344
- def reference_many(name, options, &block)
345
- if (options[:stored_as] == :array)
346
- foreign_key = "#{name.to_s.singularize}_ids"
347
- opts = optionize(name, options, constraint(name, options, :many_as_array), &block)
348
- field(
349
- foreign_key,
350
- :type => Array,
351
- :default => [],
352
- :identity => true,
353
- :inverse_class_name => opts.class_name
354
- )
355
- index(foreign_key, :background => true) if opts.index
356
- associate(Associations::ReferencesManyAsArray, opts)
357
- else
358
- opts = optionize(name, options, constraint(name, options, :many), &block)
359
- associate(Associations::ReferencesMany, opts)
360
- end
361
- end
362
- end
363
- end
364
- end
@@ -1,74 +0,0 @@
1
- # encoding: utf-8
2
- module Mongoid #:nodoc:
3
- module Associations #:nodoc:
4
- # Represents an association that is embedded within another document in the
5
- # database, either as one or many.
6
- class EmbeddedIn < Proxy
7
-
8
- # Creates the new association by setting the internal
9
- # target as the passed in Document. This should be the
10
- # parent.
11
- #
12
- # All method calls on this object will then be delegated
13
- # to the internal document itself.
14
- #
15
- # Options:
16
- #
17
- # document: The child +Document+
18
- # options: The association options
19
- def initialize(document, options, target = nil)
20
- if target
21
- inverse = determine_name(target, options)
22
- document.parentize(target, inverse)
23
- document.notify
24
- target.unmemoize(inverse)
25
- end
26
- @target, @options = document._parent, options
27
- extends(options)
28
- end
29
-
30
- # Returns the parent document. The id param is present for
31
- # compatibility with rails, however this could be overwritten
32
- # in the future.
33
- def find(id)
34
- @target
35
- end
36
-
37
- protected
38
- def determine_name(target, options)
39
- inverse = options.inverse_of
40
- return inverse unless inverse.is_a?(Array)
41
- inverse.detect { |name| target.respond_to?(name) }
42
- end
43
-
44
- class << self
45
- # Returns the macro used to create the association.
46
- def macro
47
- :embedded_in
48
- end
49
-
50
- # Perform an update of the relationship of the parent and child. This
51
- # is initialized by setting a parent object as the association on the
52
- # +Document+. Will properly set an embeds_one or an embeds_many.
53
- #
54
- # Returns:
55
- #
56
- # A new +EmbeddedIn+ association proxy.
57
- def update(target, child, options)
58
- new(child, options, target)
59
- end
60
-
61
- # Validate the options passed to the embedded in macro, to encapsulate
62
- # the behavior in this class instead of the associations module.
63
- #
64
- # Options:
65
- #
66
- # options: Thank you captain obvious.
67
- def validate_options(options = {})
68
- check_dependent_not_allowed!(options)
69
- check_inverse_must_be_defined!(options)
70
- end
71
- end
72
- end
73
- end
74
- end
@@ -1,299 +0,0 @@
1
- # encoding: utf-8
2
- module Mongoid #:nodoc:
3
- module Associations #:nodoc:
4
- # Represents embedding many documents within a parent document, which will
5
- # be an array as the underlying storage mechanism.
6
- class EmbedsMany < Proxy
7
-
8
- attr_accessor :association_name, :klass
9
-
10
- # Appends the object to the +Array+, setting its parent in
11
- # the process.
12
- def <<(*documents)
13
- documents.flatten.each do |doc|
14
- doc.parentize(@parent, @association_name)
15
- @target << doc
16
- doc._index = @target.size - 1
17
- doc.notify
18
- end
19
- end
20
-
21
- alias :concat :<<
22
- alias :push :<<
23
-
24
- # Builds a new Document and adds it to the association collection. The
25
- # document created will be of the same class as the others in the
26
- # association, and the attributes will be passed into the constructor.
27
- #
28
- # Returns:
29
- #
30
- # The newly created Document.
31
- def build(attrs = {}, type = nil)
32
- document = type ? type.instantiate : @klass.instantiate
33
- document.parentize(@parent, @association_name)
34
- document.write_attributes(attrs)
35
- @target << document
36
- document._index = @target.size - 1
37
- document
38
- end
39
-
40
- # Clears the association, and notifies the parents of the removal.
41
- def clear
42
- unless @target.empty?
43
- document = @target.first
44
- document._parent.update_child(document, true) if (document._parent)
45
- @target.clear
46
- end
47
- end
48
-
49
- # Returns a count of the number of documents in the association that have
50
- # actually been persisted to the database.
51
- #
52
- # Use #size if you want the total number of documents.
53
- #
54
- # Returns:
55
- #
56
- # The total number of persisted embedded docs, as flagged by the
57
- # #persisted? method.
58
- def count
59
- @target.select(&:persisted?).size
60
- end
61
-
62
- # Creates a new Document and adds it to the association collection. The
63
- # document created will be of the same class as the others in the
64
- # association, and the attributes will be passed into the constructor and
65
- # the new object will then be saved.
66
- #
67
- # Returns:
68
- #
69
- # The newly created Document.
70
- def create(attrs = {}, type = nil)
71
- build(attrs, type).tap(&:save)
72
- end
73
-
74
- # Creates a new Document and adds it to the association collection. The
75
- # document created will be of the same class as the others in the
76
- # association, and the attributes will be passed into the constructor and
77
- # the new object will then be saved. If validation fails an error will
78
- # get raised.
79
- #
80
- # Returns:
81
- #
82
- # The newly created Document.
83
- def create!(attrs = {}, type = nil)
84
- document = create(attrs, type)
85
- raise Errors::Validations.new(document) unless document.errors.empty?
86
- document
87
- end
88
-
89
- # Delete all the documents in the association without running callbacks.
90
- #
91
- # Example:
92
- #
93
- # <tt>addresses.delete_all</tt>
94
- #
95
- # Returns:
96
- #
97
- # The number of documents deleted.
98
- def delete_all(conditions = {})
99
- remove(:delete, conditions)
100
- end
101
-
102
- # Delete all the documents in the association and run destroy callbacks.
103
- #
104
- # Example:
105
- #
106
- # <tt>addresses.destroy_all</tt>
107
- #
108
- # Returns:
109
- #
110
- # The number of documents destroyed.
111
- def destroy_all(conditions = {})
112
- remove(:destroy, conditions)
113
- end
114
-
115
- # Finds a document in this association.
116
- #
117
- # If :all is passed, returns all the documents
118
- #
119
- # If an id is passed, will return the document for that id.
120
- #
121
- # Returns:
122
- #
123
- # Array or single Document.
124
- def find(param)
125
- return @target if param == :all
126
- criteria.id(param).first
127
- end
128
-
129
- # Creates the new association by finding the attributes in
130
- # the parent document with its name, and instantiating a
131
- # new document for each one found. These will then be put in an
132
- # internal array.
133
- #
134
- # This then delegated all methods to the array class since this is
135
- # essentially a proxy to an array itself.
136
- #
137
- # Options:
138
- #
139
- # parent: The parent document to the association.
140
- # options: The association options.
141
- def initialize(parent, options, target_array = nil)
142
- @parent, @association_name = parent, options.name
143
- @klass, @options = options.klass, options
144
- if target_array
145
- build_children_from_target_array(target_array)
146
- else
147
- build_children_from_attributes(parent.raw_attributes[@association_name])
148
- end
149
- extends(options)
150
- end
151
-
152
- # If the target array does not respond to the supplied method then try to
153
- # find a named scope or criteria on the class and send the call there.
154
- #
155
- # If the method exists on the array, use the default proxy behavior.
156
- def method_missing(name, *args, &block)
157
- if @target.respond_to?(name)
158
- super
159
- else
160
- @klass.send(:with_scope, criteria) do
161
- object = @klass.send(name, *args)
162
- end
163
- end
164
- end
165
-
166
- # Used for setting associations via a nested attributes setter from the
167
- # parent +Document+.
168
- #
169
- # Options:
170
- #
171
- # attributes: A +Hash+ of integer keys and +Hash+ values.
172
- #
173
- # Returns:
174
- #
175
- # The newly build target Document.
176
- def nested_build(attributes, options = {})
177
- @parent.instance_variable_set(:@building_nested, true)
178
- id_index, reordering = {}, false
179
- attributes.each do |index, attrs|
180
- document = if attrs["id"].present?
181
- reordering = true
182
- id_index[attrs["id"]] = index.to_i
183
- detect { |document| document.id.to_s == attrs["id"].to_s }
184
- else
185
- detect { |document| document._index == index.to_i }
186
- end
187
- if document
188
- if options && options[:allow_destroy] && Boolean.set(attrs['_destroy'])
189
- @target.delete(document)
190
- document.destroy
191
- else
192
- document.write_attributes(attrs)
193
- end
194
- else
195
- document = build(attrs)
196
- id_index[document.id.to_s] = index.to_i
197
- end
198
- end
199
- if reordering
200
- @target.sort! do |a, b|
201
- ai, bi = id_index[a.id.to_s], id_index[b.id.to_s]
202
- ai.nil? ? (bi.nil? ? 0 : 1) : (bi.nil? ? -1 : ai <=> bi)
203
- end
204
- end
205
- @target.each_with_index { |document, index| document._index = index }
206
- @parent.instance_variable_set(:@building_nested, false)
207
- self
208
- end
209
-
210
- # Paginate the association. Will create a new criteria, set the documents
211
- # on it and execute in an enumerable context.
212
- #
213
- # Options:
214
- #
215
- # options: A +Hash+ of pagination options.
216
- #
217
- # Returns:
218
- #
219
- # A +WillPaginate::Collection+.
220
- def paginate(options)
221
- criteria = Mongoid::Criteria.translate(@klass, options)
222
- criteria.documents = @target
223
- criteria.paginate(options)
224
- end
225
-
226
- protected
227
- # Initializes each of the attributes in the hash.
228
- def build_children_from_attributes(attributes)
229
- @target = []
230
- if attributes
231
- attributes.each_with_index do |attrs, index|
232
- klass = attrs.klass
233
- child = klass ? klass.instantiate(attrs) : @klass.instantiate(attrs)
234
- child.parentize(@parent, @association_name)
235
- child._index = index
236
- @target << child
237
- end
238
- end
239
- end
240
-
241
- # Initializes the target array from an existing array of documents.
242
- def build_children_from_target_array(target_array)
243
- @target = target_array
244
- @target.each_with_index do |child, index|
245
- child._index = index
246
- end
247
- end
248
-
249
- # Removes documents based on a method.
250
- def remove(method, conditions)
251
- criteria = @klass.find(conditions || {})
252
- criteria.documents = @target
253
- count = criteria.size
254
- criteria.each do |doc|
255
- @target.delete(doc); doc.send(method)
256
- end; count
257
- end
258
-
259
- # Returns the criteria object for the target class with its documents set
260
- # to @target.
261
- def criteria
262
- criteria = @klass.criteria
263
- criteria.documents = @target
264
- criteria
265
- end
266
-
267
- class << self
268
- # Returns the macro used to create the association.
269
- def macro
270
- :embeds_many
271
- end
272
-
273
- # Perform an update of the relationship of the parent and child. This
274
- # is initialized by setting the has_many to the supplied +Enumerable+
275
- # and setting up the parentization.
276
- def update(children, parent, options)
277
- parent.raw_attributes.delete(options.name)
278
- children.assimilate(parent, options)
279
- if children && children.first.is_a?(Mongoid::Document)
280
- new(parent, options, children)
281
- else
282
- new(parent, options)
283
- end
284
- end
285
-
286
- # Validate the options passed to the embeds many macro, to encapsulate
287
- # the behavior in this class instead of the associations module.
288
- #
289
- # Options:
290
- #
291
- # options: Thank you captain obvious.
292
- def validate_options(options = {})
293
- check_dependent_not_allowed!(options)
294
- check_inverse_not_allowed!(options)
295
- end
296
- end
297
- end
298
- end
299
- end