mongoid-rails2 1.9.3
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/MIT_LICENSE +20 -0
- data/README.rdoc +49 -0
- data/lib/mongoid/associations/belongs_to_related.rb +58 -0
- data/lib/mongoid/associations/embedded_in.rb +72 -0
- data/lib/mongoid/associations/embeds_many.rb +254 -0
- data/lib/mongoid/associations/embeds_one.rb +96 -0
- data/lib/mongoid/associations/has_many_related.rb +181 -0
- data/lib/mongoid/associations/has_one_related.rb +85 -0
- data/lib/mongoid/associations/meta_data.rb +29 -0
- data/lib/mongoid/associations/options.rb +57 -0
- data/lib/mongoid/associations/proxy.rb +24 -0
- data/lib/mongoid/associations.rb +300 -0
- data/lib/mongoid/attributes.rb +204 -0
- data/lib/mongoid/callbacks.rb +23 -0
- data/lib/mongoid/collection.rb +120 -0
- data/lib/mongoid/collections/cyclic_iterator.rb +34 -0
- data/lib/mongoid/collections/master.rb +29 -0
- data/lib/mongoid/collections/operations.rb +41 -0
- data/lib/mongoid/collections/slaves.rb +45 -0
- data/lib/mongoid/collections.rb +41 -0
- data/lib/mongoid/components.rb +27 -0
- data/lib/mongoid/concern.rb +31 -0
- data/lib/mongoid/config.rb +191 -0
- data/lib/mongoid/contexts/enumerable.rb +151 -0
- data/lib/mongoid/contexts/ids.rb +25 -0
- data/lib/mongoid/contexts/mongo.rb +285 -0
- data/lib/mongoid/contexts/paging.rb +50 -0
- data/lib/mongoid/contexts.rb +25 -0
- data/lib/mongoid/criteria.rb +249 -0
- data/lib/mongoid/criterion/complex.rb +21 -0
- data/lib/mongoid/criterion/exclusion.rb +65 -0
- data/lib/mongoid/criterion/inclusion.rb +110 -0
- data/lib/mongoid/criterion/optional.rb +136 -0
- data/lib/mongoid/cursor.rb +81 -0
- data/lib/mongoid/deprecation.rb +22 -0
- data/lib/mongoid/dirty.rb +253 -0
- data/lib/mongoid/document.rb +311 -0
- data/lib/mongoid/errors.rb +108 -0
- data/lib/mongoid/extensions/array/accessors.rb +17 -0
- data/lib/mongoid/extensions/array/aliasing.rb +4 -0
- data/lib/mongoid/extensions/array/assimilation.rb +26 -0
- data/lib/mongoid/extensions/array/conversions.rb +29 -0
- data/lib/mongoid/extensions/array/parentization.rb +13 -0
- data/lib/mongoid/extensions/big_decimal/conversions.rb +19 -0
- data/lib/mongoid/extensions/binary/conversions.rb +17 -0
- data/lib/mongoid/extensions/boolean/conversions.rb +22 -0
- data/lib/mongoid/extensions/date/conversions.rb +24 -0
- data/lib/mongoid/extensions/datetime/conversions.rb +12 -0
- data/lib/mongoid/extensions/float/conversions.rb +20 -0
- data/lib/mongoid/extensions/hash/accessors.rb +38 -0
- data/lib/mongoid/extensions/hash/assimilation.rb +39 -0
- data/lib/mongoid/extensions/hash/conversions.rb +45 -0
- data/lib/mongoid/extensions/hash/criteria_helpers.rb +21 -0
- data/lib/mongoid/extensions/hash/scoping.rb +12 -0
- data/lib/mongoid/extensions/integer/conversions.rb +20 -0
- data/lib/mongoid/extensions/nil/assimilation.rb +17 -0
- data/lib/mongoid/extensions/object/conversions.rb +33 -0
- data/lib/mongoid/extensions/objectid/conversions.rb +15 -0
- data/lib/mongoid/extensions/proc/scoping.rb +12 -0
- data/lib/mongoid/extensions/string/conversions.rb +15 -0
- data/lib/mongoid/extensions/string/inflections.rb +97 -0
- data/lib/mongoid/extensions/symbol/inflections.rb +36 -0
- data/lib/mongoid/extensions/time_conversions.rb +35 -0
- data/lib/mongoid/extensions.rb +101 -0
- data/lib/mongoid/extras.rb +61 -0
- data/lib/mongoid/factory.rb +20 -0
- data/lib/mongoid/field.rb +59 -0
- data/lib/mongoid/fields.rb +65 -0
- data/lib/mongoid/finders.rb +144 -0
- data/lib/mongoid/identity.rb +39 -0
- data/lib/mongoid/indexes.rb +30 -0
- data/lib/mongoid/javascript/functions.yml +37 -0
- data/lib/mongoid/javascript.rb +21 -0
- data/lib/mongoid/matchers/all.rb +11 -0
- data/lib/mongoid/matchers/default.rb +26 -0
- data/lib/mongoid/matchers/exists.rb +13 -0
- data/lib/mongoid/matchers/gt.rb +11 -0
- data/lib/mongoid/matchers/gte.rb +11 -0
- data/lib/mongoid/matchers/in.rb +11 -0
- data/lib/mongoid/matchers/lt.rb +11 -0
- data/lib/mongoid/matchers/lte.rb +11 -0
- data/lib/mongoid/matchers/ne.rb +11 -0
- data/lib/mongoid/matchers/nin.rb +11 -0
- data/lib/mongoid/matchers/size.rb +11 -0
- data/lib/mongoid/matchers.rb +36 -0
- data/lib/mongoid/memoization.rb +33 -0
- data/lib/mongoid/named_scope.rb +37 -0
- data/lib/mongoid/observable.rb +30 -0
- data/lib/mongoid/paths.rb +62 -0
- data/lib/mongoid/persistence/command.rb +39 -0
- data/lib/mongoid/persistence/insert.rb +50 -0
- data/lib/mongoid/persistence/insert_embedded.rb +38 -0
- data/lib/mongoid/persistence/remove.rb +39 -0
- data/lib/mongoid/persistence/remove_all.rb +37 -0
- data/lib/mongoid/persistence/remove_embedded.rb +50 -0
- data/lib/mongoid/persistence/update.rb +63 -0
- data/lib/mongoid/persistence.rb +222 -0
- data/lib/mongoid/scope.rb +75 -0
- data/lib/mongoid/state.rb +39 -0
- data/lib/mongoid/timestamps.rb +27 -0
- data/lib/mongoid/version.rb +4 -0
- data/lib/mongoid/versioning.rb +27 -0
- data/lib/mongoid.rb +122 -0
- metadata +298 -0
|
@@ -0,0 +1,181 @@
|
|
|
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 HasManyRelated < 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.send("#{@foreign_key}=", @parent.id)
|
|
14
|
+
@target << object
|
|
15
|
+
object.save unless @parent.new_record?
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Builds a new Document and adds it to the association collection. The
|
|
20
|
+
# document created will be of the same class as the others in the
|
|
21
|
+
# association, and the attributes will be passed into the constructor.
|
|
22
|
+
#
|
|
23
|
+
# Returns the newly created object.
|
|
24
|
+
def build(attributes = {})
|
|
25
|
+
load_target
|
|
26
|
+
name = @parent.class.to_s.underscore
|
|
27
|
+
object = @klass.instantiate(attributes.merge(name => @parent))
|
|
28
|
+
@target << object
|
|
29
|
+
object
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Delegates to <<
|
|
33
|
+
def concat(*objects)
|
|
34
|
+
self << objects
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Creates a new Document and adds it to the association collection. The
|
|
38
|
+
# document created will be of the same class as the others in the
|
|
39
|
+
# association, and the attributes will be passed into the constructor and
|
|
40
|
+
# the new object will then be saved.
|
|
41
|
+
#
|
|
42
|
+
# Returns the newly created object.
|
|
43
|
+
def create(attributes)
|
|
44
|
+
object = build(attributes)
|
|
45
|
+
object.save; object
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Creates a new Document and adds it to the association collection. If
|
|
49
|
+
# validation fails an error is raised.
|
|
50
|
+
#
|
|
51
|
+
# Returns the newly created object.
|
|
52
|
+
def create!(attributes)
|
|
53
|
+
object = build(attributes)
|
|
54
|
+
object.save!; object
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Delete all the associated objects.
|
|
58
|
+
#
|
|
59
|
+
# Example:
|
|
60
|
+
#
|
|
61
|
+
# <tt>person.posts.delete_all</tt>
|
|
62
|
+
#
|
|
63
|
+
# Returns:
|
|
64
|
+
#
|
|
65
|
+
# The number of objects deleted.
|
|
66
|
+
def delete_all(conditions = {})
|
|
67
|
+
remove(:delete_all, conditions[:conditions])
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Destroy all the associated objects.
|
|
71
|
+
#
|
|
72
|
+
# Example:
|
|
73
|
+
#
|
|
74
|
+
# <tt>person.posts.destroy_all</tt>
|
|
75
|
+
#
|
|
76
|
+
# Returns:
|
|
77
|
+
#
|
|
78
|
+
# The number of objects destroyed.
|
|
79
|
+
def destroy_all(conditions = {})
|
|
80
|
+
remove(:destroy_all, conditions[:conditions])
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Finds a document in this association.
|
|
84
|
+
# If an id is passed, will return the document for that id.
|
|
85
|
+
def find(*args)
|
|
86
|
+
args[1][:conditions].merge!(@foreign_key.to_sym => @parent.id) if args.size > 1
|
|
87
|
+
@klass.find(*args)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Initializing a related association only requires looking up the objects
|
|
91
|
+
# by their ids.
|
|
92
|
+
#
|
|
93
|
+
# Options:
|
|
94
|
+
#
|
|
95
|
+
# document: The +Document+ that contains the relationship.
|
|
96
|
+
# options: The association +Options+.
|
|
97
|
+
def initialize(document, options, target = nil)
|
|
98
|
+
@parent, @klass, @options = document, options.klass, options
|
|
99
|
+
@foreign_key = options.foreign_key
|
|
100
|
+
@base = lambda { @klass.all(:conditions => { @foreign_key => document.id }) }
|
|
101
|
+
@target = target || @base.call
|
|
102
|
+
extends(options)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Override the default behavior to allow the criteria to get reset on
|
|
106
|
+
# each call into the association.
|
|
107
|
+
#
|
|
108
|
+
# Example:
|
|
109
|
+
#
|
|
110
|
+
# person.posts.where(:title => "New")
|
|
111
|
+
# person.posts # resets the criteria
|
|
112
|
+
#
|
|
113
|
+
# Returns:
|
|
114
|
+
#
|
|
115
|
+
# A Criteria object or Array.
|
|
116
|
+
def method_missing(name, *args, &block)
|
|
117
|
+
@target = @base.call unless @target.is_a?(Array)
|
|
118
|
+
@target.send(name, *args, &block)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Delegates to <<
|
|
122
|
+
def push(*objects)
|
|
123
|
+
self << objects
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
protected
|
|
127
|
+
# Load the target entries if the document is new.
|
|
128
|
+
def load_target
|
|
129
|
+
@target = @target.entries if @parent.new_record?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Remove the objects based on conditions.
|
|
133
|
+
def remove(method, conditions)
|
|
134
|
+
selector = { @foreign_key => @parent.id }.merge(conditions || {})
|
|
135
|
+
removed = @klass.send(method, :conditions => selector)
|
|
136
|
+
reset; removed
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Reset the memoized association on the parent.
|
|
140
|
+
def reset
|
|
141
|
+
@parent.send(:reset, @options.name) { @base.call }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
class << self
|
|
145
|
+
# Preferred method for creating the new +HasManyRelated+ association.
|
|
146
|
+
#
|
|
147
|
+
# Options:
|
|
148
|
+
#
|
|
149
|
+
# document: The +Document+ that contains the relationship.
|
|
150
|
+
# options: The association +Options+.
|
|
151
|
+
def instantiate(document, options, target = nil)
|
|
152
|
+
new(document, options, target)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Returns the macro used to create the association.
|
|
156
|
+
def macro
|
|
157
|
+
:has_many_related
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Perform an update of the relationship of the parent and child. This
|
|
161
|
+
# will assimilate the child +Document+ into the parent's object graph.
|
|
162
|
+
#
|
|
163
|
+
# Options:
|
|
164
|
+
#
|
|
165
|
+
# related: The related object
|
|
166
|
+
# parent: The parent +Document+ to update.
|
|
167
|
+
# options: The association +Options+
|
|
168
|
+
#
|
|
169
|
+
# Example:
|
|
170
|
+
#
|
|
171
|
+
# <tt>RelatesToOne.update(game, person, options)</tt>
|
|
172
|
+
def update(target, document, options)
|
|
173
|
+
name = document.class.to_s.underscore
|
|
174
|
+
target.each { |child| child.send("#{name}=", document) }
|
|
175
|
+
instantiate(document, options, target)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Mongoid #:nodoc:
|
|
3
|
+
module Associations #:nodoc:
|
|
4
|
+
# Represents an relational one-to-one association with an object in a
|
|
5
|
+
# separate collection or database.
|
|
6
|
+
class HasOneRelated < Proxy
|
|
7
|
+
|
|
8
|
+
delegate :nil?, :to => :target
|
|
9
|
+
|
|
10
|
+
# Builds a new Document and sets it as the association.
|
|
11
|
+
#
|
|
12
|
+
# Returns the newly created object.
|
|
13
|
+
def build(attributes = {})
|
|
14
|
+
@target = @klass.instantiate(attributes)
|
|
15
|
+
inverse = @target.associations.values.detect do |metadata|
|
|
16
|
+
metadata.options.klass == @parent.class
|
|
17
|
+
end
|
|
18
|
+
name = inverse.name
|
|
19
|
+
@target.send("#{name}=", @parent)
|
|
20
|
+
@target
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Builds a new Document and sets it as the association, then saves the
|
|
24
|
+
# newly created document.
|
|
25
|
+
#
|
|
26
|
+
# Returns the newly created object.
|
|
27
|
+
def create(attributes)
|
|
28
|
+
build(attributes); @target.save; @target
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Initializing a related association only requires looking up the objects
|
|
32
|
+
# by their ids.
|
|
33
|
+
#
|
|
34
|
+
# Options:
|
|
35
|
+
#
|
|
36
|
+
# document: The +Document+ that contains the relationship.
|
|
37
|
+
# options: The association +Options+.
|
|
38
|
+
def initialize(document, options, target = nil)
|
|
39
|
+
@parent, @klass = document, options.klass
|
|
40
|
+
@foreign_key = options.foreign_key
|
|
41
|
+
@target = target || @klass.first(:conditions => { @foreign_key => @parent.id })
|
|
42
|
+
extends(options)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class << self
|
|
46
|
+
# Preferred method for creating the new +RelatesToMany+ association.
|
|
47
|
+
#
|
|
48
|
+
# Options:
|
|
49
|
+
#
|
|
50
|
+
# document: The +Document+ that contains the relationship.
|
|
51
|
+
# options: The association +Options+.
|
|
52
|
+
def instantiate(document, options, target = nil)
|
|
53
|
+
new(document, options, target)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns the macro used to create the association.
|
|
57
|
+
def macro
|
|
58
|
+
:has_one_related
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Perform an update of the relationship of the parent and child. This
|
|
62
|
+
# will assimilate the child +Document+ into the parent's object graph.
|
|
63
|
+
#
|
|
64
|
+
# Options:
|
|
65
|
+
#
|
|
66
|
+
# related: The related object to update.
|
|
67
|
+
# document: The parent +Document+.
|
|
68
|
+
# options: The association +Options+
|
|
69
|
+
#
|
|
70
|
+
# Example:
|
|
71
|
+
#
|
|
72
|
+
# <tt>HasOneToRelated.update(game, person, options)</tt>
|
|
73
|
+
def update(target, document, options)
|
|
74
|
+
if target
|
|
75
|
+
name = document.class.to_s.underscore
|
|
76
|
+
target.send("#{name}=", document)
|
|
77
|
+
return instantiate(document, options, target)
|
|
78
|
+
end
|
|
79
|
+
target
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
# Create the new associations MetaData object, which holds the type of
|
|
17
|
+
# the association and its options, with convenience methods for getting
|
|
18
|
+
# that information.
|
|
19
|
+
#
|
|
20
|
+
# Options:
|
|
21
|
+
#
|
|
22
|
+
# association: The association type as a class instance.
|
|
23
|
+
# options: The association options
|
|
24
|
+
def initialize(association, options)
|
|
25
|
+
@association, @options = association, options
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Mongoid #:nodoc:
|
|
3
|
+
module Associations #:nodoc:
|
|
4
|
+
class Options #: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
|
+
@attributes = attributes
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Returns the extension if it exists, nil if not.
|
|
13
|
+
def extension
|
|
14
|
+
@attributes[:extend]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Returns true is the options have extensions.
|
|
18
|
+
def extension?
|
|
19
|
+
!extension.nil?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Return the foreign key based off the association name.
|
|
23
|
+
def foreign_key
|
|
24
|
+
key = @attributes[:foreign_key] || klass.name.to_s.foreign_key
|
|
25
|
+
key.to_s
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the name of the inverse_of association
|
|
29
|
+
def inverse_of
|
|
30
|
+
@attributes[:inverse_of]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Return a +Class+ for the options. If a class_name was provided, then the
|
|
34
|
+
# constantized class_name will be returned. If not, a constant based on the
|
|
35
|
+
# association name will be returned.
|
|
36
|
+
def klass
|
|
37
|
+
class_name = @attributes[:class_name]
|
|
38
|
+
class_name ? class_name.constantize : name.to_s.classify.constantize
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns the association name of the options.
|
|
42
|
+
def name
|
|
43
|
+
@attributes[:name].to_s
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns whether or not this association is polymorphic.
|
|
47
|
+
def polymorphic
|
|
48
|
+
@attributes[:polymorphic] == true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Used with has_many_related to save as array of ids.
|
|
52
|
+
def stored_as
|
|
53
|
+
@attributes[:stored_as]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
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 =~ /(^__|^nil\?$|^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
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require "mongoid/associations/proxy"
|
|
3
|
+
require "mongoid/associations/belongs_to_related"
|
|
4
|
+
require "mongoid/associations/embedded_in"
|
|
5
|
+
require "mongoid/associations/embeds_many"
|
|
6
|
+
require "mongoid/associations/embeds_one"
|
|
7
|
+
require "mongoid/associations/has_many_related"
|
|
8
|
+
require "mongoid/associations/has_one_related"
|
|
9
|
+
require "mongoid/associations/options"
|
|
10
|
+
require "mongoid/associations/meta_data"
|
|
11
|
+
|
|
12
|
+
module Mongoid # :nodoc:
|
|
13
|
+
module Associations #:nodoc:
|
|
14
|
+
extend ActiveSupport::Concern
|
|
15
|
+
included do
|
|
16
|
+
cattr_accessor :embedded
|
|
17
|
+
self.embedded = false
|
|
18
|
+
|
|
19
|
+
class_inheritable_accessor :associations
|
|
20
|
+
self.associations = {}
|
|
21
|
+
|
|
22
|
+
delegate :embedded, :embedded?, :to => "self.class"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module InstanceMethods
|
|
26
|
+
# Returns the associations for the +Document+.
|
|
27
|
+
def associations
|
|
28
|
+
self.class.associations
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# are we in an embeds_many?
|
|
32
|
+
def embedded_many?
|
|
33
|
+
embedded? and _parent.associations[association_name].association == EmbedsMany
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Update all the dirty child documents after an update.
|
|
37
|
+
def update_embedded(name)
|
|
38
|
+
association = send(name)
|
|
39
|
+
association.to_a.each { |doc| doc.save if doc.changed? || doc.new_record? } unless association.blank?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Update the one-to-one relational association for the name.
|
|
43
|
+
def update_association(name)
|
|
44
|
+
association = send(name)
|
|
45
|
+
association.save if new_record? && !association.nil?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Updates all the one-to-many relational associations for the name.
|
|
49
|
+
def update_associations(name)
|
|
50
|
+
send(name).each { |doc| doc.save } if new_record?
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
module ClassMethods
|
|
55
|
+
# Adds a relational association from the child Document to a Document in
|
|
56
|
+
# another database or collection.
|
|
57
|
+
#
|
|
58
|
+
# Options:
|
|
59
|
+
#
|
|
60
|
+
# name: A +Symbol+ that is the related class name.
|
|
61
|
+
#
|
|
62
|
+
# Example:
|
|
63
|
+
#
|
|
64
|
+
# class Game
|
|
65
|
+
# include Mongoid::Document
|
|
66
|
+
# belongs_to_related :person
|
|
67
|
+
# end
|
|
68
|
+
#
|
|
69
|
+
def belongs_to_related(name, options = {}, &block)
|
|
70
|
+
opts = optionize(name, options, fk(name, options), &block)
|
|
71
|
+
associate(Associations::BelongsToRelated, opts)
|
|
72
|
+
field(opts.foreign_key, :type => Mongoid.use_object_ids ? BSON::ObjectId : String)
|
|
73
|
+
index(opts.foreign_key) unless embedded?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Gets whether or not the document is embedded.
|
|
77
|
+
#
|
|
78
|
+
# Example:
|
|
79
|
+
#
|
|
80
|
+
# <tt>Person.embedded?</tt>
|
|
81
|
+
#
|
|
82
|
+
# Returns:
|
|
83
|
+
#
|
|
84
|
+
# <tt>true</tt> if embedded, <tt>false</tt> if not.
|
|
85
|
+
def embedded?
|
|
86
|
+
!!self.embedded
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Adds the association back to the parent document. This macro is
|
|
90
|
+
# necessary to set the references from the child back to the parent
|
|
91
|
+
# document. If a child does not define this association calling
|
|
92
|
+
# persistence methods on the child object will cause a save to fail.
|
|
93
|
+
#
|
|
94
|
+
# Options:
|
|
95
|
+
#
|
|
96
|
+
# name: A +Symbol+ that matches the name of the parent class.
|
|
97
|
+
#
|
|
98
|
+
# Example:
|
|
99
|
+
#
|
|
100
|
+
# class Person
|
|
101
|
+
# include Mongoid::Document
|
|
102
|
+
# embeds_many :addresses
|
|
103
|
+
# end
|
|
104
|
+
#
|
|
105
|
+
# class Address
|
|
106
|
+
# include Mongoid::Document
|
|
107
|
+
# embedded_in :person, :inverse_of => :addresses
|
|
108
|
+
# end
|
|
109
|
+
def embedded_in(name, options = {}, &block)
|
|
110
|
+
unless options.has_key?(:inverse_of)
|
|
111
|
+
raise Errors::InvalidOptions.new("Options for embedded_in association must include :inverse_of")
|
|
112
|
+
end
|
|
113
|
+
self.embedded = true
|
|
114
|
+
associate(Associations::EmbeddedIn, optionize(name, options, nil, &block))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
alias :belongs_to :embedded_in
|
|
118
|
+
|
|
119
|
+
# Adds the association from a parent document to its children. The name
|
|
120
|
+
# of the association needs to be a pluralized form of the child class
|
|
121
|
+
# name.
|
|
122
|
+
#
|
|
123
|
+
# Options:
|
|
124
|
+
#
|
|
125
|
+
# name: A +Symbol+ that is the plural child class name.
|
|
126
|
+
#
|
|
127
|
+
# Example:
|
|
128
|
+
#
|
|
129
|
+
# class Person
|
|
130
|
+
# include Mongoid::Document
|
|
131
|
+
# embeds_many :addresses
|
|
132
|
+
# end
|
|
133
|
+
#
|
|
134
|
+
# class Address
|
|
135
|
+
# include Mongoid::Document
|
|
136
|
+
# embedded_in :person, :inverse_of => :addresses
|
|
137
|
+
# end
|
|
138
|
+
def embeds_many(name, options = {}, &block)
|
|
139
|
+
associate(Associations::EmbedsMany, optionize(name, options, nil, &block))
|
|
140
|
+
unless name == :versions
|
|
141
|
+
after_update do |document|
|
|
142
|
+
document.update_embedded(name)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
alias :embed_many :embeds_many
|
|
148
|
+
alias :has_many :embeds_many
|
|
149
|
+
|
|
150
|
+
# Adds the association from a parent document to its child. The name
|
|
151
|
+
# of the association needs to be a singular form of the child class
|
|
152
|
+
# name.
|
|
153
|
+
#
|
|
154
|
+
# Options:
|
|
155
|
+
#
|
|
156
|
+
# name: A +Symbol+ that is the plural child class name.
|
|
157
|
+
#
|
|
158
|
+
# Example:
|
|
159
|
+
#
|
|
160
|
+
# class Person
|
|
161
|
+
# include Mongoid::Document
|
|
162
|
+
# embeds_one :name
|
|
163
|
+
# end
|
|
164
|
+
#
|
|
165
|
+
# class Name
|
|
166
|
+
# include Mongoid::Document
|
|
167
|
+
# embedded_in :person
|
|
168
|
+
# end
|
|
169
|
+
def embeds_one(name, options = {}, &block)
|
|
170
|
+
opts = optionize(name, options, nil, &block)
|
|
171
|
+
type = Associations::EmbedsOne
|
|
172
|
+
associate(type, opts)
|
|
173
|
+
add_builder(type, opts)
|
|
174
|
+
add_creator(type, opts)
|
|
175
|
+
after_update do |document|
|
|
176
|
+
document.update_embedded(name)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
alias :embed_one :embeds_one
|
|
181
|
+
alias :has_one :embeds_one
|
|
182
|
+
|
|
183
|
+
# Adds a relational association from the Document to many Documents in
|
|
184
|
+
# another database or collection.
|
|
185
|
+
#
|
|
186
|
+
# Options:
|
|
187
|
+
#
|
|
188
|
+
# name: A +Symbol+ that is the related class name pluralized.
|
|
189
|
+
#
|
|
190
|
+
# Example:
|
|
191
|
+
#
|
|
192
|
+
# class Person
|
|
193
|
+
# include Mongoid::Document
|
|
194
|
+
# has_many_related :posts
|
|
195
|
+
# end
|
|
196
|
+
#
|
|
197
|
+
def has_many_related(name, options = {}, &block)
|
|
198
|
+
associate(Associations::HasManyRelated, optionize(name, options, fk(self.name, options), &block))
|
|
199
|
+
before_save do |document|
|
|
200
|
+
document.update_associations(name)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Adds a relational association from the Document to one Document in
|
|
205
|
+
# another database or collection.
|
|
206
|
+
#
|
|
207
|
+
# Options:
|
|
208
|
+
#
|
|
209
|
+
# name: A +Symbol+ that is the related class name pluralized.
|
|
210
|
+
#
|
|
211
|
+
# Example:
|
|
212
|
+
#
|
|
213
|
+
# class Person
|
|
214
|
+
# include Mongoid::Document
|
|
215
|
+
# has_one_related :game
|
|
216
|
+
# end
|
|
217
|
+
def has_one_related(name, options = {}, &block)
|
|
218
|
+
associate(Associations::HasOneRelated, optionize(name, options, fk(self.name, options), &block))
|
|
219
|
+
before_save do |document|
|
|
220
|
+
document.update_association(name)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Returns the macro associated with the supplied association name. This
|
|
225
|
+
# will return has_one, has_many, belongs_to or nil.
|
|
226
|
+
#
|
|
227
|
+
# Options:
|
|
228
|
+
#
|
|
229
|
+
# name: The association name.
|
|
230
|
+
#
|
|
231
|
+
# Example:
|
|
232
|
+
#
|
|
233
|
+
# <tt>Person.reflect_on_association(:addresses)</tt>
|
|
234
|
+
def reflect_on_association(name)
|
|
235
|
+
association = associations[name.to_s]
|
|
236
|
+
association ? association.macro : nil
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
protected
|
|
240
|
+
# Adds the association to the associations hash with the type as the key,
|
|
241
|
+
# then adds the accessors for the association. The defined setters and
|
|
242
|
+
# getters for the associations will perform the necessary memoization.
|
|
243
|
+
#
|
|
244
|
+
# Example:
|
|
245
|
+
#
|
|
246
|
+
# <tt>Person.associate(EmbedsMany, { :name => :addresses })</tt>
|
|
247
|
+
def associate(type, options)
|
|
248
|
+
name = options.name.to_s
|
|
249
|
+
associations[name] = MetaData.new(type, options)
|
|
250
|
+
define_method(name) { memoized(name) { type.instantiate(self, options) } }
|
|
251
|
+
define_method("#{name}=") do |object|
|
|
252
|
+
unmemoize(name)
|
|
253
|
+
memoized(name) { type.update(object, self, options) }
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Adds a builder for a has_one association. This comes in the form of
|
|
258
|
+
# build_name(attributes)
|
|
259
|
+
def add_builder(type, options)
|
|
260
|
+
name = options.name.to_s
|
|
261
|
+
define_method("build_#{name}") do |attrs|
|
|
262
|
+
reset(name) { type.new(self, (attrs || {}).stringify_keys, options) }
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Adds a creator for a has_one association. This comes in the form of
|
|
267
|
+
# create_name(attributes)
|
|
268
|
+
def add_creator(type, options)
|
|
269
|
+
name = options.name.to_s
|
|
270
|
+
define_method("create_#{name}") do |attrs|
|
|
271
|
+
document = send("build_#{name}", attrs)
|
|
272
|
+
document.save; document
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# build the options given the params.
|
|
277
|
+
def optionize(name, options, foreign_key, &block)
|
|
278
|
+
Associations::Options.new(
|
|
279
|
+
options.merge(:name => name, :foreign_key => foreign_key, :extend => block)
|
|
280
|
+
)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Find the foreign key.
|
|
284
|
+
def fk(name, options)
|
|
285
|
+
options[:foreign_key] || name.to_s.foreign_key
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Build the association options.
|
|
289
|
+
def build_options(name, options, &block)
|
|
290
|
+
Associations::Options.new(
|
|
291
|
+
options.merge(
|
|
292
|
+
:name => name,
|
|
293
|
+
:foreign_key => foreign_key(name, options),
|
|
294
|
+
:extend => block
|
|
295
|
+
)
|
|
296
|
+
)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|