Refactor to lazy construct join table aliases

Early constructed join table aliases are not always used for eager
loading, and it is required refactoring to improve eager loading
duplicated through associations.
This commit is contained in:
Ryuta Kamizono 2020-05-25 09:49:17 +09:00
parent c179a06f35
commit 590b045ee2
4 changed files with 14 additions and 29 deletions

@ -6,9 +6,14 @@ module ActiveRecord
module Associations
# Keeps track of table aliases for ActiveRecord::Associations::JoinDependency
class AliasTracker # :nodoc:
def self.create(connection, initial_table, joins)
def self.create(connection, initial_table, joins, aliases = nil)
if joins.empty?
aliases = Hash.new(0)
aliases ||= Hash.new(0)
elsif aliases
default_proc = aliases.default_proc || proc { 0 }
aliases.default_proc = proc { |h, k|
h[k] = initial_count_for(connection, k, joins) + default_proc.call(h, k)
}
else
aliases = Hash.new { |h, k|
h[k] = initial_count_for(connection, k, joins)
@ -32,8 +37,6 @@ def self.initial_count_for(connection, name, table_joins)
).size
elsif join.is_a?(Arel::Nodes::Join)
join.left.name == name ? 1 : 0
elsif join.is_a?(Hash)
join[name]
else
raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join"
end

@ -81,11 +81,9 @@ def reflections
def join_constraints(joins_to_add, alias_tracker)
@alias_tracker = alias_tracker
construct_tables!(join_root)
joins = make_join_constraints(join_root, join_type)
joins.concat joins_to_add.flat_map { |oj|
construct_tables!(oj.join_root)
if join_root.match? oj.join_root
walk(join_root, oj.join_root, oj.join_type)
else
@ -157,12 +155,6 @@ def aliases
}
end
def construct_tables!(join_root)
join_root.each_children do |parent, child|
child.tables = table_aliases_for(parent, child)
end
end
def make_join_constraints(join_root, join_type)
join_root.children.flat_map do |child|
make_constraints(join_root, child, join_type)
@ -172,18 +164,13 @@ def make_join_constraints(join_root, join_type)
def make_constraints(parent, child, join_type)
foreign_table = parent.table
foreign_klass = parent.base_klass
joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
joins.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
end
def table_aliases_for(parent, node)
node.reflection.chain.map { |reflection|
child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
alias_tracker.aliased_table_for(
reflection.table_name,
table_alias_for(reflection, parent, reflection != node.reflection),
table_alias_for(reflection, parent, reflection != child.reflection),
reflection.klass.type_caster
)
}
end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
end
def table_alias_for(reflection, parent, join)

@ -14,7 +14,6 @@ def initialize(reflection, children)
super(reflection.klass, children)
@reflection = reflection
@tables = nil
end
def match?(other)
@ -22,8 +21,10 @@ def match?(other)
super && reflection == other.reflection
end
def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker, &block)
joins = []
tables = reflection.chain.map(&block)
@table = tables.first
# The chain starts with the target table, but we want to end with it here (makes
# more sense in this context), so we reverse
@ -63,11 +64,6 @@ def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
joins
end
def tables=(tables)
@tables = tables
@table = tables.first
end
def readonly?
return @readonly if defined?(@readonly)

@ -749,8 +749,7 @@ def has_limit_or_offset? # :nodoc:
end
def alias_tracker(joins = [], aliases = nil) # :nodoc:
joins += [aliases] if aliases
ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins)
ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins, aliases)
end
class StrictLoadingScope