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.
- 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
|