diff options
Diffstat (limited to 'activerecord/lib/active_record/associations/join_dependency/join_association.rb')
-rw-r--r-- | activerecord/lib/active_record/associations/join_dependency/join_association.rb | 166 |
1 files changed, 67 insertions, 99 deletions
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index 0d3b4dbab1..5dede5527d 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -1,153 +1,121 @@ +require 'active_record/associations/join_dependency/join_part' + 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 + attr_accessor :tables - 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) + def initialize(reflection, children) + super(reflection.klass, children) @reflection = reflection - @join_dependency = join_dependency - @parent = parent - @join_type = Arel::InnerJoin - @aliased_prefix = "t#{ join_dependency.join_parts.size }" - @tables = construct_tables.reverse + @tables = nil end - def ==(other) - other.class == self.class && - other.reflection == reflection && - other.parent == parent + def match?(other) + return true if self == other + super && reflection == other.reflection end - def find_parent_in(other_join_dependency) - other_join_dependency.join_parts.detect do |join_part| - parent == join_part - end - end + JoinInformation = Struct.new :joins, :binds - def join_to(relation) - tables = @tables.dup - foreign_table = parent_table - foreign_klass = parent.active_record + def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain) + joins = [] + bind_values = [] + tables = tables.reverse + + scope_chain_index = 0 + scope_chain = scope_chain.reverse # 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| + chain.reverse_each do |reflection| table = tables.shift + klass = reflection.klass + + join_keys = reflection.join_keys(klass) + key = join_keys.key + foreign_key = join_keys.foreign_key - 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... - relation.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 + constraint = build_constraint(klass, table, key, foreign_table, foreign_key) + + scope_chain_items = scope_chain[scope_chain_index].map do |item| + if item.is_a?(Relation) + item + else + ActiveRecord::Relation.create(klass, table).instance_exec(node, &item) + end end + scope_chain_index += 1 - constraint = build_constraint(reflection, table, key, foreign_table, foreign_key) + scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact - scope_chain_items = scope_chain[i] + rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right| + left.merge right + end - if reflection.type - scope_chain_items += [ - ActiveRecord::Relation.new(reflection.klass, table) - .where(reflection.type => foreign_klass.base_class.name) - ] + if rel && !rel.arel.constraints.empty? + bind_values.concat rel.bind_values + constraint = constraint.and rel.arel.constraints end - scope_chain_items.each do |item| - unless item.is_a?(Relation) - item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item) - end + if reflection.type + value = foreign_klass.base_class.name + column = klass.columns_hash[reflection.type.to_s] - constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty? + substitute = klass.connection.substitute_at(column) + bind_values.push [column, value] + constraint = constraint.and table[reflection.type].eq substitute end - relation.from(join(table, constraint)) + joins << table.create_join(table, table.create_on(constraint), join_type) # The current table in this iteration becomes the foreign table in the next - foreign_table, foreign_klass = table, reflection.klass + foreign_table, foreign_klass = table, klass end - relation + JoinInformation.new joins, bind_values end - def build_constraint(reflection, table, key, foreign_table, foreign_key) + # Builds equality condition. + # + # Example: + # + # class Physician < ActiveRecord::Base + # has_many :appointments + # end + # + # If I execute `Physician.joins(:appointments).to_a` then + # klass # => Physician + # table # => #<Arel::Table @name="appointments" ...> + # key # => physician_id + # foreign_table # => #<Arel::Table @name="physicians" ...> + # foreign_key # => id + # + def build_constraint(klass, table, key, foreign_table, foreign_key) constraint = table[key].eq(foreign_table[foreign_key]) - if reflection.klass.finder_needs_type_condition? + if klass.finder_needs_type_condition? constraint = table.create_and([ constraint, - reflection.klass.send(:type_condition, table) + 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 + tables.first end def aliased_table_name table.table_alias || table.name end - - def scope_chain - @scope_chain ||= reflection.scope_chain.reverse - end - end end end |