module Mongoid::Association::Relatable
This module provides behaviors shared between Association types.
@since 7.0
Constants
- PRIMARY_KEY_DEFAULT
The primary key default.
@return [ String ] The primary key field default.
@since 7.0
- SHARED_OPTIONS
The options shared between all association types.
@return [ Array<Symbol> ] The shared options.
@since 7.0
Attributes
The name of the association.
@return [ Symbol ] The name of the association.
@since 7.0
The options on this association.
@return [ Hash ] The options.
@since 7.0
Public Class Methods
Initialize the Association.
@param [ Class ] _class The class of the model who owns this association. @param [ Symbol ] name The name of the association. @param [ Hash ] opts The association options. @param [ Block ] block The optional block.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 56 def initialize(_class, name, opts = {}, &block) @owner_class = _class @name = name @options = opts @extension = nil @module_path = _class.name ? _class.name.split('::')[0..-2].join('::') : '' @module_path << '::' unless @module_path.empty? create_extension!(&block) validate! end
Public Instance Methods
Compare this association to another.
@return [ Object ] The object to compare to this association.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 74 def ==(other) relation_class_name == other.relation_class_name && inverse_class_name == other.inverse_class_name && name == other.name && options == other.options end
Whether trying to bind an object using this association should raise an error.
@param [ Document ] doc The document to be bound.
@return [ true, false ] Whether the document can be bound.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 111 def bindable?(doc); false; end
Get the counter cache column name.
@return [ String ] The counter cache column name.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 322 def counter_cache_column_name @counter_cache_column_name ||= (@options[:counter_cache].is_a?(String) || @options[:counter_cache].is_a?(Symbol)) ? @options[:counter_cache] : "#{inverse || inverse_class_name.demodulize.underscore.pluralize}_count" end
Create an association proxy object using the owner and target.
@param [ Document ] owner The document this association hangs off of. @param [ Document, Array<Document> ] target The target (parent) of the
association.
@return [ Proxy ]
@since 7.0
# File lib/mongoid/association/relatable.rb, line 304 def create_relation(owner, target) relation.new(owner, target, self) end
Whether the dependent method is destructive.
@return [ true, false ] If the dependent method is destructive.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 313 def destructive? @destructive ||= !!(dependent && (dependent == :delete_all || dependent == :destroy)) end
Get the extension.
@return [ Module ] The extension module, if one has been defined.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 333 def extension @extension ||= @options[:extend] end
Get the name of the method to check if the foreign key has changed.
@example Get the foreign key check method.
association.foreign_key_check
@return [ String ] The foreign key check.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 291 def foreign_key_check @foreign_key_check ||= "#{foreign_key}_changed?" if (stores_foreign_key? && foreign_key) end
The name of the foreign key setter method.
@return [ String ] The name of the foreign key setter.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 255 def foreign_key_setter # note: You can't check if this association stores foreign key # See HasOne and HasMany binding, they referenced foreign_key_setter @foreign_key_setter ||= "#{foreign_key}=" if foreign_key end
Get the callbacks for a given type.
@param [ Symbol ] callback_type The type of callback type.
@return [ Array<Proc, Symbol> ] A list of the callbacks, either method
names or Procs.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 89 def get_callbacks(callback_type) Array(options[callback_type]) end
Get the inverse name.
@return [ Symbol ] The inverse name.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 342 def inverse(other = nil) candidates = inverses(other) candidates.detect { |c| c } if candidates end
Get the inverse's association metadata.
@param [ Object ] other The other model class or model object to use when
determining inverses.
@return [ Association ] The inverse's association metadata.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 140 def inverse_association(other = nil) (other || relation_class).relations[inverse(other)] end
The class of the object owning this association.
@return [ String ] The owning objects' class.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 217 def inverse_class @owner_class end
The class name of the object owning this association.
@return [ String ] The owning objects' class name.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 208 def inverse_class_name @inverse_class_name ||= @owner_class.name end
The name of the inverse setter method.
@return [ String ] The name of the inverse setter.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 246 def inverse_setter(other = nil) @inverse_setter ||= "#{inverses(other).first}=" unless inverses(other).blank? end
Get the inverse type.
@return [ nil ] Default is nil for an association.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 149 def inverse_type; end
Gets the setter for the field that sets the type of document on a polymorphic association.
@example Get the inverse type setter.
association.inverse_type_setter
@return [ String ] The name of the setter.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 279 def inverse_type_setter @inverse_type_setter ||= inverse_type.__setter__ end
Get the inverse names.
@param [ Object ] other The other model class or model object to use when
determining inverses.
@return [ Array<Symbol> ] The list of inverse names.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 121 def inverses(other = nil) return [ inverse_of ] if inverse_of return [] if @options.key?(:inverse_of) && !inverse_of if polymorphic? polymorphic_inverses(other) else determine_inverses(other) end end
The foreign key field if this association stores a foreign key. Otherwise, the primary key.
@return [ Symbol, String ] The primary key.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 228 def key stores_foreign_key? ? foreign_key : primary_key end
The atomic path for this association.
@return [ Mongoid::Atomic::Paths::Root ] The atomic path object.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 266 def path(document) relation.path(document) end
The class of the association object(s).
This method returns the class instance corresponding to
relation_class_name
, resolved relative to the host document
class.
If the class does not exist, this method raises NameError. This can happen because the target class has not yet been defined. Note that polymorphic associations generally do not have a well defined target class because the target class can change from one object to another, and calling this method on a polymorphic association will generally fail with a NameError or produce misleading results (if a class does happen to be defined with the same name as the association name).
@return [ String ] The association objects' class.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 195 def relation_class @klass ||= begin cls_name = @options[:class_name] || ActiveSupport::Inflector.classify(name) resolve_name(inverse_class, cls_name) end end
- The class name, possibly unqualified or
-
prefixed, of the association
object(s).
This method returns the class name as it is used in the association definition. If :class_name option is given in the association, the exact value of that option is returned here. If :class_name option is not given, the name of the class is calculated from association name but is not resolved to the actual class.
The class name returned by this method may not correspond to a defined
class, either because the corresponding class has not been loaded yet, or
because the association references a non-existent class altogether. To
obtain the association class, use relation_class
method.
@note The return value of this method should not be used to determine
whether two associations have the same target class, because the return value is not always a fully qualified class name. To compare classes, retrieve the class instance of the association target using the +relation_class+ method.
@return [ String ] The association objects' class name.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 174 def relation_class_name @class_name ||= @options[:class_name] || ActiveSupport::Inflector.classify(name) end
The name of the setter on this object for assigning an associated object.
@return [ String ] The setter name.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 237 def setter @setter ||= "#{name}=" end
Get the type setter. @note Only relevant for polymorphic associations that take the :as option.
@return [ String ] The type setter method.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 99 def type_setter @type_setter ||= type.__setter__ end
Whether the associated object(s) should be validated.
@return [ true, false ] If the associated object(s)
should be validated.
@since 7.0
# File lib/mongoid/association/relatable.rb, line 353 def validate? @validate ||= if @options[:validate].nil? validation_default else !!@options[:validate] end end
Private Instance Methods
# File lib/mongoid/association/relatable.rb, line 445 def create_extension!(&block) if block extension_module_name = "#{@owner_class.to_s.demodulize}#{name.to_s.camelize}RelationExtension" silence_warnings do @owner_class.const_set(extension_module_name, Module.new(&block)) end @extension = "#{@owner_class}::#{extension_module_name}".constantize end end
# File lib/mongoid/association/relatable.rb, line 455 def default_inverse @default_inverse ||= klass.relations[inverse_klass.name.underscore] end
# File lib/mongoid/association/relatable.rb, line 379 def define_autosaver! if autosave? Association::Referenced::AutoSave.define_autosave!(self) end end
# File lib/mongoid/association/relatable.rb, line 385 def define_builder! Association::Builders.define_builder!(self) end
# File lib/mongoid/association/relatable.rb, line 413 def define_counter_cache_callbacks! if counter_cached? Association::Referenced::CounterCache.define_callbacks!(self) end end
# File lib/mongoid/association/relatable.rb, line 389 def define_creator! Association::Builders.define_creator!(self) end
# File lib/mongoid/association/relatable.rb, line 419 def define_dependency! if dependent Association::Depending.define_dependency!(self) end end
# File lib/mongoid/association/relatable.rb, line 401 def define_existence_check! Association::Accessors.define_existence_check!(self) end
# File lib/mongoid/association/relatable.rb, line 393 def define_getter! Association::Accessors.define_getter!(self) end
# File lib/mongoid/association/relatable.rb, line 405 def define_ids_getter! Association::Accessors.define_ids_getter!(self) end
# File lib/mongoid/association/relatable.rb, line 409 def define_ids_setter! Association::Accessors.define_ids_setter!(self) end
# File lib/mongoid/association/relatable.rb, line 397 def define_setter! Association::Accessors.define_setter!(self) end
# File lib/mongoid/association/relatable.rb, line 373 def define_touchable! if touchable? Touchable.define_touchable!(self) end end
Gets the model classes with inverse associations of this model. This is used to determine the classes on the other end of polymorphic associations with models.
# File lib/mongoid/association/relatable.rb, line 365 def inverse_association_classes Mongoid::Config.models.map { |m| inverse_association(m) }.compact.map(&:inverse_class) end
Returns an array of classes/modules forming the namespace hierarchy where symbols referenced in the provided class/module would be looked up by Ruby. For example, if mod is Foo::Bar, this method would return [Foo::Bar, Foo, Object].
# File lib/mongoid/association/relatable.rb, line 463 def namespace_hierarchy(mod) parent = Object hier = [parent] mod.name.split('::').each do |part| parent = parent.const_get(part) hier << parent end hier.reverse end
# File lib/mongoid/association/relatable.rb, line 439 def polymorph! if polymorphic? @owner_class.polymorphic = true end end
Resolves the given class/module name in the context of the specified module, as Ruby would when a constant is referenced in the source.
@note This method can swallow exceptions produced during class loading,
because it rescues NameError internally. Since this method attempts to load classes, failure during the loading process may also lead to there being incomplete class definitions.
# File lib/mongoid/association/relatable.rb, line 480 def resolve_name(mod, name) cls = exc = nil parts = name.to_s.split('::') if parts.first == '' parts.shift hierarchy = [Object] else hierarchy = namespace_hierarchy(mod) end hierarchy.each do |ns| begin parts.each do |part| # Simple const_get sometimes pulls names out of weird scopes, # perhaps confusing the receiver (ns in this case) with the # local scope. Walk the class hierarchy ourselves one node # at a time by specifying false as the second argument. ns = ns.const_get(part, false) end cls = ns break rescue NameError => e if exc.nil? exc = e end end end if cls.nil? # Raise the first exception, this is from the most specific namespace raise exc end cls end
# File lib/mongoid/association/relatable.rb, line 369 def setup_index! @owner_class.index(index_spec, background: true) if indexed? end
# File lib/mongoid/association/relatable.rb, line 425 def validate! @options.keys.each do |opt| unless self.class::VALID_OPTIONS.include?(opt) raise Errors::InvalidRelationOption.new(@owner_class, name, opt, self.class::VALID_OPTIONS) end end [name, "#{name}?".to_sym, "#{name}=".to_sym].each do |n| if Mongoid.destructive_fields.include?(n) raise Errors::InvalidRelation.new(@owner_class, n) end end end