123456789_123456789_123456789_123456789_123456789_

Module: Mongoid::Association::Accessors

Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Included In:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, ActiveSupport::Concern
Defined in: lib/mongoid/association/accessors.rb

Overview

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.

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Class Method Details

.define_builder!(association) ⇒ Class (private)

Defines a builder method for an embeds_one association. This is defined as #build_name.

Examples:

Person.define_builder!(association)

Parameters:

Returns:

  • (Class)

    The class being set up.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 394

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

.define_creator!(association) ⇒ Class (private)

Defines a creator method for an embeds_one association. This is defined as #create_name. After the object is built it will immediately save.

Examples:

Person.define_creator!(association)

Parameters:

Returns:

  • (Class)

    The class being set up.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 419

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

.define_existence_check!(association) ⇒ Class (private)

Adds the existence check for associations.

Examples:

Add the existence check.

Person.define_existence_check!(association)

Check if an association exists.

person = Person.new
person.has_game?
person.game?

Parameters:

Returns:

  • (Class)

    The model being set up.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 280

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

.define_getter!(association) ⇒ Class (private)

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.

Examples:

::Set up the getter for the association.

Person.define_getter!(association)

Parameters:

Returns:

  • (Class)

    The class being set up.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 302

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

.define_ids_getter!(association) ⇒ Class (private)

Defines the getter for the ids of documents in the association. Should be specify only for referenced many associations.

Examples:

::Set up the ids getter for the association.

Person.define_ids_getter!(association)

Parameters:

Returns:

  • (Class)

    The class being set up.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 324

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).pluck(:_id)
    end
  end
end

.define_ids_setter!(association) (private)

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.

@param [ Mongoid::Association::Relatable ] association The association for the association.

@return [ Class ] The class being set up.

Examples:

::Set up the id_setter for the association.

Person.define_ids_setter!(association)
[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 375

def self.define_ids_setter!(association)
  ids_method = "#{association.name.to_s.singularize}_ids="
  association.inverse_class.aliased_associations[ids_method.chop] = association.name.to_s
  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

.define_setter!(association) ⇒ Class (private)

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.

Examples:

::Set up the setter for the association.

Person.define_setter!(association)

Parameters:

Returns:

  • (Class)

    The class being set up.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 344

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)
            value = __build__(name, value, association) 
          end

          set_relation(name, value.substitute(object.substitutable))
        else
          __build__(name, object.substitutable, association)
        end
      end
    end
  end
end

Instance Attribute Details

#without_autobuild?true | false (readonly, private)

Is the current code executing without autobuild functionality?

Examples:

Is autobuild disabled?

document.without_autobuild?

Returns:

  • (true | false)

    If autobuild is disabled.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 235

def without_autobuild?
  Threaded.executing?(:without_autobuild)
end

Instance Method Details

#__build__(name, object, association, selected_fields = nil) ⇒ Proxy

Builds the related document and creates the association unless the document is nil, then sets the association on this document.

Examples:

Build the association.

person.__build__(:addresses, { :_id => 1 }, association)

Parameters:

Returns:

  • (Proxy)

    The association.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 27

def __build__(name, object, association, selected_fields = nil)
  relation = create_relation(object, association, selected_fields)
  set_relation(name, relation)
end

#_mongoid_filter_selected_fields(assoc_key) ⇒ Hash | nil (private)

This method is for internal use only.

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 => 1, ‘b.c’ => 2, ‘b.c.f’ => 3, and assoc_key is ‘b’, return value would be => 2, ‘c.f’ => 3.

Parameters:

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 158

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://www.mongodb.com/docs/manual/reference/operator/projection/positional/
  # and https://jira.mongodb.org/browse/MONGOID-4769.
  if filtered.keys == %w($)
    filtered = nil
  end

  filtered
end

#create_relation(object, association, selected_fields = nil) ⇒ Proxy

Create an association from an object and association metadata.

Examples:

Create the association.

person.create_relation(document, association)

Parameters:

Returns:

  • (Proxy)

    The association.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 44

def create_relation(object, association, selected_fields = nil)
  key = @attributes[association.inverse_type]
  type = key ? association.resolver.model_for(key) : nil
  target = if t = association.build(self, object, type, selected_fields)
    association.create_relation(self, t)
  else
    nil
  end

  # Only need to do this on embedded associations. The pending callbacks
  # are only added when materializing the documents, which only happens
  # on embedded associations. There is no call to the database in the
  # construction of a referenced association.
  if association.embedded?
    Array(target).each do |doc|
      doc.try(:run_pending_callbacks)
    end
  end

  target
end

#get_relation(name, association, object, reload = false) ⇒ Proxy (private)

This method is for internal use only.

Get the association. Extracted out from the getter method to avoid infinite recursion when overriding the getter.

Examples:

Get the association.

document.get_relation(:name, association)

Parameters:

  • name (Symbol)

    The name of the association.

  • association (Mongoid::Association::Relatable)

    The association metadata.

  • object (Object)

    The object used to build the association.

  • reload (true | false) (defaults to: false)

    If the association is to be reloaded.

Returns:

  • (Proxy)

    The association.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 109

def get_relation(name, association, object, reload = false)
  field_name = database_field_name(name)

  # As per the comments under MONGOID-5034, I've decided to only raise on
  # embedded associations for a missing attribute. Rails does not raise
  # for a missing attribute on referenced associations.
  # We also don't want to raise if we're retrieving an association within
  # the codebase. This is often done when retrieving the inverse association
  # during binding or when cascading callbacks. Whenever we retrieve
  # associations within the codebase, we use without_autobuild.
  if !without_autobuild? && association.embedded? && attribute_missing?(field_name)
    # We always allow accessing the parent document of an embedded one.
    try_get_parent = association.is_a?(
                       Mongoid::Association::Embedded::EmbeddedIn
                     ) && field_name == association.key
    raise Mongoid::Errors::AttributeNotLoaded.new(self.class, field_name) unless try_get_parent
  end

  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

#needs_no_database_query?(object, association) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 224

def needs_no_database_query?(object, association)
  object.is_a?(Document) && !object.embedded? &&
      object._id == attributes[association.key]
end

#parse_args(*args) ⇒ Array<Hash> (private)

Parse out the attributes and the options from the args passed to a build_ or create_ methods.

Examples:

Parse the args.

doc.parse_args(:name => "Joe")

Parameters:

  • *args (Hash...)

    The arguments.

Returns:

  • (Array<Hash>)

    The attributes and options.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 263

def parse_args(*args)
  [args.first || {}, args.size > 1 ? args[1] : {}]
end

#reset_relation_criteria(name)

Resets the criteria inside the association proxy. Used by many-to-many associations to keep the underlying ids array in sync.

Examples:

Reset the association criteria.

person.reset_relation_criteria(:preferences)

Parameters:

  • name (Symbol)

    The name of the association.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 73

def reset_relation_criteria(name)
  if instance_variable_defined?("@_#{name}")
    send(name).reset_unloaded
  end
end

#set_relation(name, relation) ⇒ Proxy

::Set the supplied association to an instance variable on the class with the provided name. Used as a helper just for code cleanliness.

Examples:

::Set the proxy on the document.

person.set(:addresses, addresses)

Parameters:

  • name (String | Symbol)

    The name of the association.

  • relation (Proxy)

    The association to set.

Returns:

  • (Proxy)

    The association.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 89

def set_relation(name, relation)
  instance_variable_set("@_#{name}", relation)
end

#without_autobuildObject (readonly, private)

Yield to the block with autobuild functionality turned off.

Examples:

Execute without autobuild.

document.without_autobuild do
  document.name
end

Returns:

  • (Object)

    The result of the yield.

[ GitHub ]

  
# File 'lib/mongoid/association/accessors.rb', line 247

def without_autobuild
  Threaded.begin_execution("without_autobuild")
  yield
ensure
  Threaded.exit_execution("without_autobuild")
end