Skip to content
This repository
tree: 070dda28ae
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 175 lines (139 sloc) 5.795 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
module ActiveRecord
  module Associations
    class JoinDependency # :nodoc:
      class JoinAssociation < JoinPart # :nodoc:
        include JoinHelper

        # The reflection of the association represented
        attr_reader :reflection

        # The JoinDependency object which this JoinAssociation exists within. This is mainly
        # relevant for generating aliases which do not conflict with other joins which are
        # part of the query.
        attr_reader :join_dependency

        # A JoinBase instance representing the active record we are joining onto.
        # (So in Author.has_many :posts, the Author would be that base record.)
        attr_reader :parent

        # What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin
        attr_accessor :join_type

        # These implement abstract methods from the superclass
        attr_reader :aliased_prefix

        attr_reader :tables

        delegate :options, :through_reflection, :source_reflection, :chain, :to => :reflection
        delegate :table, :table_name, :to => :parent, :prefix => :parent
        delegate :alias_tracker, :to => :join_dependency

        alias :alias_suffix :parent_table_name

        def initialize(reflection, join_dependency, parent = nil)
          reflection.check_validity!

          if reflection.options[:polymorphic]
            raise EagerLoadPolymorphicError.new(reflection)
          end

          super(reflection.klass)

          @reflection = reflection
          @join_dependency = join_dependency
          @parent = parent
          @join_type = Arel::InnerJoin
          @aliased_prefix = "t#{ join_dependency.join_parts.size }"
          @tables = construct_tables.reverse
        end

        def ==(other)
          other.class == self.class &&
            other.reflection == reflection &&
            other.parent == parent
        end

        def find_parent_in(other_join_dependency)
          other_join_dependency.join_parts.detect do |join_part|
            case parent
            when JoinBase
              parent.base_klass == join_part.base_klass
            else
              parent == join_part
            end
          end
        end

        def join_to(manager)
          tables = @tables.dup
          foreign_table = parent_table
          foreign_klass = parent.base_klass

          # The chain starts with the target table, but we want to end with it here (makes
          # more sense in this context), so we reverse
          chain.reverse.each_with_index do |reflection, i|
            table = tables.shift

            case reflection.source_macro
            when :belongs_to
              key = reflection.association_primary_key
              foreign_key = reflection.foreign_key
            when :has_and_belongs_to_many
              # Join the join table first...
              manager.from(join(
                table,
                table[reflection.foreign_key].
                  eq(foreign_table[reflection.active_record_primary_key])
              ))

              foreign_table, table = table, tables.shift

              key = reflection.association_primary_key
              foreign_key = reflection.association_foreign_key
            else
              key = reflection.foreign_key
              foreign_key = reflection.active_record_primary_key
            end

            constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)

            scope_chain_items = scope_chain[i]

            if reflection.type
              scope_chain_items += [
                ActiveRecord::Relation.new(reflection.klass, table)
                  .where(reflection.type => foreign_klass.base_class.name)
              ]
            end

            scope_chain_items.each do |item|
              unless item.is_a?(Relation)
                item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item)
              end

              constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty?
            end

            manager.from(join(table, constraint))

            # The current table in this iteration becomes the foreign table in the next
            foreign_table, foreign_klass = table, reflection.klass
          end

          manager
        end

        # Builds equality condition.
        #
        # Example:
        #
        # class Physician < ActiveRecord::Base
        # has_many :appointments
        # end
        #
        # If I execute `Physician.joins(:appointments).to_a` then
        # reflection #=> #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
        # table #=> #<Arel::Table @name="appointments" ...>
        # key #=> physician_id
        # foreign_table #=> #<Arel::Table @name="physicians" ...>
        # foreign_key #=> id
        #
        def build_constraint(reflection, table, key, foreign_table, foreign_key)
          constraint = table[key].eq(foreign_table[foreign_key])

          if reflection.klass.finder_needs_type_condition?
            constraint = table.create_and([
              constraint,
              reflection.klass.send(:type_condition, table)
            ])
          end

          constraint
        end

        def join_relation(joining_relation)
          self.join_type = Arel::OuterJoin
          joining_relation.joins(self)
        end

        def table
          tables.last
        end

        def aliased_table_name
          table.table_alias || table.name
        end

        def scope_chain
          @scope_chain ||= reflection.scope_chain.reverse
        end

      end
    end
  end
end
Something went wrong with that request. Please try again.