aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorRyuta Kamizono <kamipo@gmail.com>2019-07-30 02:37:26 +0900
committerRyuta Kamizono <kamipo@gmail.com>2019-07-30 04:02:58 +0900
commitb4478ae8bc37c76d0143c2ff99dfc75ef34d2234 (patch)
tree574575b62a0c199242c8ce9c670b641fb114e2d0 /activerecord/lib
parent8d2e75e84cf78123d5b89e7bab727ad7499ddfa4 (diff)
downloadrails-b4478ae8bc37c76d0143c2ff99dfc75ef34d2234.tar.gz
rails-b4478ae8bc37c76d0143c2ff99dfc75ef34d2234.tar.bz2
rails-b4478ae8bc37c76d0143c2ff99dfc75ef34d2234.zip
Preserve user supplied joins order as much as possible
Currently, string joins are always applied as last joins part, and Arel join nodes are always applied as leading joins part (since #36304), it makes people struggled to preserve user supplied joins order. To mitigate this problem, preserve the order of string joins and Arel join nodes either before or after of association joins. Fixes #36761. Fixes #34328. Fixes #24281. Fixes #12953.
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb46
1 files changed, 30 insertions, 16 deletions
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 6957ba052b..29e2e2cedc 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1095,26 +1095,44 @@ module ActiveRecord
end
def build_left_outer_joins(manager, outer_joins, aliases)
- buckets = { association_join: valid_association_list(outer_joins) }
+ buckets = Hash.new { |h, k| h[k] = [] }
+ buckets[:association_join] = valid_association_list(outer_joins)
build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
end
def build_joins(manager, joins, aliases)
+ buckets = Hash.new { |h, k| h[k] = [] }
+
unless left_outer_joins_values.empty?
left_joins = valid_association_list(left_outer_joins_values.flatten)
- joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
+ buckets[:stashed_join] << construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
end
- buckets = joins.group_by do |join|
+ joins.map! do |join|
+ if join.is_a?(String)
+ table.create_string_join(Arel.sql(join.strip)) unless join.blank?
+ else
+ join
+ end
+ end.compact_blank!
+
+ while joins.first.is_a?(Arel::Nodes::Join)
+ join_node = joins.shift
+ if join_node.is_a?(Arel::Nodes::StringJoin) && !buckets[:stashed_join].empty?
+ buckets[:join_node] << join_node
+ else
+ buckets[:leading_join] << join_node
+ end
+ end
+
+ joins.each do |join|
case join
- when String
- :string_join
when Hash, Symbol, Array
- :association_join
+ buckets[:association_join] << join
when ActiveRecord::Associations::JoinDependency
- :stashed_join
+ buckets[:stashed_join] << join
when Arel::Nodes::Join
- :join_node
+ buckets[:join_node] << join
else
raise "unknown class: %s" % join.class.name
end
@@ -1124,25 +1142,21 @@ module ActiveRecord
end
def build_join_query(manager, buckets, join_type, aliases)
- buckets.default = []
-
association_joins = buckets[:association_join]
stashed_joins = buckets[:stashed_join]
+ leading_joins = buckets[:leading_join].tap(&:uniq!)
join_nodes = buckets[:join_node].tap(&:uniq!)
- string_joins = buckets[:string_join].compact_blank!.map!(&:strip).tap(&:uniq!)
-
- string_joins.map! { |join| table.create_string_join(Arel.sql(join)) }
join_sources = manager.join_sources
- join_sources.concat(join_nodes) unless join_nodes.empty?
+ join_sources.concat(leading_joins) unless leading_joins.empty?
unless association_joins.empty? && stashed_joins.empty?
- alias_tracker = alias_tracker(join_nodes + string_joins, aliases)
+ alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
join_dependency = construct_join_dependency(association_joins, join_type)
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
end
- join_sources.concat(string_joins) unless string_joins.empty?
+ join_sources.concat(join_nodes) unless join_nodes.empty?
end
def build_select(arel)