From cfc461c3f8ee3a5f35fb05c75e492176c4d8854a Mon Sep 17 00:00:00 2001 From: Ken Collins Date: Fri, 22 Sep 2017 21:33:14 -0400 Subject: Ensure `1 AS one` for SQL Server with calculations. --- activerecord/lib/active_record/relation/calculations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 42d43224fa..0889d61c92 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -391,7 +391,7 @@ module ActiveRecord def build_count_subquery(relation, column_name, distinct) relation.select_values = [ if column_name == :all - distinct ? table[Arel.star] : Arel.sql("1") + distinct ? table[Arel.star] : Arel.sql(FinderMethods::ONE_AS_ONE) else column_alias = Arel.sql("count_column") aggregate_column(column_name).as(column_alias) -- cgit v1.2.3 From 9cf7e3494f5bd34f1382c1ff4ea3d811a4972ae2 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 26 Sep 2017 13:22:12 -0600 Subject: Treat `Set` as an `Array` in `Relation#where` I do not want to set the expectation that any enumerable object should behave this way, but this case in particular comes up frequently enough that I'm caving on this one. Fixes #30684. --- activerecord/lib/active_record/relation/predicate_builder.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 5c42414072..be4b169f67 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -13,6 +13,7 @@ module ActiveRecord register_handler(Range, RangeHandler.new(self)) register_handler(Relation, RelationHandler.new) register_handler(Array, ArrayHandler.new(self)) + register_handler(Set, ArrayHandler.new(self)) end def build_from_hash(attributes) -- cgit v1.2.3 From 8a8ac422d8c388bf5f39ae2e6ec3ae84780186e7 Mon Sep 17 00:00:00 2001 From: meganemura Date: Thu, 5 Oct 2017 13:21:03 +0900 Subject: Use __callee__ to pass alias instead of original method name Before ``` > Article.left_joins ArgumentError: The method .left_outer_joins() must contain arguments. ``` After ``` > Article.left_joins ArgumentError: The method .left_joins() must contain arguments. ``` --- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index c88603fde2..e76ad05b71 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -441,7 +441,7 @@ module ActiveRecord # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" # def left_outer_joins(*args) - check_if_method_has_arguments!(:left_outer_joins, args) + check_if_method_has_arguments!(__callee__, args) args.compact! args.flatten! -- cgit v1.2.3 From 8ad8bbaef8c4f521f292765a02ddb91e48160366 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 7 Oct 2017 17:29:45 +0900 Subject: Decouple building `AliasTracker` from `JoinDependency` This is preparation to respect parent relation's alias tracking for fixing #30681. --- activerecord/lib/active_record/relation/finder_methods.rb | 4 +++- activerecord/lib/active_record/relation/merger.rb | 2 +- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c92d5a52f4..5d4b549470 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -390,7 +390,9 @@ module ActiveRecord def construct_join_dependency(joins = [], eager_loading: true) including = eager_load_values + includes_values - ActiveRecord::Associations::JoinDependency.new(klass, table, including, joins, eager_loading: eager_loading) + ActiveRecord::Associations::JoinDependency.new( + klass, table, including, alias_tracker(joins), eager_loading: eager_loading + ) end def construct_relation_for_association_calculations diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 03824ffff9..ebc72d28fd 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -122,7 +122,7 @@ module ActiveRecord end join_dependency = ActiveRecord::Associations::JoinDependency.new( - other.klass, other.table, joins_dependency, [] + other.klass, other.table, joins_dependency, other.alias_tracker ) relation.joins! rest diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index e76ad05b71..fb5beeffef 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1013,7 +1013,7 @@ module ActiveRecord join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) join_dependency = ActiveRecord::Associations::JoinDependency.new( - klass, table, association_joins, join_list + klass, table, association_joins, alias_tracker(join_list) ) joins = join_dependency.join_constraints(stashed_association_joins, join_type) -- cgit v1.2.3 From 00f2d39c2e3111bf695aed9335388f0e06904d6a Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 9 Oct 2017 07:05:26 +0900 Subject: Remove passing redundant `self` to internal `apply_join_dependency` etc --- .../lib/active_record/relation/finder_methods.rb | 25 +++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 5d4b549470..5ccfe3d1b7 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -310,12 +310,12 @@ module ActiveRecord return false if !conditions || limit_value == 0 - relation = self unless eager_loading? - relation ||= apply_join_dependency(self, construct_join_dependency(eager_loading: false)) - - return false if ActiveRecord::NullRelation === relation + if eager_loading? + relation = apply_join_dependency(construct_join_dependency(eager_loading: false)) + return relation.exists?(conditions) + end - relation = construct_relation_for_exists(relation, conditions) + relation = construct_relation_for_exists(conditions) skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false rescue ::RangeError @@ -368,15 +368,14 @@ module ActiveRecord # join_dependency = construct_join_dependency(joins_values) - aliases = join_dependency.aliases - relation = select aliases.columns - relation = apply_join_dependency(relation, join_dependency) + relation = apply_join_dependency(join_dependency) + relation._select!(join_dependency.aliases.columns) yield relation, join_dependency end - def construct_relation_for_exists(relation, conditions) - relation = relation.except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1) + def construct_relation_for_exists(conditions) + relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1) case conditions when Array, Hash @@ -396,11 +395,11 @@ module ActiveRecord end def construct_relation_for_association_calculations - apply_join_dependency(self, construct_join_dependency(joins_values)) + apply_join_dependency(construct_join_dependency(joins_values)) end - def apply_join_dependency(relation, join_dependency) - relation = relation.except(:includes, :eager_load, :preload).joins!(join_dependency) + def apply_join_dependency(join_dependency) + relation = except(:includes, :eager_load, :preload).joins!(join_dependency) if using_limitable_reflections?(join_dependency.reflections) relation -- cgit v1.2.3 From 723b526158df576f673c1bc28439744378a03f5d Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 2 Oct 2017 06:39:42 +0900 Subject: Fix `relation.exists?` with has_many through associations `relation.exists?` should reference correct aliases while joining tables of has_many through associations. --- activerecord/lib/active_record/relation/finder_methods.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 5ccfe3d1b7..6e07402979 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -366,7 +366,7 @@ module ActiveRecord # preexisting join in joins_values to categorizations (by way of # the `has_many :through` for categories). # - join_dependency = construct_join_dependency(joins_values) + join_dependency = construct_join_dependency relation = apply_join_dependency(join_dependency) relation._select!(join_dependency.aliases.columns) @@ -387,15 +387,15 @@ module ActiveRecord relation end - def construct_join_dependency(joins = [], eager_loading: true) + def construct_join_dependency(eager_loading: true) including = eager_load_values + includes_values ActiveRecord::Associations::JoinDependency.new( - klass, table, including, alias_tracker(joins), eager_loading: eager_loading + klass, table, including, alias_tracker(joins_values), eager_loading: eager_loading ) end def construct_relation_for_association_calculations - apply_join_dependency(construct_join_dependency(joins_values)) + apply_join_dependency(construct_join_dependency) end def apply_join_dependency(join_dependency) -- cgit v1.2.3 From bb5b672ac6b4dd694a3f706d98d82f3000ce18cd Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 9 Oct 2017 10:30:03 +0900 Subject: Remove meaningless named `construct_relation_for_association_calculations` I don't think this is a good abstraction because the internal method is used only if the relation need to be applied join dependency. --- activerecord/lib/active_record/relation/calculations.rb | 5 +++-- activerecord/lib/active_record/relation/finder_methods.rb | 4 ---- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 0889d61c92..4ef0502893 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -130,7 +130,7 @@ module ActiveRecord # end def calculate(operation, column_name) if has_include?(column_name) - relation = construct_relation_for_association_calculations + relation = apply_join_dependency(construct_join_dependency) relation.distinct! if operation.to_s.downcase == "count" relation.calculate(operation, column_name) @@ -180,7 +180,8 @@ module ActiveRecord end if has_include?(column_names.first) - construct_relation_for_association_calculations.pluck(*column_names) + relation = apply_join_dependency(construct_join_dependency) + relation.pluck(*column_names) else relation = spawn relation.select_values = column_names.map { |cn| diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 6e07402979..707245bab2 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -394,10 +394,6 @@ module ActiveRecord ) end - def construct_relation_for_association_calculations - apply_join_dependency(construct_join_dependency) - end - def apply_join_dependency(join_dependency) relation = except(:includes, :eager_load, :preload).joins!(join_dependency) -- cgit v1.2.3 From c5ab6e51a7b9ee05a2d262a72c7130b9c1d1b0ce Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 9 Oct 2017 15:18:59 +0900 Subject: Joined tables in association scope doesn't use the same aliases with the parent relation's aliases Building association scope in join dependency should respect the parent relation's aliases to avoid using the same alias name more than once. Fixes #30681. --- .../lib/active_record/relation/query_methods.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index fb5beeffef..578d8a6eb7 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -898,8 +898,8 @@ module ActiveRecord end # Returns the Arel object associated with the relation. - def arel # :nodoc: - @arel ||= build_arel + def arel(aliases = nil) # :nodoc: + @arel ||= build_arel(aliases) end protected @@ -921,11 +921,11 @@ module ActiveRecord raise ImmutableRelation if defined?(@arel) && @arel end - def build_arel + def build_arel(aliases) arel = Arel::SelectManager.new(table) - build_joins(arel, joins_values.flatten) unless joins_values.empty? - build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty? + build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty? + build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty? arel.where(where_clause.ast) unless where_clause.empty? arel.having(having_clause.ast) unless having_clause.empty? @@ -970,7 +970,7 @@ module ActiveRecord end end - def build_left_outer_joins(manager, outer_joins) + def build_left_outer_joins(manager, outer_joins, aliases) buckets = outer_joins.group_by do |join| case join when Hash, Symbol, Array @@ -980,10 +980,10 @@ module ActiveRecord end end - build_join_query(manager, buckets, Arel::Nodes::OuterJoin) + build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases) end - def build_joins(manager, joins) + def build_joins(manager, joins, aliases) buckets = joins.group_by do |join| case join when String @@ -999,10 +999,10 @@ module ActiveRecord end end - build_join_query(manager, buckets, Arel::Nodes::InnerJoin) + build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases) end - def build_join_query(manager, buckets, join_type) + def build_join_query(manager, buckets, join_type, aliases) buckets.default = [] association_joins = buckets[:association_join] @@ -1013,7 +1013,7 @@ module ActiveRecord join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) join_dependency = ActiveRecord::Associations::JoinDependency.new( - klass, table, association_joins, alias_tracker(join_list) + klass, table, association_joins, alias_tracker(join_list, aliases) ) joins = join_dependency.join_constraints(stashed_association_joins, join_type) -- cgit v1.2.3 From 5668dc6b1863ef43be8f8ef0fb1d5db913085fb3 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 14 Oct 2017 13:19:26 +0900 Subject: Fix `COUNT(DISTINCT ...)` for `GROUP BY` with `ORDER BY` and `LIMIT` This is the fix for the regression of #29848. In #29848, I've kept existing select list in the subquery for the count if ORDER BY is given. But it had accidentally affect to GROUP BY queries also. It should keep the previous behavior in that case. Fixes #30886. --- activerecord/lib/active_record/relation/calculations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 4ef0502893..116bddce85 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -216,7 +216,7 @@ module ActiveRecord if operation == "count" column_name ||= select_for_count if column_name == :all - if distinct && !(has_limit_or_offset? && order_values.any?) + if distinct && (group_values.any? || !(has_limit_or_offset? && order_values.any?)) column_name = primary_key end elsif column_name =~ /\s*DISTINCT[\s(]+/i -- cgit v1.2.3 From d5ea33b9a003d32ebaeae7ad89c479c04f790642 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Sat, 21 Oct 2017 22:17:03 +0900 Subject: [Active Record] require => require_relative This basically reverts 9d4f79d3d394edb74fa2192e5d9ad7b09ce50c6d --- activerecord/lib/active_record/relation/batches.rb | 2 +- .../lib/active_record/relation/predicate_builder.rb | 16 ++++++++-------- .../lib/active_record/relation/query_attribute.rb | 2 +- activerecord/lib/active_record/relation/query_methods.rb | 8 ++++---- activerecord/lib/active_record/relation/spawn_methods.rb | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 356ad0dcd6..561869017a 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "batches/batch_enumerator" +require "active_record/relation/batches/batch_enumerator" module ActiveRecord module Batches diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index be4b169f67..885c26d7aa 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -124,11 +124,11 @@ module ActiveRecord end end -require_relative "predicate_builder/array_handler" -require_relative "predicate_builder/base_handler" -require_relative "predicate_builder/basic_object_handler" -require_relative "predicate_builder/range_handler" -require_relative "predicate_builder/relation_handler" - -require_relative "predicate_builder/association_query_value" -require_relative "predicate_builder/polymorphic_array_value" +require "active_record/relation/predicate_builder/array_handler" +require "active_record/relation/predicate_builder/base_handler" +require "active_record/relation/predicate_builder/basic_object_handler" +require "active_record/relation/predicate_builder/range_handler" +require "active_record/relation/predicate_builder/relation_handler" + +require "active_record/relation/predicate_builder/association_query_value" +require "active_record/relation/predicate_builder/polymorphic_array_value" diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb index 5a9a7fd432..fad08e2613 100644 --- a/activerecord/lib/active_record/relation/query_attribute.rb +++ b/activerecord/lib/active_record/relation/query_attribute.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../attribute" +require "active_record/attribute" module ActiveRecord class Relation diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 578d8a6eb7..269790cdba 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require_relative "from_clause" -require_relative "query_attribute" -require_relative "where_clause" -require_relative "where_clause_factory" +require "active_record/relation/from_clause" +require "active_record/relation/query_attribute" +require "active_record/relation/where_clause" +require "active_record/relation/where_clause_factory" require "active_model/forbidden_attributes_protection" module ActiveRecord diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 424894f835..617d8de8b2 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -2,7 +2,7 @@ require "active_support/core_ext/hash/except" require "active_support/core_ext/hash/slice" -require_relative "merger" +require "active_record/relation/merger" module ActiveRecord module SpawnMethods -- cgit v1.2.3 From 40cadf52db5e21349f6ecd9c4dc9b89d42ebc986 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 23 Oct 2017 22:20:58 +0900 Subject: Fix duplicate aliases when using both INNER/LEFT JOINs It should be shared the count of alias tracking in both INNER/LEFT JOINs to avoid duplicate aliases. Fixes #30504. Closes #30410. --- activerecord/lib/active_record/relation/query_methods.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 269790cdba..897ff5c8af 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -924,7 +924,7 @@ module ActiveRecord def build_arel(aliases) arel = Arel::SelectManager.new(table) - build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty? + aliases = build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty? build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty? arel.where(where_clause.ast) unless where_clause.empty? @@ -1011,9 +1011,10 @@ module ActiveRecord string_joins = buckets[:string_join].map(&:strip).uniq join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) + alias_tracker = alias_tracker(join_list, aliases) join_dependency = ActiveRecord::Associations::JoinDependency.new( - klass, table, association_joins, alias_tracker(join_list, aliases) + klass, table, association_joins, alias_tracker ) joins = join_dependency.join_constraints(stashed_association_joins, join_type) @@ -1021,7 +1022,7 @@ module ActiveRecord manager.join_sources.concat(join_list) - manager + alias_tracker.aliases end def convert_join_strings_to_ast(table, joins) -- cgit v1.2.3 From e3a295664681ea0480a9c95f6bfc776a113ff926 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 1 Nov 2017 10:36:47 +0900 Subject: Ensure `apply_join_dependency` for `collection_cache_key` if eager-loading is needed Fixes #30315. --- activerecord/lib/active_record/relation/calculations.rb | 4 ++-- activerecord/lib/active_record/relation/finder_methods.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 116bddce85..11256ab3d9 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -130,7 +130,7 @@ module ActiveRecord # end def calculate(operation, column_name) if has_include?(column_name) - relation = apply_join_dependency(construct_join_dependency) + relation = apply_join_dependency relation.distinct! if operation.to_s.downcase == "count" relation.calculate(operation, column_name) @@ -180,7 +180,7 @@ module ActiveRecord end if has_include?(column_names.first) - relation = apply_join_dependency(construct_join_dependency) + relation = apply_join_dependency relation.pluck(*column_names) else relation = spawn diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 707245bab2..18566b5662 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -394,7 +394,7 @@ module ActiveRecord ) end - def apply_join_dependency(join_dependency) + def apply_join_dependency(join_dependency = construct_join_dependency) relation = except(:includes, :eager_load, :preload).joins!(join_dependency) if using_limitable_reflections?(join_dependency.reflections) -- cgit v1.2.3 From f989b341eccc6a86fd1ddfff7f1441920855c84e Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Wed, 8 Feb 2017 11:23:26 -0700 Subject: add config to check arguments to unsafe AR methods --- .../lib/active_record/relation/calculations.rb | 42 ++++++++++++------- .../lib/active_record/relation/query_methods.rb | 49 ++++++++++++++++++++++ 2 files changed, 77 insertions(+), 14 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 11256ab3d9..dea7542f03 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -175,21 +175,13 @@ module ActiveRecord # See also #ids. # def pluck(*column_names) - if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty? - return records.pluck(*column_names) - end + _pluck(column_names, @klass.allow_unsafe_raw_sql == :enabled) + end - if has_include?(column_names.first) - relation = apply_join_dependency - relation.pluck(*column_names) - else - relation = spawn - relation.select_values = column_names.map { |cn| - @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn - } - result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) } - result.cast_values(klass.attribute_types) - end + # Same as #pluck but allows raw SQL regardless of `allow_unsafe_raw_sql` + # config setting. + def unsafe_raw_pluck(*column_names) + _pluck(column_names, true) end # Pluck all the ID's for the relation using the table's primary key @@ -202,6 +194,28 @@ module ActiveRecord private + def _pluck(column_names, unsafe_raw) + unrecognized = column_names.reject do |cn| + @klass.respond_to_attribute?(cn) + end + + if loaded? && unrecognized.none? + records.pluck(*column_names) + elsif has_include?(column_names.first) + relation = apply_join_dependency + relation.pluck(*column_names) + elsif unsafe_raw || unrecognized.none? + relation = spawn + relation.select_values = column_names.map { |cn| + @klass.respond_to_attribute?(cn) ? arel_attribute(cn) : cn + } + result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) } + result.cast_values(klass.attribute_types) + else + raise ArgumentError, "Invalid column name: #{unrecognized}" + end + end + def has_include?(column_name) eager_loading? || (includes_values.present? && column_name && column_name != :all) end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 897ff5c8af..63b1d8e154 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -295,7 +295,22 @@ module ActiveRecord spawn.order!(*args) end + # Same as #order but allows raw SQL regardless of `allow_unsafe_raw_sql` + # config setting. + def unsafe_raw_order(*args) # :nodoc: + check_if_method_has_arguments!(:order, args) + spawn.unsafe_raw_order!(*args) + end + + # Same as #order but operates on relation in-place instead of copying. def order!(*args) # :nodoc: + restrict_order_args(args) unless klass.allow_unsafe_raw_sql == :enabled + unsafe_raw_order!(*args) + end + + # Same as #order! but allows raw SQL regardless of `allow_unsafe_raw_sql` + # config setting. + def unsafe_raw_order!(*args) # :nodoc: preprocess_order_args(args) self.order_values += args @@ -316,7 +331,22 @@ module ActiveRecord spawn.reorder!(*args) end + # Same as #reorder but allows raw SQL regardless of `allow_unsafe_raw_sql` + # config setting. + def unsafe_raw_reorder(*args) # :nodoc: + check_if_method_has_arguments!(:reorder, args) + spawn.unsafe_raw_reorder!(*args) + end + + # Same as #reorder but operates on relation in-place instead of copying. def reorder!(*args) # :nodoc: + restrict_order_args(args) unless klass.allow_unsafe_raw_sql == :enabled + unsafe_raw_reorder! + end + + # Same as #reorder! but allows raw SQL regardless of `allow_unsafe_raw_sql` + # config setting. + def unsafe_raw_reorder!(*args) # :nodoc: preprocess_order_args(args) self.reordering_value = true @@ -1139,6 +1169,25 @@ module ActiveRecord end.flatten! end + # Only allow column names and directions as arguments to #order and + # #reorder. Other arguments will cause an ArugmentError to be raised. + def restrict_order_args(args) + args = args.dup + orderings = args.extract_options! + columns = args | orderings.keys + + unrecognized = columns.reject { |c| klass.respond_to_attribute?(c) } + if unrecognized.any? + raise ArgumentError, "Invalid order column: #{unrecognized}" + end + + # TODO: find a better list of modifiers. + unrecognized = orderings.values.reject { |d| VALID_DIRECTIONS.include?(d.to_s) } + if unrecognized.any? + raise ArgumentError, "Invalid order direction: #{unrecognized}" + end + end + # Checks to make sure that the arguments are not blank. Note that if some # blank-like object were initially passed into the query method, then this # method will not raise an error. -- cgit v1.2.3 From 864b16063d14977096d9d24ac894fee605dfb7a7 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Tue, 21 Feb 2017 11:17:16 -0700 Subject: allow Arel.sql() for pluck --- .../lib/active_record/relation/calculations.rb | 20 +++++--- .../lib/active_record/relation/query_methods.rb | 54 +++------------------- 2 files changed, 21 insertions(+), 53 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index dea7542f03..236d36e15f 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -175,13 +175,21 @@ module ActiveRecord # See also #ids. # def pluck(*column_names) - _pluck(column_names, @klass.allow_unsafe_raw_sql == :enabled) - end + if loaded? && (column_names.map(&:to_s) - @klass.attribute_names_and_aliases).empty? + return records.pluck(*column_names) + end - # Same as #pluck but allows raw SQL regardless of `allow_unsafe_raw_sql` - # config setting. - def unsafe_raw_pluck(*column_names) - _pluck(column_names, true) + if has_include?(column_names.first) + construct_relation_for_association_calculations.pluck(*column_names) + else + enforce_raw_sql_whitelist(column_names) + relation = spawn + relation.select_values = column_names.map { |cn| + @klass.respond_to_attribute?(cn) ? arel_attribute(cn) : cn + } + result = klass.connection.select_all(relation.arel, nil, bound_attributes) + result.cast_values(klass.attribute_types) + end end # Pluck all the ID's for the relation using the table's primary key diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 63b1d8e154..4c63d0450a 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -295,22 +295,9 @@ module ActiveRecord spawn.order!(*args) end - # Same as #order but allows raw SQL regardless of `allow_unsafe_raw_sql` - # config setting. - def unsafe_raw_order(*args) # :nodoc: - check_if_method_has_arguments!(:order, args) - spawn.unsafe_raw_order!(*args) - end - # Same as #order but operates on relation in-place instead of copying. def order!(*args) # :nodoc: - restrict_order_args(args) unless klass.allow_unsafe_raw_sql == :enabled - unsafe_raw_order!(*args) - end - - # Same as #order! but allows raw SQL regardless of `allow_unsafe_raw_sql` - # config setting. - def unsafe_raw_order!(*args) # :nodoc: + enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) preprocess_order_args(args) self.order_values += args @@ -331,22 +318,9 @@ module ActiveRecord spawn.reorder!(*args) end - # Same as #reorder but allows raw SQL regardless of `allow_unsafe_raw_sql` - # config setting. - def unsafe_raw_reorder(*args) # :nodoc: - check_if_method_has_arguments!(:reorder, args) - spawn.unsafe_raw_reorder!(*args) - end - # Same as #reorder but operates on relation in-place instead of copying. def reorder!(*args) # :nodoc: - restrict_order_args(args) unless klass.allow_unsafe_raw_sql == :enabled - unsafe_raw_reorder! - end - - # Same as #reorder! but allows raw SQL regardless of `allow_unsafe_raw_sql` - # config setting. - def unsafe_raw_reorder!(*args) # :nodoc: + enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) preprocess_order_args(args) self.reordering_value = true @@ -946,6 +920,11 @@ module ActiveRecord private + # Extract column names from arguments passed to #order or #reorder. + def column_names_from_order_arguments(args) + args.flat_map { |arg| arg.is_a?(Hash) ? arg.keys : arg } + end + def assert_mutability! raise ImmutableRelation if @loaded raise ImmutableRelation if defined?(@arel) && @arel @@ -1169,25 +1148,6 @@ module ActiveRecord end.flatten! end - # Only allow column names and directions as arguments to #order and - # #reorder. Other arguments will cause an ArugmentError to be raised. - def restrict_order_args(args) - args = args.dup - orderings = args.extract_options! - columns = args | orderings.keys - - unrecognized = columns.reject { |c| klass.respond_to_attribute?(c) } - if unrecognized.any? - raise ArgumentError, "Invalid order column: #{unrecognized}" - end - - # TODO: find a better list of modifiers. - unrecognized = orderings.values.reject { |d| VALID_DIRECTIONS.include?(d.to_s) } - if unrecognized.any? - raise ArgumentError, "Invalid order direction: #{unrecognized}" - end - end - # Checks to make sure that the arguments are not blank. Note that if some # blank-like object were initially passed into the query method, then this # method will not raise an error. -- cgit v1.2.3 From 92e78593ee056bb73a7a87c10af3f2587eca1150 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Mon, 25 Sep 2017 10:13:53 -0600 Subject: work with actual string when reversing order --- activerecord/lib/active_record/relation/query_methods.rb | 3 +++ 1 file changed, 3 insertions(+) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 4c63d0450a..7fe0129b4a 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1075,6 +1075,9 @@ module ActiveRecord when Arel::Nodes::Ordering o.reverse when String + # ensure we're not dealing with string subclass (Eg. Arel::Nodes::SqlLiteral) + o = String.new(o) + if does_not_support_reverse?(o) raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically" end -- cgit v1.2.3 From 65328025917f0b60d0fbbeca87f173f34d9c91c5 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Mon, 25 Sep 2017 10:39:35 -0600 Subject: call enforce_raw_sql_whitelist on @klass so it works with FakeKlass --- activerecord/lib/active_record/relation/query_methods.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 7fe0129b4a..f3b44d19d6 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -297,7 +297,7 @@ module ActiveRecord # Same as #order but operates on relation in-place instead of copying. def order!(*args) # :nodoc: - enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) + @klass.enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) preprocess_order_args(args) self.order_values += args @@ -320,7 +320,7 @@ module ActiveRecord # Same as #reorder but operates on relation in-place instead of copying. def reorder!(*args) # :nodoc: - enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) + @klass.enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) preprocess_order_args(args) self.reordering_value = true -- cgit v1.2.3 From 5180fe2cd8233169935065efe8762bd5d7b2709c Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Tue, 26 Sep 2017 09:29:24 -0600 Subject: allow table name and direction in string order arg --- .../lib/active_record/relation/calculations.rb | 31 ++++++---------------- .../lib/active_record/relation/query_methods.rb | 26 ++++++++++++++++-- 2 files changed, 32 insertions(+), 25 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 236d36e15f..75795fe493 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -180,14 +180,15 @@ module ActiveRecord end if has_include?(column_names.first) - construct_relation_for_association_calculations.pluck(*column_names) + relation = apply_join_dependency + relation.pluck(*column_names) else - enforce_raw_sql_whitelist(column_names) + enforce_raw_sql_whitelist(column_names, whitelist: allowed_pluck_columns) relation = spawn relation.select_values = column_names.map { |cn| @klass.respond_to_attribute?(cn) ? arel_attribute(cn) : cn } - result = klass.connection.select_all(relation.arel, nil, bound_attributes) + result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) } result.cast_values(klass.attribute_types) end end @@ -202,26 +203,10 @@ module ActiveRecord private - def _pluck(column_names, unsafe_raw) - unrecognized = column_names.reject do |cn| - @klass.respond_to_attribute?(cn) - end - - if loaded? && unrecognized.none? - records.pluck(*column_names) - elsif has_include?(column_names.first) - relation = apply_join_dependency - relation.pluck(*column_names) - elsif unsafe_raw || unrecognized.none? - relation = spawn - relation.select_values = column_names.map { |cn| - @klass.respond_to_attribute?(cn) ? arel_attribute(cn) : cn - } - result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) } - result.cast_values(klass.attribute_types) - else - raise ArgumentError, "Invalid column name: #{unrecognized}" - end + def allowed_pluck_columns + @klass.attribute_names_and_aliases.map do |name| + [name, "#{table_name}.#{name}"] + end.flatten end def has_include?(column_name) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index f3b44d19d6..094e5aa733 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -297,7 +297,11 @@ module ActiveRecord # Same as #order but operates on relation in-place instead of copying. def order!(*args) # :nodoc: - @klass.enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) + @klass.enforce_raw_sql_whitelist( + column_names_from_order_arguments(args), + whitelist: allowed_order_columns + ) + preprocess_order_args(args) self.order_values += args @@ -320,7 +324,11 @@ module ActiveRecord # Same as #reorder but operates on relation in-place instead of copying. def reorder!(*args) # :nodoc: - @klass.enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) + @klass.enforce_raw_sql_whitelist( + column_names_from_order_arguments(args), + whitelist: allowed_order_columns + ) + preprocess_order_args(args) self.reordering_value = true @@ -920,6 +928,20 @@ module ActiveRecord private + def allowed_order_columns + @klass.attribute_names_and_aliases.map do |name| + [name, "#{table_name}.#{name}"].map do |name| + [ + name, + "#{name} asc", + "#{name} ASC", + "#{name} desc", + "#{name} DESC" + ] + end + end.flatten + end + # Extract column names from arguments passed to #order or #reorder. def column_names_from_order_arguments(args) args.flat_map { |arg| arg.is_a?(Hash) ? arg.keys : arg } -- cgit v1.2.3 From 798557145c727b2abef2487783f02e57f04197c9 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Wed, 11 Oct 2017 13:16:57 -0600 Subject: try using regexes --- .../lib/active_record/relation/calculations.rb | 12 +++--------- .../lib/active_record/relation/query_methods.rb | 21 ++------------------- 2 files changed, 5 insertions(+), 28 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 75795fe493..d49472fc70 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -175,7 +175,7 @@ module ActiveRecord # See also #ids. # def pluck(*column_names) - if loaded? && (column_names.map(&:to_s) - @klass.attribute_names_and_aliases).empty? + if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty? return records.pluck(*column_names) end @@ -183,10 +183,10 @@ module ActiveRecord relation = apply_join_dependency relation.pluck(*column_names) else - enforce_raw_sql_whitelist(column_names, whitelist: allowed_pluck_columns) + enforce_raw_sql_whitelist(column_names) relation = spawn relation.select_values = column_names.map { |cn| - @klass.respond_to_attribute?(cn) ? arel_attribute(cn) : cn + @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn } result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) } result.cast_values(klass.attribute_types) @@ -203,12 +203,6 @@ module ActiveRecord private - def allowed_pluck_columns - @klass.attribute_names_and_aliases.map do |name| - [name, "#{table_name}.#{name}"] - end.flatten - end - def has_include?(column_name) eager_loading? || (includes_values.present? && column_name && column_name != :all) end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 094e5aa733..59a732168c 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -299,7 +299,7 @@ module ActiveRecord def order!(*args) # :nodoc: @klass.enforce_raw_sql_whitelist( column_names_from_order_arguments(args), - whitelist: allowed_order_columns + whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST ) preprocess_order_args(args) @@ -326,7 +326,7 @@ module ActiveRecord def reorder!(*args) # :nodoc: @klass.enforce_raw_sql_whitelist( column_names_from_order_arguments(args), - whitelist: allowed_order_columns + whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST ) preprocess_order_args(args) @@ -928,20 +928,6 @@ module ActiveRecord private - def allowed_order_columns - @klass.attribute_names_and_aliases.map do |name| - [name, "#{table_name}.#{name}"].map do |name| - [ - name, - "#{name} asc", - "#{name} ASC", - "#{name} desc", - "#{name} DESC" - ] - end - end.flatten - end - # Extract column names from arguments passed to #order or #reorder. def column_names_from_order_arguments(args) args.flat_map { |arg| arg.is_a?(Hash) ? arg.keys : arg } @@ -1097,9 +1083,6 @@ module ActiveRecord when Arel::Nodes::Ordering o.reverse when String - # ensure we're not dealing with string subclass (Eg. Arel::Nodes::SqlLiteral) - o = String.new(o) - if does_not_support_reverse?(o) raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically" end -- cgit v1.2.3 From ab03eb9f576312c75e61caaf9705a8ac5175c769 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Wed, 11 Oct 2017 13:56:42 -0600 Subject: use << instead of #concat in #reverse_sql_order because we might be working with Arel SQL literator which overrides #concat --- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 59a732168c..34723b0b4f 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1088,7 +1088,7 @@ module ActiveRecord end o.split(",").map! do |s| s.strip! - s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || s.concat(" DESC") + s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC") end else o -- cgit v1.2.3 From c711a27d29a3201ff47751a1d788f1e634186dd3 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Wed, 11 Oct 2017 14:15:47 -0600 Subject: convert order arg to string before checking if we can reverse it --- activerecord/lib/active_record/relation/query_methods.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 34723b0b4f..db7fe8123d 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1097,6 +1097,10 @@ module ActiveRecord end def does_not_support_reverse?(order) + # Account for String subclasses like Arel::Nodes::SqlLiteral that + # override methods like #count. + order = String.new(order) unless order.instance_of?(String) + # Uses SQL function with multiple arguments. (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) || # Uses "nulls first" like construction. -- cgit v1.2.3 From b76cc29865fb69389ffdb7bd9f8085aa86354f82 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Thu, 12 Oct 2017 11:48:48 -0600 Subject: deal with Array arguments to #order --- activerecord/lib/active_record/relation/query_methods.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index db7fe8123d..1dcd786498 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -930,7 +930,19 @@ module ActiveRecord # Extract column names from arguments passed to #order or #reorder. def column_names_from_order_arguments(args) - args.flat_map { |arg| arg.is_a?(Hash) ? arg.keys : arg } + args.flat_map do |arg| + case arg + when Hash + # Tag.order(id: :desc) + arg.keys + when Array + # Tag.order([Arel.sql("field(id, ?)"), [1, 3, 2]]) + arg.flatten + else + # Tag.order(:id) + arg + end + end end def assert_mutability! -- cgit v1.2.3 From 8ef71ac4a119a4c03d78db2372b41ddcc8a95035 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Wed, 18 Oct 2017 10:21:45 -0600 Subject: push order arg checks down to allow for binds --- .../lib/active_record/relation/query_methods.rb | 33 ++++------------------ 1 file changed, 6 insertions(+), 27 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 1dcd786498..cef0847651 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -297,11 +297,6 @@ module ActiveRecord # Same as #order but operates on relation in-place instead of copying. def order!(*args) # :nodoc: - @klass.enforce_raw_sql_whitelist( - column_names_from_order_arguments(args), - whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST - ) - preprocess_order_args(args) self.order_values += args @@ -324,11 +319,6 @@ module ActiveRecord # Same as #reorder but operates on relation in-place instead of copying. def reorder!(*args) # :nodoc: - @klass.enforce_raw_sql_whitelist( - column_names_from_order_arguments(args), - whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST - ) - preprocess_order_args(args) self.reordering_value = true @@ -928,23 +918,6 @@ module ActiveRecord private - # Extract column names from arguments passed to #order or #reorder. - def column_names_from_order_arguments(args) - args.flat_map do |arg| - case arg - when Hash - # Tag.order(id: :desc) - arg.keys - when Array - # Tag.order([Arel.sql("field(id, ?)"), [1, 3, 2]]) - arg.flatten - else - # Tag.order(:id) - arg - end - end - end - def assert_mutability! raise ImmutableRelation if @loaded raise ImmutableRelation if defined?(@arel) && @arel @@ -1146,6 +1119,12 @@ module ActiveRecord klass.send(:sanitize_sql_for_order, arg) end order_args.flatten! + + @klass.enforce_raw_sql_whitelist( + order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a }, + whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST + ) + validate_order_args(order_args) references = order_args.grep(String) -- cgit v1.2.3 From c3675f50d2e59b7fc173d7b332860c4b1a24a726 Mon Sep 17 00:00:00 2001 From: Lisa Ugray Date: Thu, 19 Oct 2017 12:45:07 -0400 Subject: Move Attribute and AttributeSet to ActiveModel Use these to back the attributes API. Stop automatically including ActiveModel::Dirty in ActiveModel::Attributes, and make it optional. --- activerecord/lib/active_record/relation/query_attribute.rb | 4 ++-- activerecord/lib/active_record/relation/query_methods.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb index fad08e2613..3532f28858 100644 --- a/activerecord/lib/active_record/relation/query_attribute.rb +++ b/activerecord/lib/active_record/relation/query_attribute.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require "active_record/attribute" +require "active_model/attribute" module ActiveRecord class Relation - class QueryAttribute < Attribute # :nodoc: + class QueryAttribute < ActiveModel::Attribute # :nodoc: def type_cast(value) value end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 897ff5c8af..9c5be4ad9b 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -930,7 +930,7 @@ module ActiveRecord arel.where(where_clause.ast) unless where_clause.empty? arel.having(having_clause.ast) unless having_clause.empty? if limit_value - limit_attribute = Attribute.with_cast_value( + limit_attribute = ActiveModel::Attribute.with_cast_value( "LIMIT".freeze, connection.sanitize_limit(limit_value), Type.default_value, @@ -938,7 +938,7 @@ module ActiveRecord arel.take(Arel::Nodes::BindParam.new(limit_attribute)) end if offset_value - offset_attribute = Attribute.with_cast_value( + offset_attribute = ActiveModel::Attribute.with_cast_value( "OFFSET".freeze, offset_value.to_i, Type.default_value, -- cgit v1.2.3 From df49896a1e67feea56062639c3cf51e8e0b12a51 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 10 Nov 2017 22:22:55 +0900 Subject: Ensure `apply_join_dependency` for subqueries in `from` and `where` Fixes #21577. --- .../lib/active_record/relation/predicate_builder/relation_handler.rb | 4 ++++ activerecord/lib/active_record/relation/query_methods.rb | 3 +++ 2 files changed, 7 insertions(+) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb index f51ea4fde0..c8bbfa5051 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb @@ -4,6 +4,10 @@ module ActiveRecord class PredicateBuilder class RelationHandler # :nodoc: def call(attribute, value) + if value.eager_loading? + value = value.send(:apply_join_dependency) + end + if value.select_values.empty? value = value.select(value.arel_attribute(value.klass.primary_key)) end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 9c5be4ad9b..34554450dd 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -963,6 +963,9 @@ module ActiveRecord name = from_clause.name case opts when Relation + if opts.eager_loading? + opts = opts.send(:apply_join_dependency) + end name ||= "subquery" opts.arel.as(name.to_s) else -- cgit v1.2.3 From b1e068bd30a88fbcc93a835edd6dbacf1d2d251c Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 10 Nov 2017 23:33:39 +0900 Subject: Consolidate duplicated `to_ary`/`to_a` definitions in `Relation` and `CollectionProxy` --- activerecord/lib/active_record/relation/delegation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 48af777b69..4863befec8 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -38,7 +38,7 @@ module ActiveRecord # may vary depending on the klass of a relation, so we create a subclass of Relation # for each different klass, and the delegations are compiled into that subclass only. - delegate :to_xml, :encode_with, :length, :each, :uniq, :to_ary, :join, + delegate :to_xml, :encode_with, :length, :each, :uniq, :join, :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of, :to_sentence, :to_formatted_s, :as_json, :shuffle, :split, :slice, :index, :rindex, to: :records -- cgit v1.2.3 From 4528dd6327f35d3139a48cbac9c9192f2253cbad Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 11 Nov 2017 03:54:10 +0900 Subject: Relation merging should keep joining order `joins_values.partition` will break joins values order. It should be kept as user intended order. Fixes #15488. --- activerecord/lib/active_record/relation/merger.rb | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index ebc72d28fd..b736b21525 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -112,22 +112,20 @@ module ActiveRecord if other.klass == relation.klass relation.joins!(*other.joins_values) else - joins_dependency, rest = other.joins_values.partition do |join| + alias_tracker = nil + joins_dependency = other.joins_values.map do |join| case join when Hash, Symbol, Array - true + alias_tracker ||= other.alias_tracker + ActiveRecord::Associations::JoinDependency.new( + other.klass, other.table, join, alias_tracker + ) else - false + join end end - join_dependency = ActiveRecord::Associations::JoinDependency.new( - other.klass, other.table, joins_dependency, other.alias_tracker - ) - - relation.joins! rest - - @relation = relation.joins join_dependency + relation.joins!(*joins_dependency) end end -- cgit v1.2.3 From 6acde9578fa55ceab8ef6520bbd5aab2a860d051 Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Sun, 5 Feb 2017 16:40:03 -0500 Subject: Do not use `Arel.star` when `ignored_columns` If there are any ignored columns, we will now list out all columns we want to be returned from the database. Includes a regression test. --- activerecord/lib/active_record/relation/query_methods.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 897ff5c8af..a8800e432a 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1035,6 +1035,8 @@ module ActiveRecord def build_select(arel) if select_values.any? arel.project(*arel_columns(select_values.uniq)) + elsif @klass.ignored_columns.any? + arel.project(*arel_columns(@klass.column_names)) else arel.project(table[Arel.star]) end -- cgit v1.2.3 From 67dbfc69f690f231d5b8257e03b8338af19c1d05 Mon Sep 17 00:00:00 2001 From: Nikolai B Date: Tue, 14 Nov 2017 14:12:40 +0000 Subject: Update `exists?` documentation Make it clear that `exists?` can be chained onto a relation --- activerecord/lib/active_record/relation/finder_methods.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 18566b5662..706fd57704 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -284,7 +284,7 @@ module ActiveRecord # * Hash - Finds the record that matches these +find+-style conditions # (such as {name: 'David'}). # * +false+ - Returns always +false+. - # * No args - Returns +false+ if the table is empty, +true+ otherwise. + # * No args - Returns +false+ if the relation is empty, +true+ otherwise. # # For more information about specifying conditions as a hash or array, # see the Conditions section in the introduction to ActiveRecord::Base. @@ -300,6 +300,7 @@ module ActiveRecord # Person.exists?(name: 'David') # Person.exists?(false) # Person.exists? + # Person.where(name: 'Spartacus', rating: 4).exists? def exists?(conditions = :none) if Base === conditions raise ArgumentError, <<-MSG.squish -- cgit v1.2.3 From ae032ec38463e923c0556fbdd28b9d9fced18b11 Mon Sep 17 00:00:00 2001 From: Nikita Misharin Date: Mon, 20 Nov 2017 16:31:46 +0300 Subject: Provide arguments to RecordNotFound --- activerecord/lib/active_record/relation/finder_methods.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 706fd57704..77c1367556 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -88,7 +88,7 @@ module ActiveRecord where(arg, *args).take! rescue ::RangeError raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value", - @klass.name) + @klass.name, @klass.primary_key) end # Gives a record (or N records if a parameter is supplied) without any implied @@ -339,7 +339,7 @@ module ActiveRecord if ids.nil? error = "Couldn't find #{name}".dup error << " with#{conditions}" if conditions - raise RecordNotFound.new(error, name) + raise RecordNotFound.new(error, name, key) elsif Array(ids).size == 1 error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}" raise RecordNotFound.new(error, name, key, ids) @@ -347,7 +347,7 @@ module ActiveRecord error = "Couldn't find all #{name.pluralize} with '#{key}': ".dup error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})." error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids - raise RecordNotFound.new(error, name, primary_key, ids) + raise RecordNotFound.new(error, name, key, ids) end end @@ -433,9 +433,12 @@ module ActiveRecord ids = ids.flatten.compact.uniq + model_name = @klass.name + case ids.size when 0 - raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" + error_message = "Couldn't find #{model_name} without an ID" + raise RecordNotFound.new(error_message, model_name, primary_key) when 1 result = find_one(ids.first) expects_array ? [ result ] : result @@ -443,7 +446,8 @@ module ActiveRecord find_some(ids) end rescue ::RangeError - raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID" + error_message = "Couldn't find #{model_name} with an out of range ID" + raise RecordNotFound.new(error_message, model_name, primary_key, ids) end def find_one(id) -- cgit v1.2.3 From 2c5acb20dd944cc061a03e8d1ca6d3e469c0d46e Mon Sep 17 00:00:00 2001 From: suginoy Date: Tue, 28 Nov 2017 19:36:01 +0900 Subject: Update docs `ActiveRecord::FinderMethods#find` ref https://github.com/rails/rails/pull/22653 --- activerecord/lib/active_record/relation/finder_methods.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 77c1367556..ff06ecbee1 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -18,9 +18,10 @@ module ActiveRecord # Person.find([1]) # returns an array for the object with ID = 1 # Person.where("administrator = 1").order("created_on DESC").find(1) # - # NOTE: The returned records may not be in the same order as the ids you - # provide since database rows are unordered. You will need to provide an explicit QueryMethods#order - # option if you want the results to be sorted. + # NOTE: The returned records are in the same order as the ids you provide. + # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where + # method and provide an explicit ActiveRecord::QueryMethods#order option. + # But ActiveRecord::QueryMethods#where method doesn't raise ActiveRecord::RecordNotFound. # # ==== Find with lock # -- cgit v1.2.3 From 6a8ce7416d6615a13ee5c4b9f6bcd91cc5adef4d Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 8 Dec 2017 02:37:02 +0900 Subject: Fix `scope_for_create` to do not lose polymorphic associations This regression was caused at 213796fb due to polymorphic predicates are combined by `Arel::Nodes::And`. But I'd like to keep that combined because it would help inverting polymorphic predicates correctly (e9ba12f7), and we can collect equality nodes regardless of combined by `Arel::Nodes::And` (`a AND (b AND c) AND d` == `a AND b AND c AND d`). This change fixes the regression to collect equality nodes in `Arel::Nodes::And` as well. Fixes #31338. --- .../lib/active_record/relation/where_clause.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record/relation') diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index 752bb38481..a502713e56 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -47,7 +47,7 @@ module ActiveRecord end def to_h(table_name = nil) - equalities = predicates.grep(Arel::Nodes::Equality) + equalities = equalities(predicates) if table_name equalities = equalities.select do |node| node.left.relation.name == table_name @@ -90,6 +90,20 @@ module ActiveRecord end private + def equalities(predicates) + equalities = [] + + predicates.each do |node| + case node + when Arel::Nodes::Equality + equalities << node + when Arel::Nodes::And + equalities.concat equalities(node.children) + end + end + + equalities + end def predicates_unreferenced_by(other) predicates.reject do |n| @@ -121,7 +135,7 @@ module ActiveRecord end def except_predicates(columns) - self.predicates.reject do |node| + predicates.reject do |node| case node when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) -- cgit v1.2.3