class ActiveRecord::Reflection::AssociationReflection
:nodoc:
Active Record class.
Holds all the metadata about an association as it was specified in the
def active_record_primary_key
def active_record_primary_key custom_primary_key = options[:primary_key] @active_record_primary_key ||= if custom_primary_key if custom_primary_key.is_a?(Array) custom_primary_key.map { |pk| pk.to_s.freeze }.freeze else custom_primary_key.to_s.freeze end elsif active_record.has_query_constraints? || options[:query_constraints] active_record.query_constraints_list elsif active_record.composite_primary_key? # If active_record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id primary_key = primary_key(active_record) primary_key.include?("id") ? "id" : primary_key.freeze else primary_key(active_record).freeze end end
def add_as_polymorphic_through(reflection, seed)
def add_as_polymorphic_through(reflection, seed) seed + [PolymorphicReflection.new(self, reflection)] end
def add_as_source(seed)
def add_as_source(seed) seed end
def add_as_through(seed)
def add_as_through(seed) seed + [self] end
def association_class; raise NotImplementedError; end
def association_class; raise NotImplementedError; end
def association_foreign_key
def association_foreign_key @association_foreign_key ||= -(options[:association_foreign_key]&.to_s || class_name.foreign_key) end
def association_primary_key(klass = nil)
def association_primary_key(klass = nil) primary_key(klass || self.klass) end
def association_scope_cache(klass, owner, &block)
def association_scope_cache(klass, owner, &block) key = self if polymorphic? key = [key, owner._read_attribute(@foreign_type)] end klass.cached_find_by_statement(key, &block) end
def automatic_inverse_of
def automatic_inverse_of if can_find_inverse_of_automatically?(self) inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym begin reflection = klass._reflect_on_association(inverse_name) rescue NameError => error raise unless error.name.to_s == class_name # Give up: we couldn't compute the klass type so we won't be able # to find any associations either. reflection = false end if valid_inverse_reflection?(reflection) inverse_name end end end
def belongs_to?; false; end
def belongs_to?; false; end
def can_find_inverse_of_automatically?(reflection, inverse_reflection = false)
Third, we must not have options such as :foreign_key
have has_many, has_one, belongs_to associations.
inverse_of option cannot be set to false. Second, we must
us from being able to guess the inverse automatically. First, the
Checks to see if the reflection doesn't have any options that prevent
def can_find_inverse_of_automatically?(reflection, inverse_reflection = false) reflection.options[:inverse_of] != false && !reflection.options[:through] && !reflection.options[:foreign_key] && scope_allows_automatic_inverse_of?(reflection, inverse_reflection) end
def check_eager_loadable!
def check_eager_loadable! return unless scope unless scope.arity == 0 raise ArgumentError, <<-MSG.squish The association scope '#{name}' is instance dependent (the scope block takes an argument). Eager loading instance dependent scopes is not supported. MSG end end
def check_validity!
def check_validity! check_validity_of_inverse! if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?) if (has_one? || collection?) && Array(active_record_primary_key).length != Array(foreign_key).length raise CompositePrimaryKeyMismatchError.new(self) elsif belongs_to? && Array(association_primary_key).length != Array(foreign_key).length raise CompositePrimaryKeyMismatchError.new(self) end end end
def clear_association_scope_cache # :nodoc:
SQL queries on associations.
This is for clearing cache on the reflection. Useful for tests that need to compare
def clear_association_scope_cache # :nodoc: klass.initialize_find_by_cache end
def collect_join_chain
A chain of reflections from this one back to the owner. For more see the explanation in
def collect_join_chain [self] end
def collection?
association. Returns +true+ if the +macro+ is either +has_many+ or
Returns whether or not this association reflection is for a collection
def collection? false end
def compute_class(name)
Active Record class.
Holds all the metadata about an association as it was specified in the
def compute_class(name) if polymorphic? raise ArgumentError, "Polymorphic associations do not support computing the class." end begin klass = active_record.send(:compute_type, name) rescue NameError => error if error.name.match?(/(?:\A|::)#{name}\z/) message = "Missing model class #{name} for the #{active_record}##{self.name} association." message += " You can specify a different model class with the :class_name option." unless options[:class_name] raise NameError.new(message, name) else raise end end unless klass < ActiveRecord::Base raise ArgumentError, "The #{name} model class for the #{active_record}##{self.name} association is not an ActiveRecord::Base subclass." end klass end
def derive_class_name
def derive_class_name class_name = name.to_s class_name = class_name.singularize if collection? class_name.camelize end
def derive_fk_query_constraints(foreign_key)
def derive_fk_query_constraints(foreign_key) primary_query_constraints = active_record.query_constraints_list owner_pk = active_record.primary_key if primary_query_constraints.size != 2 raise ArgumentError, <<~MSG.squish The query constraints list on the `#{active_record}` model has more than 2 attributes. Active Record is unable to derive the query constraints for the association. You need to explicitly define the query constraints for this association. MSG end if !primary_query_constraints.include?(owner_pk) raise ArgumentError, <<~MSG.squish The query constraints on the `#{active_record}` model does not include the primary key so Active Record is unable to derive the foreign key constraints for the association. You need to explicitly define the query constraints for this association. MSG end first_key, last_key = primary_query_constraints if first_key == owner_pk [foreign_key, last_key.to_s] elsif last_key == owner_pk [first_key.to_s, foreign_key] else raise ArgumentError, <<~MSG.squish Active Record couldn't correctly interpret the query constraints for the `#{active_record}` model. The query constraints on `#{active_record}` are `#{primary_query_constraints}` and the foreign key is `#{foreign_key}`. You need to explicitly set the query constraints for this association. MSG end end
def derive_foreign_key(infer_from_inverse_of: true)
def derive_foreign_key(infer_from_inverse_of: true) if belongs_to? "#{name}_id" elsif options[:as] "#{options[:as]}_id" elsif options[:inverse_of] && infer_from_inverse_of inverse_of.foreign_key(infer_from_inverse_of: false) else active_record.model_name.to_s.foreign_key end end
def derive_join_table
def derive_join_table ModelSchema.derive_join_table_name active_record.table_name, klass.table_name end
def extensions
def extensions Array(options[:extend]) end
def foreign_key(infer_from_inverse_of: true)
def foreign_key(infer_from_inverse_of: true) @foreign_key ||= if options[:query_constraints] options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze elsif options[:foreign_key] options[:foreign_key].to_s else derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of) if active_record.has_query_constraints? derived_fk = derive_fk_query_constraints(derived_fk) end derived_fk end end
def has_inverse?
def has_inverse? inverse_name end
def has_one?; false; end
def has_one?; false; end
def has_scope?
def has_scope? scope end
def initialize(name, scope, options, active_record)
def initialize(name, scope, options, active_record) super @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as] @foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic] @join_table = nil @foreign_key = nil @association_foreign_key = nil @association_primary_key = nil ensure_option_not_given_as_class!(:class_name) end
def inverse_name
If it cannot find a suitable inverse association name, it returns
Attempts to find the inverse association name automatically.
def inverse_name unless defined?(@inverse_name) @inverse_name = options.fetch(:inverse_of) { automatic_inverse_of } end @inverse_name end
def join_foreign_key
def join_foreign_key active_record_primary_key end
def join_id_for(owner) # :nodoc:
def join_id_for(owner) # :nodoc: Array(join_foreign_key).map { |key| owner._read_attribute(key) } end
def join_primary_key(klass = nil)
def join_primary_key(klass = nil) foreign_key end
def join_primary_type
def join_primary_type type end
def join_table
def join_table @join_table ||= -(options[:join_table]&.to_s || derive_join_table) end
def macro; raise NotImplementedError; end
Returns the macro type.
def macro; raise NotImplementedError; end
def nested?
def nested? false end
def polymorphic?
def polymorphic? options[:polymorphic] end
def polymorphic_inverse_of(associated_class)
def polymorphic_inverse_of(associated_class) if has_inverse? if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of]) inverse_relationship else raise InverseOfAssociationNotFoundError.new(self, associated_class) end end end
def polymorphic_name
def polymorphic_name active_record.polymorphic_name end
def scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
config.active_record.automatic_scope_inversing is set to
automatic inverse_of as long as
we would inverse from. Scopes on the reflection itself allow for
inverse_of, since the scope could exclude the owner record
Scopes on the potential inverse reflection prevent automatic
def scope_allows_automatic_inverse_of?(reflection, inverse_reflection) if inverse_reflection !reflection.scope else !reflection.scope || reflection.klass.automatic_scope_inversing end end
def source_reflection
def source_reflection self end
def through_reflection
def through_reflection nil end
def valid_inverse_reflection?(reflection)
make sure that the reflection's active_record name matches up
+automatic_inverse_of+ method is a valid reflection. We must
Checks if the inverse reflection that is returned from the
def valid_inverse_reflection?(reflection) reflection && reflection != self && foreign_key == reflection.foreign_key && klass <= reflection.active_record && can_find_inverse_of_automatically?(reflection, true) end
def validate?
* you use autosave; autosave: true
* you explicitly enable validation; validate: true
validate: false, validation will take place when:
Unless you explicitly disable validation with
the parent's validation.
Returns whether or not the association should be validated as part of
def validate? !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?) end