module Mongoid::Association::Accessors
This module contains all the behavior related to accessing associations through the getters and setters, and how to delegate to builders to create new ones.
Private Class Methods
Defines a builder method for an embeds_one association. This is defined as build_name.
@example
Person.define_builder!(association)
@param [ Association
] association The association for the association.
@return [ Class ] The class being set up.
@since 2.0.0.rc.1
# File lib/mongoid/association/accessors.rb, line 385 def self.define_builder!(association) name = association.name association.inverse_class.tap do |klass| klass.re_define_method("build_#{name}") do |*args| attributes, _options = parse_args(*args) document = Factory.build(association.relation_class, attributes) _building do child = send("#{name}=", document) child.run_callbacks(:build) child end end end end
Defines a creator method for an embeds_one association. This is defined as create_name. After the object is built it will immediately save.
@example
Person.define_creator!(association)
@param [ Association
] association The association for the association.
@return [ Class ] The class being set up.
@since 2.0.0.rc.1
# File lib/mongoid/association/accessors.rb, line 412 def self.define_creator!(association) name = association.name association.inverse_class.tap do |klass| klass.re_define_method("create_#{name}") do |*args| attributes, _options = parse_args(*args) document = Factory.build(association.klass, attributes) doc = _assigning do send("#{name}=", document) end doc.save save if new_record? && association.stores_foreign_key? doc end end end
Adds the existence check for associations.
@example Add the existence check.
Person.define_existence_check!(association)
@example Check if an association exists.
person = Person.new person.has_game? person.game?
@param [ Association
] association The association.
@return [ Class ] The model being set up.
@since 3.0.0
# File lib/mongoid/association/accessors.rb, line 265 def self.define_existence_check!(association) name = association.name association.inverse_class.tap do |klass| klass.module_eval <<-END, __FILE__, __LINE__ + 1 def #{name}? without_autobuild { !__send__(:#{name}).blank? } end alias :has_#{name}? :#{name}? END end end
Defines the getter for the association. Nothing too special here: just return the instance variable for the association if it exists or build the thing.
@example Set up the getter for the association.
Person.define_getter!(association)
@param [ Association
] association The association metadata for the association.
@return [ Class ] The class being set up.
@since 2.0.0.rc.1
# File lib/mongoid/association/accessors.rb, line 289 def self.define_getter!(association) name = association.name association.inverse_class.tap do |klass| klass.re_define_method(name) do |reload = false| value = get_relation(name, association, nil, reload) if value.nil? && association.autobuilding? && !without_autobuild? value = send("build_#{name}") end value end end end
Defines the getter for the ids of documents in the association. Should be specify only for referenced many associations.
@example Set up the ids getter for the association.
Person.define_ids_getter!(association)
@param [ Association
] association The association metadata for the association.
@return [ Class ] The class being set up.
# File lib/mongoid/association/accessors.rb, line 311 def self.define_ids_getter!(association) ids_method = "#{association.name.to_s.singularize}_ids" association.inverse_class.tap do |klass| klass.re_define_method(ids_method) do send(association.name).only(:_id).map(&:_id) end end end
Defines the setter method that allows you to set documents in this association by their ids. The defined setter, finds documents with given ids and invokes regular association setter with found documents. Ids setters should be defined only for referenced many associations.
@example Set up the id_setter for the association.
Person.define_ids_setter!(association) @param [ Association ] association The association for the association. @return [ Class ] The class being set up.
# File lib/mongoid/association/accessors.rb, line 365 def self.define_ids_setter!(association) ids_method = "#{association.name.to_s.singularize}_ids=" association.inverse_class.tap do |klass| klass.re_define_method(ids_method) do |ids| send(association.setter, association.relation_class.find(ids.reject(&:blank?))) end end end
Defines the setter for the association. This does a few things based on some conditions. If there is an existing association, a target substitution will take place, otherwise a new association will be created with the supplied target.
@example Set up the setter for the association.
Person.define_setter!(association)
@param [ Association
] association The association metadata for the association.
@return [ Class ] The class being set up.
@since 2.0.0.rc.1
# File lib/mongoid/association/accessors.rb, line 333 def self.define_setter!(association) name = association.name association.inverse_class.tap do |klass| klass.re_define_method("#{name}=") do |object| without_autobuild do if value = get_relation(name, association, object) if value.respond_to?(:substitute) set_relation(name, value.substitute(object.substitutable)) else value = __build__(name, value, association) set_relation(name, value.substitute(object.substitutable)) end else __build__(name, object.substitutable, association) end end end end end
Public Instance Methods
Builds the related document and creates the association unless the document is nil, then sets the association on this document.
@example Build the association.
person.__build__(:addresses, { :_id => 1 }, association)
@param [ String, Symbol ] name The name of the association. @param [ Hash, BSON::ObjectId
] object The id or attributes to use. @param [ Association
] association The association metadata. @param [ Hash ] selected_fields Fields
which were retrieved via only.
If selected_fields is specified, fields not listed in it will not be accessible in the built document.
@return [ Proxy
] The association.
@since 2.0.0.rc.1
# File lib/mongoid/association/accessors.rb, line 29 def __build__(name, object, association, selected_fields = nil) relation = create_relation(object, association, selected_fields) set_relation(name, relation) end
Create an association from an object and association metadata.
@example Create the association.
person.create_relation(document, association)
@param [ Document
, Array<Document> ] object The association target. @param [ Association
] association The association metadata. @param [ Hash ] selected_fields Fields
which were retrieved via only.
If selected_fields is specified, fields not listed in it will not be accessible in the created association document.
@return [ Proxy
] The association.
@since 2.0.0.rc.1
# File lib/mongoid/association/accessors.rb, line 48 def create_relation(object, association, selected_fields = nil) type = @attributes[association.inverse_type] target = association.build(self, object, type, selected_fields) target ? association.create_relation(self, target) : nil end
Resets the criteria inside the association proxy. Used by many-to-many associations to keep the underlying ids array in sync.
@example Reset the association criteria.
person.reset_relation_criteria(:preferences)
@param [ Symbol ] name The name of the association.
@since 3.0.14
# File lib/mongoid/association/accessors.rb, line 63 def reset_relation_criteria(name) if instance_variable_defined?("@_#{name}") send(name).reset_unloaded end end
Set the supplied association to an instance variable on the class with the provided name. Used as a helper just for code cleanliness.
@example Set the proxy on the document.
person.set(:addresses, addresses)
@param [ String, Symbol ] name The name of the association. @param [ Proxy
] relation The association to set.
@return [ Proxy
] The association.
@since 2.0.0.rc.1
# File lib/mongoid/association/accessors.rb, line 81 def set_relation(name, relation) instance_variable_set("@_#{name}", relation) end
Private Instance Methods
Returns a subset of __selected_fields attribute applicable to the (embedded) association with the given key, or nil if no projection is to be performed.
Also returns nil if exclusionary projection was requested but it does not exclude the field of the association.
For example, if __selected_fields is {‘a’ => 1, ‘b.c’ => 2, ‘b.c.f’ => 3}, and assoc_key is ‘b’, return value would be {‘c’ => 2, ‘c.f’ => 3}.
@param [ String ] assoc_key
@return [ Hash | nil ]
@api private
# File lib/mongoid/association/accessors.rb, line 135 def _mongoid_filter_selected_fields(assoc_key) return nil unless __selected_fields # If the list of fields was specified using #without instead of #only # and the provided list does not include the association, any of its # fields should be allowed. if __selected_fields.values.all? { |v| v == 0 } && __selected_fields.keys.none? { |k| k.split('.', 2).first == assoc_key } then return nil end projecting_assoc = false filtered = {} __selected_fields.each do |k, v| bits = k.split('.') # If we are asked to project an association, we need all of that # association's fields. However, we may be asked to project # an association *and* its fields in the same query. In this case # behavior differs according to server version: # # 4.2 and lower take the most recent projection specification, meaning # projecting foo followed by foo.bar effectively projects foo.bar and # projecting foo.bar followed by foo effectively projects foo. # To match this behavior we need to track when we are being asked # to project the association and when we are asked to project a field, # and if we are asked to project the association last we need to # remove any field projections. # # 4.4 (and presumably higher) do not allow projection to be on an # association and its field, so it doesn't matter what we do. Hence # we just need to handle the 4.2 and lower case correctly. if bits.first == assoc_key # Projecting the entire association OR some of its fields if bits.length > 1 # Projecting a field bits.shift filtered[bits.join('.')] = v projecting_assoc = false else # Projecting the entire association projecting_assoc = true end end end if projecting_assoc # The last projection was of the entire association; we may have # also been projecting fields, but discard the field projections # and return nil indicating we want the entire association. return nil end # Positional projection is specified as "foo.$". In this case the # document that the $ is referring to should be retrieved with all # fields. See https://docs.mongodb.com/manual/reference/operator/projection/positional/ # and https://jira.mongodb.org/browse/MONGOID-4769. if filtered.keys == %w($) filtered = nil end filtered end
Get the association. Extracted out from the getter method to avoid infinite recursion when overriding the getter.
@api private
@example Get the association.
document.get_relation(:name, association)
@param [ Symbol ] name The name of the association. @param [ Association
] association The association metadata. @param [ Object
] object The object used to build the association. @param [ true, false ] reload If the association is to be reloaded.
@return [ Proxy
] The association.
@since 3.0.16
# File lib/mongoid/association/accessors.rb, line 103 def get_relation(name, association, object, reload = false) if !reload && (value = ivar(name)) != false value else _building do _loading do if object && needs_no_database_query?(object, association) __build__(name, object, association) else selected_fields = _mongoid_filter_selected_fields(association.key) __build__(name, attributes[association.key], association, selected_fields) end end end end end
# File lib/mongoid/association/accessors.rb, line 201 def needs_no_database_query?(object, association) object.is_a?(Document) && !object.embedded? && object._id == attributes[association.key] end
Parse out the attributes and the options from the args passed to a build_ or create_ methods.
@example Parse the args.
doc.parse_args(:name => "Joe")
@param [ Array ] args The arguments.
@return [ Array<Hash> ] The attributes and options.
@since 2.3.4
# File lib/mongoid/association/accessors.rb, line 246 def parse_args(*args) [args.first || {}, args.size > 1 ? args[1] : {}] end
Yield to the block with autobuild functionality turned off.
@example Execute without autobuild.
document.without_autobuild do document.name end
@return [ Object
] The result of the yield.
@since 3.0.0
# File lib/mongoid/association/accessors.rb, line 228 def without_autobuild Threaded.begin_execution("without_autobuild") yield ensure Threaded.exit_execution("without_autobuild") end
Is the current code executing without autobuild functionality?
@example Is autobuild disabled?
document.without_autobuild?
@return [ true, false ] If autobuild is disabled.
@since 3.0.0
# File lib/mongoid/association/accessors.rb, line 214 def without_autobuild? Threaded.executing?(:without_autobuild) end