diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index bc235cf63d9a0..a25a061fc0331 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -85,6 +85,20 @@ def outer_joins nodes end + def merge_outer_joins!(other) + left = join_root + right = other.join_root + + if left.match? right + merge_node left, right + else + # If the roots aren't the same, then deep copy the RHS to the LHS + left.children.concat right.children.map { |node| + deep_copy left, node + } + end + end + def join_constraints join_root.flat_map(&:join_constraints) end @@ -118,6 +132,22 @@ def instantiate(result_set) private + def merge_node(left, right) + intersection, missing = right.children.map { |node1| + [left.children.find { |node2| node1.match? node2 }, node1] + }.partition(&:first) + + intersection.each { |l,r| merge_node l, r } + + left.children.concat missing.map { |_,node| deep_copy left, node } + end + + def deep_copy(parent, node) + dup = build_join_association(node.reflection, parent, Arel::OuterJoin) + dup.children.concat node.children.map { |n| deep_copy dup, n } + dup + end + def find_node(target_node) stack = target_node.parents << target_node diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 364c7ec418838..9c9690215ab81 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -951,7 +951,7 @@ def build_joins(manager, joins) ) stashed_association_joins.each do |dep| - join_dependency.graft dep.outer_joins + join_dependency.merge_outer_joins! dep end joins = join_dependency.join_constraints