# frozen_string_literal: true # rubocop:todo all require 'mongoid/association/referenced/belongs_to/binding' require 'mongoid/association/referenced/belongs_to/buildable' require 'mongoid/association/referenced/belongs_to/proxy' require 'mongoid/association/referenced/belongs_to/eager' module Mongoid module Association module Referenced # The BelongsTo type association. class BelongsTo include Relatable include Buildable # The options available for this type of association, in addition to the # common ones. # # @return [ Array ] The extra valid options. ASSOCIATION_OPTIONS = [ :autobuild, :autosave, :counter_cache, :dependent, :foreign_key, :index, :polymorphic, :primary_key, :touch, :optional, :required, :scope, ].freeze # The complete list of valid options for this association, including # the shared ones. # # @return [ Array ] The valid options. VALID_OPTIONS = (ASSOCIATION_OPTIONS + SHARED_OPTIONS).freeze # The type of the field holding the foreign key. # # @return [ Object ] FOREIGN_KEY_FIELD_TYPE = Object # The default foreign key suffix. # # @return [ String ] '_id' FOREIGN_KEY_SUFFIX = '_id'.freeze # The list of association complements. # # @return [ Array ] The association complements. def relation_complements @relation_complements ||= [ HasMany, HasOne ].freeze end # Setup the instance methods, fields, etc. on the association owning class. # # @return [ self ] def setup! setup_instance_methods! @owner_class.aliased_fields[name.to_s] = foreign_key self end # Does this association type store the foreign key? # # @return [ true ] Always true. def stores_foreign_key?; true; end # Is this association type embedded? # # @return [ false ] Always false. def embedded?; false; end # The default for validation the association object. # # @return [ false ] Always false. def validation_default; false; end # Get the foreign key field for saving the association reference. # # @return [ String ] The foreign key field for saving the association reference. def foreign_key @foreign_key ||= @options[:foreign_key] ? @options[:foreign_key].to_s : default_foreign_key_field end # Get the association proxy class for this association type. # # @return [ Association::BelongsTo::Proxy ] The proxy class. def relation Proxy end # Is this association polymorphic? # # @return [ true | false ] Whether this association is polymorphic. def polymorphic? @polymorphic ||= !!@options[:polymorphic] end # Returns the object responsible for converting polymorphic type references into # class objects, and vice versa. This is obtained via the `:polymorphic` option # that was given when the association was defined. # # See Mongoid::ModelResolver.resolver for how the `:polymorphic` option is # interpreted here. # # @raise KeyError if no such resolver has been registered under the given # identifier. # # @return [ nil | Mongoid::ModelResolver ] the resolver to use def resolver @resolver ||= Mongoid::ModelResolver.resolver(@options[:polymorphic]) end # The name of the field used to store the type of polymorphic association. # # @return [ String ] The field used to store the type of polymorphic association. def inverse_type (@inverse_type ||= "#{name}_type") if polymorphic? end # The nested builder object. # # @param [ Hash ] attributes The attributes to use to build the association object. # @param [ Hash ] options The options for the association. # # @return [ Association::Nested::One ] The Nested Builder object. def nested_builder(attributes, options) Nested::One.new(self, attributes, options) end # Get the path calculator for the supplied document. # # @example Get the path calculator. # association.path(document) # # @param [ Document ] document The document to calculate on. # # @return [ Root ] The root atomic path calculator. def path(document) Mongoid::Atomic::Paths::Root.new(document) end # Get the scope to be applied when querying the association. # # @return [ Proc | Symbol | nil ] The association scope, if any. def scope @options[:scope] end private def setup_instance_methods! define_getter! define_setter! define_existence_check! define_builder! define_creator! define_autosaver! define_counter_cache_callbacks! polymorph! define_dependency! create_foreign_key_field! setup_index! define_touchable! @owner_class.validates_associated(name) if validate? || require_association? @owner_class.validates(name, presence: true) if require_association? end def index_spec if polymorphic? { key => 1, inverse_type => 1 } else { key => 1 } end end def default_primary_key PRIMARY_KEY_DEFAULT end def default_foreign_key_field @default_foreign_key_field ||= "#{name}#{FOREIGN_KEY_SUFFIX}" end def polymorph! if polymorphic? @owner_class.polymorphic = true @owner_class.field(inverse_type, type: String) end end def polymorphic_inverses(other = nil) if other matches = other.relations.values.select do |rel| relation_complements.include?(rel.class) && rel.as == name && rel.relation_class_name == inverse_class_name end matches.collect { |m| m.name } end end def determine_inverses(other) matches = (other || relation_class).relations.values.select do |rel| relation_complements.include?(rel.class) && rel.relation_class_name == inverse_class_name end if matches.size > 1 raise Errors::AmbiguousRelationship.new(relation_class, @owner_class, name, matches) end matches.collect { |m| m.name } end # If set to true, then the associated object will be validated when this object is saved def require_association? required = @options[:required] if @options.key?(:required) required = !@options[:optional] if @options.key?(:optional) && required.nil? required.nil? ? Mongoid.belongs_to_required_by_default : required end def create_foreign_key_field! @owner_class.field( foreign_key, type: FOREIGN_KEY_FIELD_TYPE, identity: true, overwrite: true, association: self, default: nil ) end end end end end