123456789_123456789_123456789_123456789_123456789_

Class: ActiveRecord::Reflection::AssociationReflection

Do not use. This class is for internal use only.
Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Subclasses:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Instance Chain:
Inherits: ActiveRecord::Reflection::MacroReflection
Defined in: activerecord/lib/active_record/reflection.rb

Overview

Holds all the metadata about an association as it was specified in the Active Record class.

Constant Summary

Class Method Summary

Instance Attribute Summary

MacroReflection - Inherited

#active_record, #autosave=,
#name

Returns the name of the macro.

#options

Returns the hash of options used for the macro.

#scope, #plural_name

AbstractReflection - Inherited

#counter_must_be_updated_by_has_many?,
#has_active_cached_counter?

Returns whether this association has a counter cache and its column values were backfilled (and so it is used internally by methods like size/+any?+/etc).

#has_cached_counter?

Returns whether this association has a counter cache.

#inverse_updates_counter_cache?
#inverse_updates_counter_in_memory?, #strict_loading?, #through_reflection?

Instance Method Summary

MacroReflection - Inherited

#==

Returns true if self and other_aggregation have the same name attribute, active_record attribute, and other_aggregation has an options hash assigned to it.

#compute_class,
#klass

Returns the class for the macro.

#scope_for, #derive_class_name, #normalize_options, #_klass

AbstractReflection - Inherited

#alias_candidate, #association_route_for_join, #association_scope_cache,
#build_association

Returns a new, unsaved instance of the associated class.

#build_scope, #chain, #check_validity_of_inverse!,
#class_name

Returns the class name for the macro.

#constraints, #counter_cache_column, #inverse_of,
#inverse_which_updates_counter_cache

We need to avoid the following situation:

#join_scope, #join_scopes, #klass_join_scope,
#scopes

Returns a list of scopes that should be applied for this ::ActiveRecord::Reflection object when querying the database.

#strict_loading_violation_message, #table_name,
#actual_source_reflection

FIXME: this is a horrible name.

#ensure_option_not_given_as_class!,
#inferred_primary_key_definition

The Key form of a model's inferred primary key.

#primary_key, #primary_key_definition

Constructor Details

.new(name, scope, options, active_record) ⇒ AssociationReflection

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 588

def initialize(name, scope, options, active_record)
  super

  @validated = false
  @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_definition = nil
  @association_foreign_key_definition = nil
  @association_primary_key_definition = nil
  @active_record_primary_key_definition = nil
  @default_association_route = nil
  @default_association_route_key = :"active_record_default_association_route_#{object_id}"
  @extensions = options[:extend] ? Array(options[:extend]) : FROZEN_EMPTY_ARRAY
  @extensions = @extensions.dup.freeze unless @extensions.frozen?

  if options[:query_constraints]
    raise ConfigurationError, <<~MSG.squish
      Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is not allowed.
      To get the same behavior, use the `foreign_key` option instead.
    MSG
  end

  # If the foreign key is an array, set query constraints options and don't use the foreign key
  if options[:foreign_key].is_a?(Array)
    options[:query_constraints] = options.delete(:foreign_key)
  end

  @deprecated = !!options[:deprecated]

  ensure_option_not_given_as_class!(:class_name)
end

Instance Attribute Details

#belongs_to? ⇒ Boolean (readonly)

Returns true if self is a belongs_to reflection.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 824

def belongs_to?; false; end

#collection? ⇒ Boolean (readonly)

Returns whether or not this association reflection is for a collection association. Returns true if the #macro is either has_many or has_and_belongs_to_many, false otherwise.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 806

def collection?
  false
end

#deprecated? ⇒ Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 851

def deprecated?
  @deprecated
end

#extensions (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 582

attr_reader :type, :foreign_type, :extensions

#foreign_type (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 582

attr_reader :type, :foreign_type, :extensions

#has_inverse? ⇒ Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 784

def has_inverse?
  inverse_name
end

#has_one? ⇒ Boolean (readonly)

Returns true if self is a has_one reflection.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 827

def has_one?; false; end

#has_scope? ⇒ Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 780

def has_scope?
  scope
end

#nested? ⇒ Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 776

def nested?
  false
end

#parent_reflection (rw)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 583

attr_accessor :parent_reflection # Reflection

#polymorphic? ⇒ Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 831

def polymorphic?
  options[:polymorphic]
end

#type (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 582

attr_reader :type, :foreign_type, :extensions

#validate? ⇒ Boolean (readonly)

Returns whether or not the association should be validated as part of the parent's validation.

Unless you explicitly disable validation with validate: false, validation will take place when:

  • you explicitly enable validation; validate: true
  • you use autosave; autosave: true
  • the association is a has_many association
[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 819

def validate?
  !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
end

Instance Method Details

#active_record_primary_key

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 709

def active_record_primary_key
  active_record_primary_key_definition.name
end

#active_record_primary_key_definition

Same as #active_record_primary_key but returns an ::ActiveRecord::Key. Only an explicit :primary_key is memoized; derived keys follow the model's current schema.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 716

def active_record_primary_key_definition # :nodoc:
  if options[:primary_key]
    @active_record_primary_key_definition ||= ActiveRecord::Key.for(options[:primary_key])
  elsif active_record.has_query_constraints? || options[:query_constraints]
    ActiveRecord::Key.for(active_record.query_constraints_list)
  else
    inferred_primary_key_definition(active_record)
  end
end

#add_as_polymorphic_through(reflection, seed)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 843

def add_as_polymorphic_through(reflection, seed)
  seed + [PolymorphicReflection.new(self, reflection)]
end

#add_as_source(seed)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 839

def add_as_source(seed)
  seed
end

#add_as_through(seed)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 847

def add_as_through(seed)
  seed + [self]
end

#association_class

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 829

def association_class; raise NotImplementedError; end

#association_foreign_key

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 691

def association_foreign_key
  association_foreign_key_definition.name
end

#association_foreign_key_definition

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 696

def association_foreign_key_definition # :nodoc:
  @association_foreign_key_definition ||= ActiveRecord::Key.for(options[:association_foreign_key] || class_name.foreign_key)
end

#association_primary_key(klass = nil)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 700

def association_primary_key(klass = nil)
  association_primary_key_definition(klass).name
end

#association_primary_key_definition(klass = nil)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 705

def association_primary_key_definition(klass = nil) # :nodoc:
  primary_key_definition(klass || self.klass)
end

#association_route(origin_class: active_record, destination_class: nil)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 650

def association_route(origin_class: active_record, destination_class: nil)
  if origin_class.equal?(active_record) && (slot = @default_association_route) &&
      (destination_class.nil? || destination_class.equal?(slot[1])) && current_default_association_route?(slot)
    return slot[3]
  end

  destination_class ||= klass unless polymorphic?

  if origin_class.equal?(active_record) && destination_class.equal?(default_destination_class)
    return default_association_route
  end

  # Schema contexts are replaced on reload, so key by both endpoints to
  # avoid reusing routes with outdated keys.
  by_destination = association_route_cache.fetch_or_store(origin_class.schema_context) { AssociationRoute::Cache.new }
  by_destination.fetch_or_store(destination_class&.schema_context) do
    resolve_association_route(origin_class, destination_class)
  end
end

#association_route_cache (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 865

def association_route_cache
  AssociationRoute::Cache.for(self)
end

#association_route_chain(origin_class: active_record, destination_class: nil)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 642

def association_route_chain(origin_class: active_record, destination_class: nil)
  [association_route(origin_class: origin_class, destination_class: destination_class)]
end

#association_route_class (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 922

def association_route_class
  AssociationRoute::Reverse
end

#association_route_constraints(_reference_class, _target_class) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 941

def association_route_constraints(_reference_class, _target_class)
  Key::Mapping.empty
end

#association_route_target_key(_target_class) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 918

def association_route_target_key(_target_class)
  active_record_primary_key_definition
end

#automatic_inverse_of (private)

returns either nil or the inverse association name that it finds.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 957

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)
      if !reflection && active_record.automatically_invert_plural_associations
        plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
        reflection = klass._reflect_on_association(plural_inverse_name)
      end
    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)
      reflection.name
    end
  end
end

#build_association_route(reference_class:, target_class:, fixed_values: {}) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 926

def build_association_route(reference_class:, target_class:, fixed_values: {})
  reference = Key::Mapping.new(
    reference_key: foreign_key_definition,
    target_key: association_route_target_key(target_class)
  )
  constraints = association_route_constraints(reference_class, target_class)

  association_route_class.new(
    link: AssociationLink.for(
      reference: reference, constraints: constraints, fixed_values: fixed_values,
      reference_class: reference_class, target_class: target_class
    )
  )
end

#can_find_inverse_of_automatically?(reflection, inverse_reflection = false) ⇒ Boolean (private)

Checks to see if the reflection doesn't have any options that prevent us from being able to guess the inverse automatically. First, the inverse_of option cannot be set to false. Second, we must have has_many, has_one, belongs_to associations. Third, we must not have options such as :foreign_key which prevent us from correctly guessing the inverse association.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 999

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

#check_eager_loadable!

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 742

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

#check_validity!

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 726

def check_validity!
  return if @validated

  check_validity_of_inverse!

  if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
    if (has_one? || collection?) && active_record_primary_key_definition.length != foreign_key_definition.length
      raise CompositePrimaryKeyMismatchError.new(self)
    elsif belongs_to? && association_primary_key_definition.length != foreign_key_definition.length
      raise CompositePrimaryKeyMismatchError.new(self)
    end
  end

  @validated = true
end

#clear_association_scope_cache(klass = self.klass)

Pass the concrete target class for polymorphic associations.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 769

def clear_association_scope_cache(klass = self.klass) # :nodoc:
  AssociationRoute::Cache.clear(self)
  @default_association_route = nil unless frozen?
  ActiveSupport::Ractors[@default_association_route_key] = nil
  klass.initialize_find_by_cache
end

#collect_join_chain

A chain of reflections from this one back to the owner. For more see the explanation in ThroughReflection.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 764

def collect_join_chain
  [self]
end

#compute_class(name)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 558

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

#current_default_association_route?(slot) ⇒ Boolean (private)

Compares loaded contexts by identity: a context matching the slot's was loaded when the slot was filled, so this needn't load anything.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 901

def current_default_association_route?(slot)
  slot &&
    slot[0].equal?(active_record.current_schema_context) &&
    slot[2].equal?(slot[1]&.current_schema_context)
end

#default_association_route (private)

Slots contain [origin context, destination class, destination context, route]. A slot resolved before the reflection is frozen is shared until a schema context changes; slots resolved later are stored per Ractor.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 876

def default_association_route
  slot = @default_association_route
  return slot[3] if current_default_association_route?(slot)

  if frozen?
    slot = ActiveSupport::Ractors[@default_association_route_key]
    return slot[3] if current_default_association_route?(slot)
  end

  destination_class = default_destination_class
  origin_context = active_record.schema_context
  destination_context = destination_class&.schema_context
  route = resolve_association_route(active_record, destination_class)
  slot = [origin_context, destination_class, destination_context, route].freeze

  if frozen?
    ActiveSupport::Ractors[@default_association_route_key] = slot
  else
    @default_association_route = slot
  end
  route
end

#default_destination_class (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 869

def default_destination_class
  polymorphic? ? nil : klass
end

#derive_class_name (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 1020

def derive_class_name
  class_name = name.to_s
  class_name = class_name.singularize if collection?
  class_name.camelize
end

#derive_fk_query_constraints(foreign_key) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 1038

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

  return foreign_key if primary_query_constraints.include?(foreign_key)

  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

#derive_foreign_key(infer_from_inverse_of: true) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 1026

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

#derive_join_table (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 1078

def derive_join_table
  ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
end

#foreign_key(infer_from_inverse_of: true)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 670

def foreign_key(infer_from_inverse_of: true)
  foreign_key_definition(infer_from_inverse_of: infer_from_inverse_of).name
end

#foreign_key_definition(infer_from_inverse_of: true)

Same as #foreign_key but returns an ::ActiveRecord::Key.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 675

def foreign_key_definition(infer_from_inverse_of: true) # :nodoc:
  @foreign_key_definition ||= if options[:foreign_key]
    ActiveRecord::Key.for(options[:foreign_key])
  elsif options[:query_constraints]
    ActiveRecord::Key.for(options[:query_constraints])
  else
    derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)

    if !derived_fk.is_a?(Array) && active_record.has_query_constraints?
      derived_fk = derive_fk_query_constraints(derived_fk)
    end

    ActiveRecord::Key.for(derived_fk)
  end
end

#freeze

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 621

def freeze
  return self if frozen?

  check_validity!

  unless polymorphic?
    klass
    association_primary_key_definition if belongs_to?
    inverse_of
    inverse_which_updates_counter_cache
  end

  active_record_primary_key_definition
  association_foreign_key_definition
  counter_cache_column
  foreign_key_definition
  @scope = ActiveSupport::Ractors.try_shareable_proc(@scope) if @scope

  super
end

#initialize_copy(other) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 907

def initialize_copy(other)
  super
  @default_association_route = nil
  @default_association_route_key = :"active_record_default_association_route_#{object_id}"
end

#inverse_name (private)

Attempts to find the inverse association name automatically. If it cannot find a suitable inverse association name, it returns nil.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 948

def inverse_name
  unless defined?(@inverse_name)
    @inverse_name = options.fetch(:inverse_of) { automatic_inverse_of }
  end

  @inverse_name
end

#join_table

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 646

def join_table
  @join_table ||= -(options[:join_table]&.to_s || derive_join_table)
end

#macro

Returns the macro type.

has_many :clients returns :has_many

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 801

def macro; raise NotImplementedError; end

#polymorphic_inverse_of(associated_class)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 788

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

#polymorphic_name

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 835

def polymorphic_name
  active_record.polymorphic_name
end

#prepare_default_association_route

Resolves the default route ahead of the reflection being made shareable, so that every Ractor can read it from the frozen slot.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 857

def prepare_default_association_route # :nodoc:
  return unless @klass || polymorphic?
  return unless active_record.send(:schema_loaded?) && (polymorphic? || klass.send(:schema_loaded?))

  default_association_route
end

#resolve_association_route(origin_class, destination_class) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 913

def resolve_association_route(origin_class, destination_class)
  fixed_values = type ? { type => -origin_class.polymorphic_name } : {}
  build_association_route(reference_class: destination_class, target_class: origin_class, fixed_values: fixed_values)
end

#scope_allows_automatic_inverse_of?(reflection, inverse_reflection) ⇒ Boolean (private)

Scopes on the potential inverse reflection prevent automatic inverse_of, since the scope could exclude the owner record we would inverse from. Scopes on the reflection itself allow for automatic inverse_of as long as config.active_record.automatic_scope_inversing is set to {true} (the default for new applications).

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 1012

def scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
  if inverse_reflection
    !reflection.scope
  else
    !reflection.scope || reflection.klass.automatic_scope_inversing
  end
end

#source_reflection

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 758

def source_reflection
  self
end

#through_reflection

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 754

def through_reflection
  nil
end

#valid_inverse_reflection?(reflection) ⇒ Boolean (private)

Checks if the inverse reflection that is returned from the #automatic_inverse_of method is a valid reflection. We must make sure that the reflection's active_record name matches up with the current reflection's klass name.

[ GitHub ]

  
# File 'activerecord/lib/active_record/reflection.rb', line 985

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