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.
- data/README.rdoc +8 -0
- data/Rakefile +51 -0
- data/lib/config/locales/nl.yml +39 -0
- data/lib/config/locales/ro.yml +1 -1
- data/lib/mongoid.rb +17 -17
- data/lib/mongoid/atomicity.rb +54 -22
- data/lib/mongoid/attributes.rb +145 -125
- data/lib/mongoid/callbacks.rb +7 -2
- data/lib/mongoid/collection.rb +49 -32
- data/lib/mongoid/collections.rb +0 -1
- data/lib/mongoid/components.rb +34 -29
- data/lib/mongoid/config.rb +207 -193
- data/lib/mongoid/config/database.rb +167 -0
- data/lib/mongoid/contexts.rb +2 -5
- data/lib/mongoid/contexts/enumerable.rb +30 -4
- data/lib/mongoid/contexts/ids.rb +2 -2
- data/lib/mongoid/contexts/mongo.rb +30 -5
- data/lib/mongoid/copyable.rb +44 -0
- data/lib/mongoid/criteria.rb +110 -56
- data/lib/mongoid/criterion/creational.rb +34 -0
- data/lib/mongoid/criterion/destructive.rb +37 -0
- data/lib/mongoid/criterion/exclusion.rb +3 -1
- data/lib/mongoid/criterion/inclusion.rb +59 -64
- data/lib/mongoid/criterion/inspection.rb +22 -0
- data/lib/mongoid/criterion/optional.rb +42 -54
- data/lib/mongoid/criterion/selector.rb +9 -0
- data/lib/mongoid/default_scope.rb +28 -0
- data/lib/mongoid/deprecation.rb +5 -5
- data/lib/mongoid/dirty.rb +4 -5
- data/lib/mongoid/document.rb +161 -114
- data/lib/mongoid/extensions.rb +7 -11
- data/lib/mongoid/extensions/array/parentization.rb +2 -2
- data/lib/mongoid/extensions/date/conversions.rb +1 -1
- data/lib/mongoid/extensions/hash/conversions.rb +0 -23
- data/lib/mongoid/extensions/nil/collectionization.rb +12 -0
- data/lib/mongoid/extensions/object/reflections.rb +17 -0
- data/lib/mongoid/extensions/object/yoda.rb +27 -0
- data/lib/mongoid/extensions/string/conversions.rb +23 -4
- data/lib/mongoid/extensions/time_conversions.rb +4 -4
- data/lib/mongoid/field.rb +30 -19
- data/lib/mongoid/fields.rb +15 -5
- data/lib/mongoid/finders.rb +19 -11
- data/lib/mongoid/hierarchy.rb +34 -28
- data/lib/mongoid/identity.rb +62 -20
- data/lib/mongoid/inspection.rb +58 -0
- data/lib/mongoid/matchers.rb +20 -0
- data/lib/mongoid/multi_database.rb +11 -0
- data/lib/mongoid/nested_attributes.rb +41 -0
- data/lib/mongoid/paranoia.rb +3 -4
- data/lib/mongoid/paths.rb +1 -1
- data/lib/mongoid/persistence.rb +89 -90
- data/lib/mongoid/persistence/command.rb +20 -4
- data/lib/mongoid/persistence/insert.rb +13 -11
- data/lib/mongoid/persistence/insert_embedded.rb +8 -6
- data/lib/mongoid/persistence/remove.rb +6 -4
- data/lib/mongoid/persistence/remove_all.rb +6 -4
- data/lib/mongoid/persistence/remove_embedded.rb +8 -6
- data/lib/mongoid/persistence/update.rb +12 -10
- data/lib/mongoid/railtie.rb +2 -2
- data/lib/mongoid/railties/database.rake +10 -9
- data/lib/mongoid/relations.rb +104 -0
- data/lib/mongoid/relations/accessors.rb +154 -0
- data/lib/mongoid/relations/auto_save.rb +34 -0
- data/lib/mongoid/relations/binding.rb +24 -0
- data/lib/mongoid/relations/bindings.rb +9 -0
- data/lib/mongoid/relations/bindings/embedded/in.rb +77 -0
- data/lib/mongoid/relations/bindings/embedded/many.rb +93 -0
- data/lib/mongoid/relations/bindings/embedded/one.rb +65 -0
- data/lib/mongoid/relations/bindings/referenced/in.rb +78 -0
- data/lib/mongoid/relations/bindings/referenced/many.rb +93 -0
- data/lib/mongoid/relations/bindings/referenced/many_to_many.rb +94 -0
- data/lib/mongoid/relations/bindings/referenced/one.rb +63 -0
- data/lib/mongoid/relations/builder.rb +41 -0
- data/lib/mongoid/relations/builders.rb +79 -0
- data/lib/mongoid/relations/builders/embedded/in.rb +25 -0
- data/lib/mongoid/relations/builders/embedded/many.rb +32 -0
- data/lib/mongoid/relations/builders/embedded/one.rb +26 -0
- data/lib/mongoid/relations/builders/nested_attributes/many.rb +116 -0
- data/lib/mongoid/relations/builders/nested_attributes/one.rb +135 -0
- data/lib/mongoid/relations/builders/referenced/in.rb +32 -0
- data/lib/mongoid/relations/builders/referenced/many.rb +26 -0
- data/lib/mongoid/relations/builders/referenced/many_to_many.rb +29 -0
- data/lib/mongoid/relations/builders/referenced/one.rb +30 -0
- data/lib/mongoid/relations/cascading.rb +55 -0
- data/lib/mongoid/relations/cascading/delete.rb +19 -0
- data/lib/mongoid/relations/cascading/destroy.rb +19 -0
- data/lib/mongoid/relations/cascading/nullify.rb +18 -0
- data/lib/mongoid/relations/cascading/strategy.rb +26 -0
- data/lib/mongoid/relations/cyclic.rb +97 -0
- data/lib/mongoid/relations/embedded/in.rb +172 -0
- data/lib/mongoid/relations/embedded/many.rb +450 -0
- data/lib/mongoid/relations/embedded/one.rb +169 -0
- data/lib/mongoid/relations/macros.rb +302 -0
- data/lib/mongoid/relations/many.rb +185 -0
- data/lib/mongoid/relations/metadata.rb +529 -0
- data/lib/mongoid/relations/nested_builder.rb +52 -0
- data/lib/mongoid/relations/one.rb +29 -0
- data/lib/mongoid/relations/polymorphic.rb +54 -0
- data/lib/mongoid/relations/proxy.rb +122 -0
- data/lib/mongoid/relations/referenced/in.rb +214 -0
- data/lib/mongoid/relations/referenced/many.rb +358 -0
- data/lib/mongoid/relations/referenced/many_to_many.rb +379 -0
- data/lib/mongoid/relations/referenced/one.rb +204 -0
- data/lib/mongoid/relations/reflections.rb +45 -0
- data/lib/mongoid/safe.rb +11 -1
- data/lib/mongoid/safety.rb +122 -97
- data/lib/mongoid/scope.rb +14 -9
- data/lib/mongoid/state.rb +37 -3
- data/lib/mongoid/timestamps.rb +11 -0
- data/lib/mongoid/validations.rb +42 -3
- data/lib/mongoid/validations/associated.rb +8 -5
- data/lib/mongoid/validations/uniqueness.rb +23 -2
- data/lib/mongoid/version.rb +1 -1
- data/lib/mongoid/versioning.rb +25 -16
- data/lib/rails/generators/mongoid/model/templates/model.rb +3 -1
- metadata +95 -80
- data/lib/mongoid/associations.rb +0 -364
- data/lib/mongoid/associations/embedded_in.rb +0 -74
- data/lib/mongoid/associations/embeds_many.rb +0 -299
- data/lib/mongoid/associations/embeds_one.rb +0 -111
- data/lib/mongoid/associations/foreign_key.rb +0 -35
- data/lib/mongoid/associations/meta_data.rb +0 -38
- data/lib/mongoid/associations/options.rb +0 -78
- data/lib/mongoid/associations/proxy.rb +0 -60
- data/lib/mongoid/associations/referenced_in.rb +0 -70
- data/lib/mongoid/associations/references_many.rb +0 -254
- data/lib/mongoid/associations/references_many_as_array.rb +0 -128
- data/lib/mongoid/associations/references_one.rb +0 -104
- data/lib/mongoid/extensions/array/accessors.rb +0 -17
- data/lib/mongoid/extensions/array/assimilation.rb +0 -26
- data/lib/mongoid/extensions/hash/accessors.rb +0 -42
- data/lib/mongoid/extensions/hash/assimilation.rb +0 -40
- data/lib/mongoid/extensions/nil/assimilation.rb +0 -17
- data/lib/mongoid/memoization.rb +0 -33
@@ -1,111 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Mongoid #:nodoc:
|
3
|
-
module Associations #:nodoc:
|
4
|
-
# Represents an association that is embedded in a parent document as a
|
5
|
-
# one-to-one relationship.
|
6
|
-
class EmbedsOne < Proxy
|
7
|
-
|
8
|
-
# Build a new object for the association.
|
9
|
-
def build(attrs = {}, type = nil)
|
10
|
-
@target = attrs.assimilate(@parent, @options, type); self
|
11
|
-
end
|
12
|
-
|
13
|
-
# Replaces the target with a new object
|
14
|
-
#
|
15
|
-
# Returns the association proxy
|
16
|
-
def replace(obj)
|
17
|
-
@target = obj
|
18
|
-
self
|
19
|
-
end
|
20
|
-
|
21
|
-
# Creates the new association by finding the attributes in
|
22
|
-
# the parent document with its name, and instantiating a
|
23
|
-
# new document for it.
|
24
|
-
#
|
25
|
-
# All method calls on this object will then be delegated
|
26
|
-
# to the internal document itself.
|
27
|
-
#
|
28
|
-
# Options:
|
29
|
-
#
|
30
|
-
# document: The parent +Document+
|
31
|
-
# attributes: The attributes of the target object.
|
32
|
-
# options: The association options.
|
33
|
-
#
|
34
|
-
# Returns:
|
35
|
-
#
|
36
|
-
# A new +HashOne+ association proxy.
|
37
|
-
def initialize(document, options, target = nil)
|
38
|
-
@parent, @options = document, options
|
39
|
-
|
40
|
-
if target
|
41
|
-
replace(target)
|
42
|
-
else
|
43
|
-
attributes = document.raw_attributes[options.name]
|
44
|
-
build(attributes) unless attributes.blank?
|
45
|
-
end
|
46
|
-
|
47
|
-
extends(options)
|
48
|
-
end
|
49
|
-
|
50
|
-
# Used for setting the association via a nested attributes setter on the
|
51
|
-
# parent +Document+. Called when using accepts_nested_attributes_for.
|
52
|
-
#
|
53
|
-
# Options:
|
54
|
-
#
|
55
|
-
# attributes: The attributes for the new association
|
56
|
-
#
|
57
|
-
# Returns:
|
58
|
-
#
|
59
|
-
# A new target document.
|
60
|
-
def nested_build(attributes, options = nil)
|
61
|
-
options ||= {}
|
62
|
-
_destroy = Boolean.set(attributes.delete('_destroy'))
|
63
|
-
if options[:allow_destroy] && _destroy
|
64
|
-
@target = nil
|
65
|
-
elsif @target.present? || !options[:update_only]
|
66
|
-
@target.write_attributes(attributes)
|
67
|
-
end
|
68
|
-
target
|
69
|
-
end
|
70
|
-
|
71
|
-
class << self
|
72
|
-
# Returns the macro used to create the association.
|
73
|
-
def macro
|
74
|
-
:embeds_one
|
75
|
-
end
|
76
|
-
|
77
|
-
# Perform an update of the relationship of the parent and child. This
|
78
|
-
# will assimilate the child +Document+ into the parent's object graph.
|
79
|
-
#
|
80
|
-
# Options:
|
81
|
-
#
|
82
|
-
# child: The child +Document+ or +Hash+.
|
83
|
-
# parent: The parent +Document+ to update.
|
84
|
-
# options: The association +Options+
|
85
|
-
#
|
86
|
-
# Example:
|
87
|
-
#
|
88
|
-
# <tt>EmbedsOne.update({:first_name => "Hank"}, person, options)</tt>
|
89
|
-
#
|
90
|
-
# Returns:
|
91
|
-
#
|
92
|
-
# A new +EmbedsOne+ association proxy.
|
93
|
-
def update(child, parent, options)
|
94
|
-
child.assimilate(parent, options)
|
95
|
-
new(parent, options, child.is_a?(Hash) ? nil : child)
|
96
|
-
end
|
97
|
-
|
98
|
-
# Validate the options passed to the embeds one macro, to encapsulate
|
99
|
-
# the behavior in this class instead of the associations module.
|
100
|
-
#
|
101
|
-
# Options:
|
102
|
-
#
|
103
|
-
# options: Thank you captain obvious.
|
104
|
-
def validate_options(options = {})
|
105
|
-
check_dependent_not_allowed!(options)
|
106
|
-
check_inverse_not_allowed!(options)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Mongoid #:nodoc:
|
3
|
-
module Associations #:nodoc:
|
4
|
-
module ForeignKey #:nodoc:
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
|
7
|
-
module ClassMethods #:nodoc:
|
8
|
-
# Determine the value for the foreign key constriant field in the
|
9
|
-
# database, based on the type of association or if the actual value was
|
10
|
-
# supplied as an option.
|
11
|
-
#
|
12
|
-
# Example:
|
13
|
-
#
|
14
|
-
# <tt>contraint(:posts, {}, :references_one)</tt>
|
15
|
-
#
|
16
|
-
# Returns
|
17
|
-
#
|
18
|
-
# A +String+ for the foreign key field.
|
19
|
-
def constraint(name, options, association)
|
20
|
-
key = options[:foreign_key]
|
21
|
-
|
22
|
-
# Always return the supplied foreign_key option if it was supplied -
|
23
|
-
# the user should always be ble to override.
|
24
|
-
return key.to_s if key
|
25
|
-
|
26
|
-
case association
|
27
|
-
when :one, :many then self.name.foreign_key
|
28
|
-
when :many_as_array then "#{name.to_s.singularize}_ids"
|
29
|
-
else name.to_s.foreign_key
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Mongoid #:nodoc:
|
3
|
-
module Associations #:nodoc:
|
4
|
-
# This class contains metadata about association proxies.
|
5
|
-
class MetaData
|
6
|
-
|
7
|
-
attr_reader :association, :options
|
8
|
-
|
9
|
-
delegate :macro, :to => :association
|
10
|
-
|
11
|
-
# Delegate all methods on +Options+ to the options instance.
|
12
|
-
Associations::Options.public_instance_methods(false).each do |name|
|
13
|
-
define_method(name) { |*args| @options.send(name) }
|
14
|
-
end
|
15
|
-
|
16
|
-
# Return true if this meta data is for an embedded association.
|
17
|
-
#
|
18
|
-
# Example:
|
19
|
-
#
|
20
|
-
# <tt>metadata.embedded?</tt>
|
21
|
-
def embedded?
|
22
|
-
[ EmbedsOne, EmbedsMany ].include?(association)
|
23
|
-
end
|
24
|
-
|
25
|
-
# Create the new associations MetaData object, which holds the type of
|
26
|
-
# the association and its options, with convenience methods for getting
|
27
|
-
# that information.
|
28
|
-
#
|
29
|
-
# Options:
|
30
|
-
#
|
31
|
-
# association: The association type as a class instance.
|
32
|
-
# options: The association options
|
33
|
-
def initialize(association, options)
|
34
|
-
@association, @options = association, options
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Mongoid #:nodoc:
|
3
|
-
module Associations #:nodoc:
|
4
|
-
class Options < Hash #:nodoc:
|
5
|
-
|
6
|
-
# Create the new +Options+ object, which provides convenience methods for
|
7
|
-
# accessing values out of an options +Hash+.
|
8
|
-
def initialize(attributes = {})
|
9
|
-
self.merge!(attributes)
|
10
|
-
end
|
11
|
-
|
12
|
-
# For relational associations we want to know if we cascade deletes or
|
13
|
-
# destroys to associations.
|
14
|
-
def dependent
|
15
|
-
self[:dependent]
|
16
|
-
end
|
17
|
-
|
18
|
-
# Returns the extension if it exists, nil if not.
|
19
|
-
def extension
|
20
|
-
self[:extend]
|
21
|
-
end
|
22
|
-
|
23
|
-
# Returns true is the options have extensions.
|
24
|
-
def extension?
|
25
|
-
!extension.nil?
|
26
|
-
end
|
27
|
-
|
28
|
-
# Return the foreign key if it exists, otherwise inflect it from the
|
29
|
-
# associated class name.
|
30
|
-
def foreign_key
|
31
|
-
key = self[:foreign_key] || klass.name.to_s.foreign_key
|
32
|
-
key.to_s
|
33
|
-
end
|
34
|
-
|
35
|
-
# Returns whether the foreign key column is indexed.
|
36
|
-
def index
|
37
|
-
self[:index] || false
|
38
|
-
end
|
39
|
-
|
40
|
-
# Returns the name of the inverse_of association
|
41
|
-
def inverse_of
|
42
|
-
self[:inverse_of]
|
43
|
-
end
|
44
|
-
|
45
|
-
# Return a +Class+ for the options. See #class_name
|
46
|
-
def klass
|
47
|
-
class_name.constantize
|
48
|
-
end
|
49
|
-
|
50
|
-
# Return a +String+ representing the associated class_name. If a class_name
|
51
|
-
# was provided, then the constantized class_name will be returned. If not,
|
52
|
-
# a constant based on the association name will be returned.
|
53
|
-
def class_name
|
54
|
-
self[:class_name] || name.to_s.classify
|
55
|
-
end
|
56
|
-
|
57
|
-
# Returns the association name of the options.
|
58
|
-
def name
|
59
|
-
self[:name].to_s
|
60
|
-
end
|
61
|
-
|
62
|
-
# Returns whether or not this association is polymorphic.
|
63
|
-
def polymorphic
|
64
|
-
self[:polymorphic] == true
|
65
|
-
end
|
66
|
-
|
67
|
-
# Used with references_many to save as array of ids.
|
68
|
-
def stored_as
|
69
|
-
self[:stored_as]
|
70
|
-
end
|
71
|
-
|
72
|
-
# Used with references_many to define a default sorting order
|
73
|
-
def default_order
|
74
|
-
self[:default_order]
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
@@ -1,60 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Mongoid #:nodoc
|
3
|
-
module Associations #:nodoc
|
4
|
-
class Proxy #:nodoc
|
5
|
-
instance_methods.each do |method|
|
6
|
-
undef_method(method) unless method =~ /(^__|^send$|^object_id$|^extend$)/
|
7
|
-
end
|
8
|
-
attr_reader \
|
9
|
-
:options,
|
10
|
-
:target
|
11
|
-
|
12
|
-
# Default behavior of method missing should be to delegate all calls
|
13
|
-
# to the target of the proxy. This can be overridden in special cases.
|
14
|
-
def method_missing(name, *args, &block)
|
15
|
-
@target.send(name, *args, &block)
|
16
|
-
end
|
17
|
-
|
18
|
-
# If anonymous extensions are added this will take care of them.
|
19
|
-
def extends(options)
|
20
|
-
extend Module.new(&options.extension) if options.extension?
|
21
|
-
end
|
22
|
-
|
23
|
-
# Sets up the parent, klass, foreign_key, options
|
24
|
-
def setup(document, options)
|
25
|
-
@parent = document
|
26
|
-
@klass = options.klass
|
27
|
-
@options = options
|
28
|
-
@foreign_key = options.foreign_key
|
29
|
-
extends(options)
|
30
|
-
end
|
31
|
-
|
32
|
-
protected
|
33
|
-
class << self
|
34
|
-
def check_dependent_not_allowed!(options)
|
35
|
-
if options.has_key?(:dependent)
|
36
|
-
raise Errors::InvalidOptions.new(
|
37
|
-
"dependent_only_references_one_or_many", {}
|
38
|
-
)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def check_inverse_not_allowed!(options)
|
43
|
-
if options.has_key?(:inverse_of)
|
44
|
-
raise Errors::InvalidOptions.new(
|
45
|
-
"association_cant_have_inverse_of", {}
|
46
|
-
)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def check_inverse_must_be_defined!(options)
|
51
|
-
unless options.has_key?(:inverse_of)
|
52
|
-
raise Errors::InvalidOptions.new(
|
53
|
-
"embedded_in_must_have_inverse_of", {}
|
54
|
-
)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Mongoid #:nodoc:
|
3
|
-
module Associations #:nodoc:
|
4
|
-
# Represents a relational association to a "parent" object.
|
5
|
-
class ReferencedIn < Proxy
|
6
|
-
|
7
|
-
# Initializing a related association only requires looking up the object
|
8
|
-
# by its id.
|
9
|
-
#
|
10
|
-
# Options:
|
11
|
-
#
|
12
|
-
# document: The +Document+ that contains the relationship.
|
13
|
-
# options: The association +Options+.
|
14
|
-
def initialize(document, options, target = nil)
|
15
|
-
@options = options
|
16
|
-
|
17
|
-
if target
|
18
|
-
replace(target)
|
19
|
-
else
|
20
|
-
foreign_key = document.send(options.foreign_key)
|
21
|
-
replace(options.klass.find(foreign_key)) unless foreign_key.blank?
|
22
|
-
end
|
23
|
-
|
24
|
-
extends(options)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Replaces the target with a new object
|
28
|
-
#
|
29
|
-
# Returns the association proxy
|
30
|
-
def replace(obj)
|
31
|
-
@target = obj
|
32
|
-
self
|
33
|
-
end
|
34
|
-
|
35
|
-
class << self
|
36
|
-
# Returns the macro used to create the association.
|
37
|
-
def macro
|
38
|
-
:referenced_in
|
39
|
-
end
|
40
|
-
|
41
|
-
# Perform an update of the relationship of the parent and child. This
|
42
|
-
# will assimilate the child +Document+ into the parent's object graph.
|
43
|
-
#
|
44
|
-
# Options:
|
45
|
-
#
|
46
|
-
# target: The target(parent) object
|
47
|
-
# document: The +Document+ to update.
|
48
|
-
# options: The association +Options+
|
49
|
-
#
|
50
|
-
# Example:
|
51
|
-
#
|
52
|
-
# <tt>ReferencedIn.update(person, game, options)</tt>
|
53
|
-
def update(target, document, options)
|
54
|
-
document.send("#{options.foreign_key}=", target ? target.id : nil)
|
55
|
-
new(document, options, target)
|
56
|
-
end
|
57
|
-
|
58
|
-
# Validate the options passed to the referenced in macro, to encapsulate
|
59
|
-
# the behavior in this class instead of the associations module.
|
60
|
-
#
|
61
|
-
# Options:
|
62
|
-
#
|
63
|
-
# options: Thank you captain obvious.
|
64
|
-
def validate_options(options = {})
|
65
|
-
check_dependent_not_allowed!(options)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
@@ -1,254 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Mongoid #:nodoc:
|
3
|
-
module Associations #:nodoc:
|
4
|
-
# Represents an relational one-to-many association with an object in a
|
5
|
-
# separate collection or database.
|
6
|
-
class ReferencesMany < Proxy
|
7
|
-
|
8
|
-
# Appends the object to the +Array+, setting its parent in
|
9
|
-
# the process.
|
10
|
-
def <<(*objects)
|
11
|
-
load_target
|
12
|
-
objects.flatten.each do |object|
|
13
|
-
object.write_attribute(@foreign_key, @parent.id)
|
14
|
-
@target << object
|
15
|
-
object.save unless @parent.new_record?
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
alias :concat :<<
|
20
|
-
alias :push :<<
|
21
|
-
|
22
|
-
# Builds a new Document and adds it to the association collection. The
|
23
|
-
# document created will be of the same class as the others in the
|
24
|
-
# association, and the attributes will be passed into the constructor.
|
25
|
-
#
|
26
|
-
# Returns the newly created object.
|
27
|
-
def build(attributes = {},type = nil)
|
28
|
-
load_target
|
29
|
-
name = determine_name
|
30
|
-
object = (type || @klass).instantiate(attributes)
|
31
|
-
object.send("#{name}=", @parent)
|
32
|
-
@target << object
|
33
|
-
object
|
34
|
-
end
|
35
|
-
|
36
|
-
# Creates a new Document and adds it to the association collection. The
|
37
|
-
# document created will be of the same class as the others in the
|
38
|
-
# association, and the attributes will be passed into the constructor and
|
39
|
-
# the new object will then be saved.
|
40
|
-
#
|
41
|
-
# Returns the newly created object.
|
42
|
-
def create(attributes = nil,type = nil)
|
43
|
-
build(attributes,type).tap(&:save)
|
44
|
-
end
|
45
|
-
|
46
|
-
# Creates a new Document and adds it to the association collection. If
|
47
|
-
# validation fails an error is raised.
|
48
|
-
#
|
49
|
-
# Returns the newly created object.
|
50
|
-
def create!(attributes = nil,type = nil)
|
51
|
-
build(attributes,type).tap(&:save!)
|
52
|
-
end
|
53
|
-
|
54
|
-
# Delete all the associated objects.
|
55
|
-
#
|
56
|
-
# Example:
|
57
|
-
#
|
58
|
-
# <tt>person.posts.delete_all</tt>
|
59
|
-
#
|
60
|
-
# Returns:
|
61
|
-
#
|
62
|
-
# The number of objects deleted.
|
63
|
-
def delete_all(conditions = {})
|
64
|
-
remove(:delete_all, conditions[:conditions])
|
65
|
-
end
|
66
|
-
|
67
|
-
# Destroy all the associated objects.
|
68
|
-
#
|
69
|
-
# Example:
|
70
|
-
#
|
71
|
-
# <tt>person.posts.destroy_all</tt>
|
72
|
-
#
|
73
|
-
# Returns:
|
74
|
-
#
|
75
|
-
# The number of objects destroyed.
|
76
|
-
def destroy_all(conditions = {})
|
77
|
-
remove(:destroy_all, conditions[:conditions])
|
78
|
-
end
|
79
|
-
|
80
|
-
# Finds a document in this association.
|
81
|
-
# If an id is passed, will return the document for that id.
|
82
|
-
def find(id_or_type, options = {})
|
83
|
-
return self.id_criteria(id_or_type) unless id_or_type.is_a?(Symbol)
|
84
|
-
options[:conditions] = (options[:conditions] || {}).merge(@foreign_key.to_sym => @parent.id)
|
85
|
-
@klass.find(id_or_type, options)
|
86
|
-
end
|
87
|
-
|
88
|
-
# Initializing a related association only requires looking up the objects
|
89
|
-
# by their ids.
|
90
|
-
#
|
91
|
-
# Options:
|
92
|
-
#
|
93
|
-
# document: The +Document+ that contains the relationship.
|
94
|
-
# options: The association +Options+.
|
95
|
-
def initialize(document, options, target = nil)
|
96
|
-
setup(document, options)
|
97
|
-
@target = target || query.call
|
98
|
-
end
|
99
|
-
|
100
|
-
# Override the default behavior to allow the criteria to get reset on
|
101
|
-
# each call into the association.
|
102
|
-
#
|
103
|
-
# Example:
|
104
|
-
#
|
105
|
-
# person.posts.where(:title => "New")
|
106
|
-
# person.posts # resets the criteria
|
107
|
-
#
|
108
|
-
# Returns:
|
109
|
-
#
|
110
|
-
# A Criteria object or Array.
|
111
|
-
def method_missing(name, *args, &block)
|
112
|
-
@target = query.call unless @target.is_a?(Array)
|
113
|
-
@target.send(name, *args, &block)
|
114
|
-
end
|
115
|
-
|
116
|
-
# Used for setting associations via a nested attributes setter from the
|
117
|
-
# parent +Document+.
|
118
|
-
#
|
119
|
-
# Options:
|
120
|
-
#
|
121
|
-
# attributes: A +Hash+ of integer keys and +Hash+ values.
|
122
|
-
#
|
123
|
-
# Returns:
|
124
|
-
#
|
125
|
-
# The newly build target Document.
|
126
|
-
def nested_build(attributes, options = {})
|
127
|
-
attributes.each do |index, attrs|
|
128
|
-
begin
|
129
|
-
_destroy = Boolean.set(attrs.delete('_destroy'))
|
130
|
-
document = find(attrs.delete("id"))
|
131
|
-
if options && options[:allow_destroy] && _destroy
|
132
|
-
@target.delete(document)
|
133
|
-
document.destroy
|
134
|
-
else
|
135
|
-
document.update_attributes(attrs)
|
136
|
-
end
|
137
|
-
rescue Errors::DocumentNotFound
|
138
|
-
create(attrs)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
protected
|
144
|
-
# Load the target entries if the parent document is new.
|
145
|
-
def load_target
|
146
|
-
@target = @target.entries if @parent.new_record?
|
147
|
-
end
|
148
|
-
|
149
|
-
def determine_name
|
150
|
-
@proxy ||= class << self; self; end
|
151
|
-
@proxy.send(:determine_name, @parent, @options)
|
152
|
-
end
|
153
|
-
|
154
|
-
# The default query used for retrieving the documents from the database.
|
155
|
-
# In this case we use the common API between Mongoid, ActiveRecord, and
|
156
|
-
# DataMapper so we can do one-to-many relationships with data in other
|
157
|
-
# databases.
|
158
|
-
#
|
159
|
-
# Example:
|
160
|
-
#
|
161
|
-
# <tt>association.query</tt>
|
162
|
-
#
|
163
|
-
# Returns:
|
164
|
-
#
|
165
|
-
# A +Criteria+ if a Mongoid association.
|
166
|
-
# An +Array+ of objects if an ActiveRecord association
|
167
|
-
# A +Collection+ if a DataMapper association.
|
168
|
-
def query
|
169
|
-
@query ||= lambda {
|
170
|
-
@klass.all(:conditions => { @foreign_key => @parent.id }).tap do |crit|
|
171
|
-
crit.set_order_by(@options.default_order) if @options.default_order
|
172
|
-
end
|
173
|
-
}
|
174
|
-
end
|
175
|
-
|
176
|
-
# Remove the objects based on conditions.
|
177
|
-
def remove(method, conditions)
|
178
|
-
selector = { @foreign_key => @parent.id }.merge(conditions || {})
|
179
|
-
removed = @klass.send(method, :conditions => selector)
|
180
|
-
reset; removed
|
181
|
-
end
|
182
|
-
|
183
|
-
# Reset the memoized association on the parent. This will execute the
|
184
|
-
# database query again.
|
185
|
-
#
|
186
|
-
# Example:
|
187
|
-
#
|
188
|
-
# <tt>association.reset</tt>
|
189
|
-
#
|
190
|
-
# Returns:
|
191
|
-
#
|
192
|
-
# See #query rdoc for return values.
|
193
|
-
def reset
|
194
|
-
@parent.send(:reset, @options.name) { query.call }
|
195
|
-
end
|
196
|
-
|
197
|
-
class << self
|
198
|
-
# Preferred method for creating the new +ReferencesMany+ association.
|
199
|
-
#
|
200
|
-
# Options:
|
201
|
-
#
|
202
|
-
# document: The +Document+ that contains the relationship.
|
203
|
-
# options: The association +Options+.
|
204
|
-
def instantiate(document, options, target = nil)
|
205
|
-
new(document, options, target)
|
206
|
-
end
|
207
|
-
|
208
|
-
# Returns the macro used to create the association.
|
209
|
-
def macro
|
210
|
-
:references_many
|
211
|
-
end
|
212
|
-
|
213
|
-
# Perform an update of the relationship of the parent and child. This
|
214
|
-
# will assimilate the child +Document+ into the parent's object graph.
|
215
|
-
#
|
216
|
-
# Options:
|
217
|
-
#
|
218
|
-
# related: The related object
|
219
|
-
# parent: The parent +Document+ to update.
|
220
|
-
# options: The association +Options+
|
221
|
-
#
|
222
|
-
# Example:
|
223
|
-
#
|
224
|
-
# <tt>RelatesToOne.update(game, person, options)</tt>
|
225
|
-
def update(target, document, options)
|
226
|
-
name = determine_name(document, options)
|
227
|
-
target.each { |child| child.send("#{name}=", document) }
|
228
|
-
instantiate(document, options, target)
|
229
|
-
end
|
230
|
-
|
231
|
-
protected
|
232
|
-
def determine_name(document, options)
|
233
|
-
target = document.class
|
234
|
-
if (inverse = options.inverse_of) && inverse.is_a?(Array)
|
235
|
-
inverse = [*inverse].detect { |name| target.respond_to?(name) }
|
236
|
-
end
|
237
|
-
if !inverse
|
238
|
-
association = detect_association(target, options, false)
|
239
|
-
association = detect_association(target, options, true) if association.blank?
|
240
|
-
inferred = association.name if association
|
241
|
-
end
|
242
|
-
inverse || inferred || target.to_s.underscore
|
243
|
-
end
|
244
|
-
|
245
|
-
def detect_association(target, options, with_class_name = false)
|
246
|
-
association = options.klass.associations.values.detect do |metadata|
|
247
|
-
metadata.options.klass == target &&
|
248
|
-
(with_class_name ? true : metadata.options[:class_name].nil?)
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|