Skip to content

Commit

Permalink
Extract aliasing code from JoinDependency and JoinAssociation into a …
Browse files Browse the repository at this point in the history
…separate AliasTracker class. This can then be used by ThroughAssociationScope as well.
  • Loading branch information
jonleighton committed Oct 12, 2010
1 parent c37a5e7 commit e887431
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 41 deletions.
56 changes: 15 additions & 41 deletions activerecord/lib/active_record/associations.rb
Expand Up @@ -114,6 +114,7 @@ module Associations # :nodoc:
autoload :NestedHasManyThroughAssociation, 'active_record/associations/nested_has_many_through_association' autoload :NestedHasManyThroughAssociation, 'active_record/associations/nested_has_many_through_association'
autoload :HasOneAssociation, 'active_record/associations/has_one_association' autoload :HasOneAssociation, 'active_record/associations/has_one_association'
autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association' autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
autoload :AliasTracker, 'active_record/associations/alias_tracker'


# Clears out the association cache. # Clears out the association cache.
def clear_association_cache #:nodoc: def clear_association_cache #:nodoc:
Expand Down Expand Up @@ -1834,16 +1835,16 @@ def create_extension_modules(association_id, block_extension, extensions)
end end


class JoinDependency # :nodoc: class JoinDependency # :nodoc:
attr_reader :join_parts, :reflections, :table_aliases attr_reader :join_parts, :reflections, :alias_tracker


def initialize(base, associations, joins) def initialize(base, associations, joins)
@join_parts = [JoinBase.new(base, joins)] @join_parts = [JoinBase.new(base, joins)]
@associations = associations @associations = associations
@reflections = [] @reflections = []
@base_records_hash = {} @base_records_hash = {}
@base_records_in_order = [] @base_records_in_order = []
@table_aliases = Hash.new(0) @alias_tracker = AliasTracker.new(joins)
@table_aliases[base.table_name] = 1 @alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
build(associations) build(associations)
end end


Expand All @@ -1863,17 +1864,6 @@ def join_base
join_parts.first join_parts.first
end end


def count_aliases_from_table_joins(name)
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
quoted_name = join_base.active_record.connection.quote_table_name(name.downcase).downcase
join_sql = join_base.table_joins.to_s.downcase
join_sql.blank? ? 0 :
# Table names
join_sql.scan(/join(?:\s+\w+)?\s+#{quoted_name}\son/).size +
# Table aliases
join_sql.scan(/join(?:\s+\w+)?\s+\S+\s+#{quoted_name}\son/).size
end

def instantiate(rows) def instantiate(rows)
rows.each_with_index do |row, i| rows.each_with_index do |row, i|
primary_id = join_base.record_id(row) primary_id = join_base.record_id(row)
Expand Down Expand Up @@ -2130,6 +2120,7 @@ class JoinAssociation < JoinPart # :nodoc:


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


def initialize(reflection, join_dependency, parent = nil) def initialize(reflection, join_dependency, parent = nil)
reflection.check_validity! reflection.check_validity!
Expand Down Expand Up @@ -2248,37 +2239,23 @@ def aliased_table_name


protected protected


def aliased_table_name_for(name, aliased_name, suffix = nil) def table_alias_for(reflection)
if @join_dependency.table_aliases[name].zero? name = pluralize(reflection.name)
@join_dependency.table_aliases[name] = @join_dependency.count_aliases_from_table_joins(name) name << "_#{parent_table_name}"
end name << "_join" if reflection != self.reflection

if !@join_dependency.table_aliases[name].zero? # We need an alias
name = active_record.connection.table_alias_for "#{aliased_name}_#{parent_table_name}#{suffix}"
@join_dependency.table_aliases[name] += 1
if @join_dependency.table_aliases[name] == 1 # First time we've seen this name
# Also need to count the aliases from the table_aliases to avoid incorrect count
@join_dependency.table_aliases[name] += @join_dependency.count_aliases_from_table_joins(name)
end
table_index = @join_dependency.table_aliases[name]
name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index}" if table_index > 1
else
@join_dependency.table_aliases[name] += 1
end

name name
end end


def pluralize(table_name) def pluralize(table_name)
ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
end end


def table_alias_for(table_name, table_alias) def table_name_and_alias_for(table_name, table_alias)
"#{table_name} #{table_alias if table_name != table_alias}".strip "#{table_name} #{table_alias if table_name != table_alias}".strip
end end


def table_name_and_alias def table_name_and_alias
table_alias_for table_name, aliased_table_name table_name_and_alias_for(table_name, aliased_table_name)
end end


def interpolate_sql(sql) def interpolate_sql(sql)
Expand All @@ -2292,12 +2269,9 @@ def interpolate_sql(sql)
# the proper alias. # the proper alias.
def setup_tables def setup_tables
@tables = through_reflection_chain.map do |reflection| @tables = through_reflection_chain.map do |reflection|
suffix = reflection == self.reflection ? nil : '_join' aliased_table_name = alias_tracker.aliased_name_for(

aliased_table_name = aliased_table_name_for(
reflection.table_name, reflection.table_name,
pluralize(reflection.name), table_alias_for(reflection)
suffix
) )


table = Arel::Table.new( table = Arel::Table.new(
Expand All @@ -2308,9 +2282,9 @@ def setup_tables
# For habtm, we have two Arel::Table instances related to a single reflection, so # For habtm, we have two Arel::Table instances related to a single reflection, so
# we just store them as a pair in the array. # we just store them as a pair in the array.
if reflection.macro == :has_and_belongs_to_many if reflection.macro == :has_and_belongs_to_many
aliased_join_table_name = aliased_table_name_for( aliased_join_table_name = alias_tracker.aliased_name_for(
reflection.options[:join_table], reflection.options[:join_table],
pluralize(reflection.name), "_join" table_alias_for(reflection)
) )


join_table = Arel::Table.new( join_table = Arel::Table.new(
Expand Down
68 changes: 68 additions & 0 deletions activerecord/lib/active_record/associations/alias_tracker.rb
@@ -0,0 +1,68 @@
require 'active_support/core_ext/string/conversions'

module ActiveRecord
module Associations
# Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency
class AliasTracker # :nodoc:
# other_sql is some other sql which might conflict with the aliases we assign here. Therefore
# we store other_sql so that we can scan it before assigning a specific name.
def initialize(other_sql)
@aliases = Hash.new
@other_sql = other_sql.to_s.downcase
end

def aliased_name_for(table_name, aliased_name = nil)
aliased_name ||= table_name

initialize_count_for(table_name) if @aliases[table_name].nil?

if @aliases[table_name].zero?
# If it's zero, we can have our table_name
@aliases[table_name] = 1
table_name
else
# Otherwise, we need to use an alias
aliased_name = connection.table_alias_for(aliased_name)

initialize_count_for(aliased_name) if @aliases[aliased_name].nil?

# Update the count
@aliases[aliased_name] += 1

if @aliases[aliased_name] > 1
"#{truncate(aliased_name)}_#{@aliases[aliased_name]}"
else
aliased_name
end
end
end

private

def initialize_count_for(name)
@aliases[name] = 0

unless @other_sql.blank?
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
quoted_name = connection.quote_table_name(name.downcase).downcase

# Table names
@aliases[name] += @other_sql.scan(/join(?:\s+\w+)?\s+#{quoted_name}\son/).size

# Table aliases
@aliases[name] += @other_sql.scan(/join(?:\s+\w+)?\s+\S+\s+#{quoted_name}\son/).size
end

@aliases[name]
end

def truncate(name)
name[0..connection.table_alias_length-3]
end

def connection
ActiveRecord::Base.connection
end
end
end
end

0 comments on commit e887431

Please sign in to comment.