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.
Files changed (104) hide show
  1. data/MIT_LICENSE +20 -0
  2. data/README.rdoc +49 -0
  3. data/lib/mongoid/associations/belongs_to_related.rb +58 -0
  4. data/lib/mongoid/associations/embedded_in.rb +72 -0
  5. data/lib/mongoid/associations/embeds_many.rb +254 -0
  6. data/lib/mongoid/associations/embeds_one.rb +96 -0
  7. data/lib/mongoid/associations/has_many_related.rb +181 -0
  8. data/lib/mongoid/associations/has_one_related.rb +85 -0
  9. data/lib/mongoid/associations/meta_data.rb +29 -0
  10. data/lib/mongoid/associations/options.rb +57 -0
  11. data/lib/mongoid/associations/proxy.rb +24 -0
  12. data/lib/mongoid/associations.rb +300 -0
  13. data/lib/mongoid/attributes.rb +204 -0
  14. data/lib/mongoid/callbacks.rb +23 -0
  15. data/lib/mongoid/collection.rb +120 -0
  16. data/lib/mongoid/collections/cyclic_iterator.rb +34 -0
  17. data/lib/mongoid/collections/master.rb +29 -0
  18. data/lib/mongoid/collections/operations.rb +41 -0
  19. data/lib/mongoid/collections/slaves.rb +45 -0
  20. data/lib/mongoid/collections.rb +41 -0
  21. data/lib/mongoid/components.rb +27 -0
  22. data/lib/mongoid/concern.rb +31 -0
  23. data/lib/mongoid/config.rb +191 -0
  24. data/lib/mongoid/contexts/enumerable.rb +151 -0
  25. data/lib/mongoid/contexts/ids.rb +25 -0
  26. data/lib/mongoid/contexts/mongo.rb +285 -0
  27. data/lib/mongoid/contexts/paging.rb +50 -0
  28. data/lib/mongoid/contexts.rb +25 -0
  29. data/lib/mongoid/criteria.rb +249 -0
  30. data/lib/mongoid/criterion/complex.rb +21 -0
  31. data/lib/mongoid/criterion/exclusion.rb +65 -0
  32. data/lib/mongoid/criterion/inclusion.rb +110 -0
  33. data/lib/mongoid/criterion/optional.rb +136 -0
  34. data/lib/mongoid/cursor.rb +81 -0
  35. data/lib/mongoid/deprecation.rb +22 -0
  36. data/lib/mongoid/dirty.rb +253 -0
  37. data/lib/mongoid/document.rb +311 -0
  38. data/lib/mongoid/errors.rb +108 -0
  39. data/lib/mongoid/extensions/array/accessors.rb +17 -0
  40. data/lib/mongoid/extensions/array/aliasing.rb +4 -0
  41. data/lib/mongoid/extensions/array/assimilation.rb +26 -0
  42. data/lib/mongoid/extensions/array/conversions.rb +29 -0
  43. data/lib/mongoid/extensions/array/parentization.rb +13 -0
  44. data/lib/mongoid/extensions/big_decimal/conversions.rb +19 -0
  45. data/lib/mongoid/extensions/binary/conversions.rb +17 -0
  46. data/lib/mongoid/extensions/boolean/conversions.rb +22 -0
  47. data/lib/mongoid/extensions/date/conversions.rb +24 -0
  48. data/lib/mongoid/extensions/datetime/conversions.rb +12 -0
  49. data/lib/mongoid/extensions/float/conversions.rb +20 -0
  50. data/lib/mongoid/extensions/hash/accessors.rb +38 -0
  51. data/lib/mongoid/extensions/hash/assimilation.rb +39 -0
  52. data/lib/mongoid/extensions/hash/conversions.rb +45 -0
  53. data/lib/mongoid/extensions/hash/criteria_helpers.rb +21 -0
  54. data/lib/mongoid/extensions/hash/scoping.rb +12 -0
  55. data/lib/mongoid/extensions/integer/conversions.rb +20 -0
  56. data/lib/mongoid/extensions/nil/assimilation.rb +17 -0
  57. data/lib/mongoid/extensions/object/conversions.rb +33 -0
  58. data/lib/mongoid/extensions/objectid/conversions.rb +15 -0
  59. data/lib/mongoid/extensions/proc/scoping.rb +12 -0
  60. data/lib/mongoid/extensions/string/conversions.rb +15 -0
  61. data/lib/mongoid/extensions/string/inflections.rb +97 -0
  62. data/lib/mongoid/extensions/symbol/inflections.rb +36 -0
  63. data/lib/mongoid/extensions/time_conversions.rb +35 -0
  64. data/lib/mongoid/extensions.rb +101 -0
  65. data/lib/mongoid/extras.rb +61 -0
  66. data/lib/mongoid/factory.rb +20 -0
  67. data/lib/mongoid/field.rb +59 -0
  68. data/lib/mongoid/fields.rb +65 -0
  69. data/lib/mongoid/finders.rb +144 -0
  70. data/lib/mongoid/identity.rb +39 -0
  71. data/lib/mongoid/indexes.rb +30 -0
  72. data/lib/mongoid/javascript/functions.yml +37 -0
  73. data/lib/mongoid/javascript.rb +21 -0
  74. data/lib/mongoid/matchers/all.rb +11 -0
  75. data/lib/mongoid/matchers/default.rb +26 -0
  76. data/lib/mongoid/matchers/exists.rb +13 -0
  77. data/lib/mongoid/matchers/gt.rb +11 -0
  78. data/lib/mongoid/matchers/gte.rb +11 -0
  79. data/lib/mongoid/matchers/in.rb +11 -0
  80. data/lib/mongoid/matchers/lt.rb +11 -0
  81. data/lib/mongoid/matchers/lte.rb +11 -0
  82. data/lib/mongoid/matchers/ne.rb +11 -0
  83. data/lib/mongoid/matchers/nin.rb +11 -0
  84. data/lib/mongoid/matchers/size.rb +11 -0
  85. data/lib/mongoid/matchers.rb +36 -0
  86. data/lib/mongoid/memoization.rb +33 -0
  87. data/lib/mongoid/named_scope.rb +37 -0
  88. data/lib/mongoid/observable.rb +30 -0
  89. data/lib/mongoid/paths.rb +62 -0
  90. data/lib/mongoid/persistence/command.rb +39 -0
  91. data/lib/mongoid/persistence/insert.rb +50 -0
  92. data/lib/mongoid/persistence/insert_embedded.rb +38 -0
  93. data/lib/mongoid/persistence/remove.rb +39 -0
  94. data/lib/mongoid/persistence/remove_all.rb +37 -0
  95. data/lib/mongoid/persistence/remove_embedded.rb +50 -0
  96. data/lib/mongoid/persistence/update.rb +63 -0
  97. data/lib/mongoid/persistence.rb +222 -0
  98. data/lib/mongoid/scope.rb +75 -0
  99. data/lib/mongoid/state.rb +39 -0
  100. data/lib/mongoid/timestamps.rb +27 -0
  101. data/lib/mongoid/version.rb +4 -0
  102. data/lib/mongoid/versioning.rb +27 -0
  103. data/lib/mongoid.rb +122 -0
  104. 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