method

derive_fk_query_constraints

Importance_0
v7.1.3.4 - Show latest stable - 0 notes - Class: ActiveRecord::Reflection::AssociationReflection
  • 1.0.0
  • 1.1.6
  • 1.2.6
  • 2.0.3
  • 2.1.0
  • 2.2.1
  • 2.3.8
  • 3.0.0
  • 3.0.9
  • 3.1.0
  • 3.2.1
  • 3.2.8
  • 3.2.13
  • 4.0.2
  • 4.1.8
  • 4.2.1
  • 4.2.7
  • 4.2.9
  • 5.0.0.1
  • 5.1.7
  • 5.2.3
  • 6.0.0
  • 6.1.3.1
  • 6.1.7.7
  • 7.0.0
  • 7.1.3.2 (0)
  • 7.1.3.4 (0)
  • What's this?
derive_fk_query_constraints(foreign_key) private

No documentation

This method has no description. You can help the Ruby on Rails community by adding new notes.

Hide source
# File activerecord/lib/active_record/reflection.rb, line 785
        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_join_table
          ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
        end
    end

    class HasManyReflection < AssociationReflection # :nodoc:
      def macro; :has_many; end

      def collection?; true; end

      def association_class
        if options[:through]
          Associations::HasManyThroughAssociation
        else
          Associations::HasManyAssociation
        end
      end
    end

    class HasOneReflection < AssociationReflection # :nodoc:
      def macro; :has_one; end

      def has_one?; true; end

      def association_class
        if options[:through]
          Associations::HasOneThroughAssociation
        else
          Associations::HasOneAssociation
        end
      end
    end

    class BelongsToReflection < AssociationReflection # :nodoc:
      def macro; :belongs_to; end

      def belongs_to?; true; end

      def association_class
        if polymorphic?
          Associations::BelongsToPolymorphicAssociation
        else
          Associations::BelongsToAssociation
        end
      end

      # klass option is necessary to support loading polymorphic associations
      def association_primary_key(klass = nil)
        if primary_key = options[:primary_key]
          @association_primary_key ||= -primary_key.to_s
        elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
          (klass || self.klass).composite_query_constraints_list
        elsif (klass || self.klass).composite_primary_key?
          # If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
          primary_key = (klass || self.klass).primary_key
          primary_key.include?("id") ? "id" : primary_key
        else
          primary_key(klass || self.klass)
        end
      end

      def join_primary_key(klass = nil)
        polymorphic? ? association_primary_key(klass) : association_primary_key
      end

      def join_foreign_key
        foreign_key
      end

      def join_foreign_type
        foreign_type
      end

      private
        def can_find_inverse_of_automatically?(*)
          !polymorphic? && super
        end
    end

    class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
      def macro; :has_and_belongs_to_many; end

      def collection?
        true
      end
    end

    # Holds all the metadata about a :through association as it was specified
    # in the Active Record class.
    class ThroughReflection < AbstractReflection # :nodoc:
      delegate :foreign_key, :foreign_type, :association_foreign_key, :join_id_for, :type,
               :active_record_primary_key, :join_foreign_key, to: :source_reflection

      def initialize(delegate_reflection)
        super()
        @delegate_reflection = delegate_reflection
        @klass = delegate_reflection.options[:anonymous_class]
        @source_reflection_name = delegate_reflection.options[:source]

        ensure_option_not_given_as_class!(:source_type)
      end

      def through_reflection?
        true
      end

      def klass
        @klass ||= delegate_reflection.compute_class(class_name)
      end

      # Returns the source of the through reflection. It checks both a singularized
      # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
      #
      #   class Post < ActiveRecord::Base
      #     has_many :taggings
      #     has_many :tags, through: :taggings
      #   end
      #
      #   class Tagging < ActiveRecord::Base
      #     belongs_to :post
      #     belongs_to :tag
      #   end
      #
      #   tags_reflection = Post.reflect_on_association(:tags)
      #   tags_reflection.source_reflection
      #   # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
      #
      def source_reflection
        through_reflection.klass._reflect_on_association(source_reflection_name)
      end

      # Returns the AssociationReflection object specified in the <tt>:through</tt> option
      # of a HasManyThrough or HasOneThrough association.
      #
      #   class Post < ActiveRecord::Base
      #     has_many :taggings
      #     has_many :tags, through: :taggings
      #   end
      #
      #   tags_reflection = Post.reflect_on_association(:tags)
      #   tags_reflection.through_reflection
      #   # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
      #
      def through_reflection
        active_record._reflect_on_association(options[:through])
      end

      # Returns an array of reflections which are involved in this association. Each item in the
      # array corresponds to a table which will be part of the query for this association.
      #
      # The chain is built by recursively calling #chain on the source reflection and the through
      # reflection. The base case for the recursion is a normal association, which just returns
      # [self] as its #chain.
      #
      #   class Post < ActiveRecord::Base
      #     has_many :taggings
      #     has_many :tags, through: :taggings
      #   end
      #
      #   tags_reflection = Post.reflect_on_association(:tags)
      #   tags_reflection.chain
      #   # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
      #         <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
      #
      def collect_join_chain
        collect_join_reflections [self]
      end

      # This is for clearing cache on the reflection. Useful for tests that need to compare
      # SQL queries on associations.
      def clear_association_scope_cache # :nodoc:
        delegate_reflection.clear_association_scope_cache
        source_reflection.clear_association_scope_cache
        through_reflection.clear_association_scope_cache
      end

      def scopes
        source_reflection.scopes + super
      end

      def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
        source_reflection.join_scopes(table, predicate_builder, klass, record) + super
      end

      def has_scope?
        scope || options[:source_type] ||
          source_reflection.has_scope? ||
          through_reflection.has_scope?
      end

      # A through association is nested if there would be more than one join table
      def nested?
        source_reflection.through_reflection? || through_reflection.through_reflection?
      end

      # We want to use the klass from this reflection, rather than just delegate straight to
      # the source_reflection, because the source_reflection may be polymorphic. We still
      # need to respect the source_reflection's :primary_key option, though.
      def association_primary_key(klass = nil)
        # Get the "actual" source reflection if the immediate source reflection has a
        # source reflection itself
        if primary_key = actual_source_reflection.options[:primary_key]
          @association_primary_key ||= -primary_key.to_s
        else
          primary_key(klass || self.klass)
        end
      end

      def join_primary_key(klass = self.klass)
        source_reflection.join_primary_key(klass)
      end

      # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
      #
      #   class Post < ActiveRecord::Base
      #     has_many :taggings
      #     has_many :tags, through: :taggings
      #   end
      #
      #   tags_reflection = Post.reflect_on_association(:tags)
      #   tags_reflection.source_reflection_names
      #   # => [:tag, :tags]
      #
      def source_reflection_names
        options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
      end

      def source_reflection_name # :nodoc:
        @source_reflection_name ||= begin
          names = [name.to_s.singularize, name].collect(&:to_sym).uniq
          names = names.find_all { |n|
            through_reflection.klass._reflect_on_association(n)
          }

          if names.length > 1
            raise AmbiguousSourceReflectionForThroughAssociation.new(
              active_record.name,
              macro,
              name,
              options,
              source_reflection_names
            )
          end
          names.first
        end
      end

      def source_options
        source_reflection.options
      end

      def through_options
        through_reflection.options
      end

      def check_validity!
        if through_reflection.nil?
          raise HasManyThroughAssociationNotFoundError.new(active_record, self)
        end

        if through_reflection.polymorphic?
          if has_one?
            raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self)
          else
            raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
          end
        end

        if source_reflection.nil?
          raise HasManyThroughSourceAssociationNotFoundError.new(self)
        end

        if options[:source_type] && !source_reflection.polymorphic?
          raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
        end

        if source_reflection.polymorphic? && options[:source_type].nil?
          raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
        end

        if has_one? && through_reflection.collection?
          raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
        end

        if parent_reflection.nil?
          reflections = active_record.reflections.keys.map(&:to_sym)

          if reflections.index(through_reflection.name) > reflections.index(name)
            raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
          end
        end

        check_validity_of_inverse!
      end

      def constraints
        scope_chain = source_reflection.constraints
        scope_chain << scope if scope
        scope_chain
      end

      def add_as_source(seed)
        collect_join_reflections seed
      end

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

      def add_as_through(seed)
        collect_join_reflections(seed + [self])
      end

      protected
        def actual_source_reflection # FIXME: this is a horrible name
          source_reflection.actual_source_reflection
        end

      private
        attr_reader :delegate_reflection

        def collect_join_reflections(seed)
          a = source_reflection.add_as_source seed
          if options[:source_type]
            through_reflection.add_as_polymorphic_through self, a
          else
            through_reflection.add_as_through a
          end
        end

        def inverse_name; delegate_reflection.send(:inverse_name); end

        def derive_class_name
          # get the class_name of the belongs_to association of the through reflection
          options[:source_type] || source_reflection.class_name
        end

        delegate_methods = AssociationReflection.public_instance_methods -
          public_instance_methods

        delegate(*delegate_methods, to: :delegate_reflection)
    end

    class PolymorphicReflection < AbstractReflection # :nodoc:
      delegate :klass, :scope, :plural_name, :type, :join_primary_key, :join_foreign_key,
               :name, :scope_for, to: :@reflection

      def initialize(reflection, previous_reflection)
        super()
        @reflection = reflection
        @previous_reflection = previous_reflection
      end

      def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
        scopes = @previous_reflection.join_scopes(table, predicate_builder, klass, record) + super
        scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
      end

      def constraints
        @reflection.constraints + [source_type_scope]
      end

      private
        def source_type_scope
          type = @previous_reflection.foreign_type
          source_type = @previous_reflection.options[:source_type]
          lambda { |object| where(type => source_type) }
        end
    end

    class RuntimeReflection < AbstractReflection # :nodoc:
      delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection

      def initialize(reflection, association)
        super()
        @reflection = reflection
        @association = association
      end

      def klass
        @association.klass
      end

      def aliased_table
        klass.arel_table
      end

      def join_primary_key(klass = self.klass)
        @reflection.join_primary_key(klass)
      end

      def all_includes; yield; end
    end
  end
end
Register or log in to add new notes.