From 3360742396a00c9e2ff9838373788bed432d5ea7 Mon Sep 17 00:00:00 2001 From: Devin Christensen Date: Wed, 12 Apr 2017 14:45:10 -0600 Subject: Switch to LIFO for the connection pool Using a FIFO for the connection pool can lead to issues when there are upstream components (pgbouncer, haproxy, etc.) that terminate connections that are idle after a period of time. Switching to a LIFO reduces the probability that a thread will checkout a connection that is about to be closed by an idle timeout in an upstream component. --- .../lib/active_record/connection_adapters/abstract/connection_pool.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 53dbbd8c21..ea76ff983e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -80,7 +80,7 @@ module ActiveRecord # * private methods that require being called in a +synchronize+ blocks # are now explicitly documented class ConnectionPool - # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool + # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool # with which it shares a Monitor. But could be a generic Queue. # # The Queue in stdlib's 'thread' could replace this class except @@ -111,7 +111,7 @@ module ActiveRecord # Add +element+ to the queue. Never blocks. def add(element) synchronize do - @queue.push element + @queue.unshift element @cond.signal end end -- cgit v1.2.3 From 6116d7bc052839646f448b8403a7287f52b97ed7 Mon Sep 17 00:00:00 2001 From: Devin Christensen Date: Thu, 13 Apr 2017 13:56:42 -0600 Subject: Improve documentation and add test --- .../connection_adapters/abstract/connection_pool.rb | 11 ++++------- activerecord/test/cases/connection_pool_test.rb | 8 ++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index ea76ff983e..3dc265622d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -80,11 +80,8 @@ module ActiveRecord # * private methods that require being called in a +synchronize+ blocks # are now explicitly documented class ConnectionPool - # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool - # with which it shares a Monitor. But could be a generic Queue. - # - # The Queue in stdlib's 'thread' could replace this class except - # stdlib's doesn't support waiting with a timeout. + # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool + # with which it shares a Monitor. class Queue def initialize(lock = Monitor.new) @lock = lock @@ -111,7 +108,7 @@ module ActiveRecord # Add +element+ to the queue. Never blocks. def add(element) synchronize do - @queue.unshift element + @queue.push element @cond.signal end end @@ -173,7 +170,7 @@ module ActiveRecord # Removes and returns the head of the queue if possible, or +nil+. def remove - @queue.shift + @queue.pop end # Remove and return the head the queue if the number of diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 7e88c9cf7a..46081cc13d 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -203,6 +203,14 @@ module ActiveRecord end.join end + def test_checkout_order_is_fifo + conn1 = @pool.checkout + conn2 = @pool.checkout + @pool.checkin conn1 + @pool.checkin conn2 + assert_equal [conn2, conn1], 2.times.map { @pool.checkout } + end + # The connection pool is "fair" if threads waiting for # connections receive them in the order in which they began # waiting. This ensures that we don't timeout one HTTP request -- cgit v1.2.3 From 47d678d9b2ab6d3e888691e8327e657ff0b5dcb0 Mon Sep 17 00:00:00 2001 From: Devin Christensen Date: Thu, 13 Apr 2017 14:05:39 -0600 Subject: Fix typos --- .../lib/active_record/connection_adapters/abstract/connection_pool.rb | 2 +- activerecord/test/cases/connection_pool_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 3dc265622d..9f660a419d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -80,7 +80,7 @@ module ActiveRecord # * private methods that require being called in a +synchronize+ blocks # are now explicitly documented class ConnectionPool - # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool + # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool # with which it shares a Monitor. class Queue def initialize(lock = Monitor.new) diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 46081cc13d..790eed6007 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -203,7 +203,7 @@ module ActiveRecord end.join end - def test_checkout_order_is_fifo + def test_checkout_order_is_lifo conn1 = @pool.checkout conn2 = @pool.checkout @pool.checkin conn1 -- cgit v1.2.3 From 11e32c1d841c08bd85842eb059fbf30536e804dc Mon Sep 17 00:00:00 2001 From: Eugene Kenny Date: Mon, 24 Apr 2017 23:26:13 +0100 Subject: Enable query cache on all connection pools Since the query cache no longer eagerly checks out a connection, we can enable it on all connection pools at the start of every request, and it will only take effect for requests that actually use those pools. --- activerecord/lib/active_record/query_cache.rb | 15 +++++++++------ activerecord/test/cases/query_cache_test.rb | 9 +++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index ec246e97bc..92dd52b068 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -24,16 +24,19 @@ module ActiveRecord end def self.run - caching_pool = ActiveRecord::Base.connection_pool - caching_was_enabled = caching_pool.query_cache_enabled + ActiveRecord::Base.connection_handler.connection_pool_list.map do |pool| + caching_was_enabled = pool.query_cache_enabled - caching_pool.enable_query_cache! + pool.enable_query_cache! - [caching_pool, caching_was_enabled] + [pool, caching_was_enabled] + end end - def self.complete((caching_pool, caching_was_enabled)) - caching_pool.disable_query_cache! unless caching_was_enabled + def self.complete(caching_pools) + caching_pools.each do |pool, caching_was_enabled| + pool.disable_query_cache! unless caching_was_enabled + end ActiveRecord::Base.connection_handler.connection_pool_list.each do |pool| pool.release_connection if pool.active_connection? && !pool.connection.transaction_open? diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 494663eb04..210199c977 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -426,6 +426,15 @@ class QueryCacheTest < ActiveRecord::TestCase end end + def test_query_cache_is_enabled_on_all_connection_pools + middleware { + ActiveRecord::Base.connection_handler.connection_pool_list.each do |pool| + assert pool.query_cache_enabled + assert pool.connection.query_cache_enabled + end + }.call({}) + end + private def middleware(&app) executor = Class.new(ActiveSupport::Executor) -- cgit v1.2.3 From 87598c8c80f4cbeef114267d75c46f1c87ca057a Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Mon, 2 Oct 2017 15:46:30 +0900 Subject: Make automatically synchronize test schema work inside engine In Rails engine, migration files are in under `db/migrate` of engine. Therefore, when rake task is executed in engine, `db/migrate` is automatically added to `DatabaseTasks.migrations_paths`. https://github.com/rails/rails/blob/a18cf23a9cbcbeed61e8049442640c7153e0a8fb/activerecord/lib/active_record/railtie.rb#L39..L43 However, if execute the rake task under dummy app, migration files will not be loaded because engine's migration path setting process is not called. Therefore, in order to load migration files correctly, it is necessary to execute rake task under engine. Fixes #30765 --- activerecord/lib/active_record/migration.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 8845e26ab7..ec8119e5da 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -581,7 +581,8 @@ module ActiveRecord def load_schema_if_pending! if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations? # Roundtrip to Rake to allow plugins to hook into database initialization. - FileUtils.cd Rails.root do + root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root + FileUtils.cd(root) do current_config = Base.connection_config Base.clear_all_connections! system("bin/rails db:test:prepare") -- cgit v1.2.3 From 71b15603fddd85602813d1c96857e2674b6075d0 Mon Sep 17 00:00:00 2001 From: Felipe Oliveira Date: Tue, 3 Oct 2017 08:41:12 -0300 Subject: Add update_only example to AR nested attributes doc [ci_skip] --- activerecord/lib/active_record/nested_attributes.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 435c81c153..fa20bce3a9 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -63,6 +63,18 @@ module ActiveRecord # member.update params[:member] # member.avatar.icon # => 'sad' # + # If you want to update the current avatar without providing the id, you must add :update_only option. + # + # class Member < ActiveRecord::Base + # has_one :avatar + # accepts_nested_attributes_for :avatar, update_only: true + # end + # + # params = { member: { avatar_attributes: { icon: 'sad' } } } + # member.update params[:member] + # member.avatar.id # => 2 + # member.avatar.icon # => 'sad' + # # By default you will only be able to set and update attributes on the # associated model. If you want to destroy the associated model through the # attributes hash, you have to enable it first using the -- cgit v1.2.3 From c4867b88f6eed16230a90077b82ad02293ea45b3 Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Fri, 25 Aug 2017 23:05:21 +0300 Subject: Raise error if unsupported charset for mysql `blog$ bin/rails db:create` Before: ``` Couldn't create database for {"adapter"=>"mysql2", "encoding"=>"utf42", "pool"=>5, "username"=>"root", "password"=>nil, "socket"=>"/var/run/mysqld/mysqld.sock", "database"=>"blog_development"}, {:charset=>"utf42"} (If you set the charset manually, make sure you have a matching collation) Created database 'blog_development' Couldn't create database for {"adapter"=>"mysql2", "encoding"=>"utf42", "pool"=>5, "username"=>"root", "password"=>nil, "socket"=>"/var/run/mysqld/mysqld.sock", "database"=>"blog_test"}, {:charset=>"utf42"} (If you set the charset manually, make sure you have a matching collation) Created database 'blog_test' ``` After: ``` Unsupported charset: '"utf42"' Couldn't create database for {"adapter"=>"mysql2", "encoding"=>"utf42", "pool"=>5, "username"=>"root", "password"=>nil, "socket"=>"/var/run/mysqld/mysqld.sock", "database"=>"blog_development"} rails aborted! Mysql2::Error: Unsupported charset: '"utf42"' ... (stack trace) ... bin/rails:4:in `
' Tasks: TOP => db:create (See full trace by running task with --trace) ``` Closes #29683 Related to #27398 --- activerecord/lib/active_record/tasks/mysql_database_tasks.rb | 4 +--- activerecord/test/cases/tasks/mysql_rake_test.rb | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index 2f91884f59..d6a46f7743 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -31,9 +31,7 @@ module ActiveRecord end establish_connection configuration else - $stderr.puts error.inspect - $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}" - $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration["encoding"] + raise end end diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index 98fe24baa0..6c9b0040ad 100644 --- a/activerecord/test/cases/tasks/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -156,12 +156,12 @@ if current_adapter?(:Mysql2Adapter) ActiveRecord::Tasks::DatabaseTasks.create @configuration end - def test_sends_output_to_stderr_when_other_errors + def test_raises_error_when_other_errors @error.stubs(:errno).returns(42) - $stderr.expects(:puts).at_least_once.returns(nil) - - ActiveRecord::Tasks::DatabaseTasks.create @configuration + assert_raises(Mysql2::Error) do + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end end private -- cgit v1.2.3 From a37d03c4b08451765acc721b51a4b5b7eab7e8cc Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Sat, 26 Aug 2017 00:06:10 +0300 Subject: Simplify implementation of `MySQLDatabaseTasks` Don't process MySQL ERROR 1045, raise error instead Make behavior of `MySQLDatabaseTasks` more consistent with behavior of `PostgreSQLDatabaseTasks` --- .../lib/active_record/railties/jdbcmysql_error.rb | 18 ------ .../active_record/tasks/mysql_database_tasks.rb | 45 ------------- activerecord/test/cases/tasks/mysql_rake_test.rb | 75 +--------------------- 3 files changed, 3 insertions(+), 135 deletions(-) delete mode 100644 activerecord/lib/active_record/railties/jdbcmysql_error.rb (limited to 'activerecord') diff --git a/activerecord/lib/active_record/railties/jdbcmysql_error.rb b/activerecord/lib/active_record/railties/jdbcmysql_error.rb deleted file mode 100644 index 72c75ddd52..0000000000 --- a/activerecord/lib/active_record/railties/jdbcmysql_error.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -#FIXME Remove if ArJdbcMysql will give. -module ArJdbcMySQL #:nodoc: - class Error < StandardError #:nodoc: - attr_accessor :error_number, :sql_state - - def initialize(msg) - super - @error_number = nil - @sql_state = nil - end - - # Mysql gem compatibility - alias_method :errno, :error_number - alias_method :error, :message - end -end diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index d6a46f7743..e697fa6def 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -3,8 +3,6 @@ module ActiveRecord module Tasks # :nodoc: class MySQLDatabaseTasks # :nodoc: - ACCESS_DENIED_ERROR = 1045 - delegate :connection, :establish_connection, to: ActiveRecord::Base def initialize(configuration) @@ -21,18 +19,6 @@ module ActiveRecord else raise end - rescue error_class => error - if error.respond_to?(:errno) && error.errno == ACCESS_DENIED_ERROR - $stdout.print error.message - establish_connection root_configuration_without_database - connection.create_database configuration["database"], creation_options - if configuration["username"] != "root" - connection.execute grant_statement.gsub(/\s+/, " ").strip - end - establish_connection configuration - else - raise - end end def drop @@ -97,37 +83,6 @@ module ActiveRecord end end - def error_class - if configuration["adapter"].include?("jdbc") - require "active_record/railties/jdbcmysql_error" - ArJdbcMySQL::Error - elsif defined?(Mysql2) - Mysql2::Error - else - StandardError - end - end - - def grant_statement - <<-SQL -GRANT ALL PRIVILEGES ON `#{configuration['database']}`.* - TO '#{configuration['username']}'@'localhost' -IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION; - SQL - end - - def root_configuration_without_database - configuration_without_database.merge( - "username" => "root", - "password" => root_password - ) - end - - def root_password - $stdout.print "Please provide the root password for your MySQL installation\n>" - $stdin.gets.strip - end - def prepare_command_options args = { "host" => "--host", diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index 6c9b0040ad..047153e7cc 100644 --- a/activerecord/test/cases/tasks/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -75,7 +75,7 @@ if current_adapter?(:Mysql2Adapter) end end - class MysqlDBCreateAsRootTest < ActiveRecord::TestCase + class MysqlDBCreateWithInvalidPermissionsTest < ActiveRecord::TestCase def setup @connection = stub("Connection", create_database: true) @error = Mysql2::Error.new("Invalid permissions") @@ -86,13 +86,8 @@ if current_adapter?(:Mysql2Adapter) "password" => "wossname" } - $stdin.stubs(:gets).returns("secret\n") - $stdout.stubs(:print).returns(nil) - @error.stubs(:errno).returns(1045) ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection). - raises(@error). - then.returns(true) + ActiveRecord::Base.stubs(:establish_connection).raises(@error) $stdout, @original_stdout = StringIO.new, $stdout $stderr, @original_stderr = StringIO.new, $stderr @@ -102,75 +97,11 @@ if current_adapter?(:Mysql2Adapter) $stdout, $stderr = @original_stdout, @original_stderr end - def test_root_password_is_requested - assert_permissions_granted_for("pat") - $stdin.expects(:gets).returns("secret\n") - - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end - - def test_connection_established_as_root - assert_permissions_granted_for("pat") - ActiveRecord::Base.expects(:establish_connection).with( - "adapter" => "mysql2", - "database" => nil, - "username" => "root", - "password" => "secret" - ) - - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end - - def test_database_created_by_root - assert_permissions_granted_for("pat") - @connection.expects(:create_database). - with("my-app-db", {}) - - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end - - def test_grant_privileges_for_normal_user - assert_permissions_granted_for("pat") - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end - - def test_do_not_grant_privileges_for_root_user - @configuration["username"] = "root" - @configuration["password"] = "" - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end - - def test_connection_established_as_normal_user - assert_permissions_granted_for("pat") - ActiveRecord::Base.expects(:establish_connection).returns do - ActiveRecord::Base.expects(:establish_connection).with( - "adapter" => "mysql2", - "database" => "my-app-db", - "username" => "pat", - "password" => "secret" - ) - - raise @error - end - - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end - - def test_raises_error_when_other_errors - @error.stubs(:errno).returns(42) - + def test_raises_error assert_raises(Mysql2::Error) do ActiveRecord::Tasks::DatabaseTasks.create @configuration end end - - private - - def assert_permissions_granted_for(db_user) - db_name = @configuration["database"] - db_password = @configuration["password"] - @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON `#{db_name}`.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;") - end end class MySQLDBDropTest < ActiveRecord::TestCase -- cgit v1.2.3 From ae6361b88c58c5dc1e86833e0299d031bb573fe1 Mon Sep 17 00:00:00 2001 From: Bogdan Gusiev Date: Tue, 7 Nov 2017 17:23:56 +0200 Subject: Set counter caches to correct values in fixtures --- .../cases/associations/cascaded_eager_loading_test.rb | 2 +- .../cases/associations/has_many_associations_test.rb | 2 +- activerecord/test/cases/relations_test.rb | 18 +++++++++--------- activerecord/test/fixtures/other_posts.yml | 1 + activerecord/test/fixtures/posts.yml | 8 ++++++++ 5 files changed, 20 insertions(+), 11 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index e69cfe5e52..829e12fbc8 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -37,7 +37,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase def test_eager_association_loading_with_hmt_does_not_table_name_collide_when_joining_associations authors = Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a - assert_equal 1, assert_no_queries { authors.size } + assert_equal 3, assert_no_queries { authors.size } assert_equal 10, assert_no_queries { authors[0].comments.size } end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 6bd11a5d81..4ca11af791 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -831,7 +831,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_scoped_grouped_having - assert_equal 1, authors(:david).popular_grouped_posts.length + assert_equal 2, authors(:david).popular_grouped_posts.length assert_equal 0, authors(:mary).popular_grouped_posts.length end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 72433d1e8e..844be0d0bf 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -927,13 +927,13 @@ class RelationTest < ActiveRecord::TestCase assert_equal 11, posts.count(:all) assert_equal 11, posts.count(:id) - assert_equal 1, posts.where("comments_count > 1").count - assert_equal 9, posts.where(comments_count: 0).count + assert_equal 3, posts.where("comments_count > 1").count + assert_equal 6, posts.where(comments_count: 0).count end def test_count_with_block posts = Post.all - assert_equal 10, posts.count { |p| p.comments_count.even? } + assert_equal 8, posts.count { |p| p.comments_count.even? } end def test_count_on_association_relation @@ -950,10 +950,10 @@ class RelationTest < ActiveRecord::TestCase def test_count_with_distinct posts = Post.all - assert_equal 3, posts.distinct(true).count(:comments_count) + assert_equal 4, posts.distinct(true).count(:comments_count) assert_equal 11, posts.distinct(false).count(:comments_count) - assert_equal 3, posts.distinct(true).select(:comments_count).count + assert_equal 4, posts.distinct(true).select(:comments_count).count assert_equal 11, posts.distinct(false).select(:comments_count).count end @@ -992,7 +992,7 @@ class RelationTest < ActiveRecord::TestCase best_posts = posts.where(comments_count: 0) best_posts.load # force load - assert_no_queries { assert_equal 9, best_posts.size } + assert_no_queries { assert_equal 6, best_posts.size } end def test_size_with_limit @@ -1003,7 +1003,7 @@ class RelationTest < ActiveRecord::TestCase best_posts = posts.where(comments_count: 0) best_posts.load # force load - assert_no_queries { assert_equal 9, best_posts.size } + assert_no_queries { assert_equal 6, best_posts.size } end def test_size_with_zero_limit @@ -1026,7 +1026,7 @@ class RelationTest < ActiveRecord::TestCase def test_count_complex_chained_relations posts = Post.select("comments_count").where("id is not null").group("author_id").where("comments_count > 0") - expected = { 1 => 2 } + expected = { 1 => 4, 2 => 1 } assert_equal expected, posts.count end @@ -1781,7 +1781,7 @@ class RelationTest < ActiveRecord::TestCase end test "arel_attribute respects a custom table" do - assert_equal [posts(:welcome)], custom_post_relation.ranked_by_comments.limit_by(1).to_a + assert_equal [posts(:sti_comments)], custom_post_relation.ranked_by_comments.limit_by(1).to_a end test "alias_tracker respects a custom table" do diff --git a/activerecord/test/fixtures/other_posts.yml b/activerecord/test/fixtures/other_posts.yml index 39ff763547..3e11a33802 100644 --- a/activerecord/test/fixtures/other_posts.yml +++ b/activerecord/test/fixtures/other_posts.yml @@ -5,3 +5,4 @@ second_welcome: author_id: 1 title: Welcome to the another weblog body: It's really nice today + comments_count: 1 diff --git a/activerecord/test/fixtures/posts.yml b/activerecord/test/fixtures/posts.yml index 86d46f753a..8d7e1e0ae7 100644 --- a/activerecord/test/fixtures/posts.yml +++ b/activerecord/test/fixtures/posts.yml @@ -28,6 +28,7 @@ sti_comments: author_id: 1 title: sti comments body: hello + comments_count: 5 type: Post sti_post_and_comments: @@ -35,6 +36,7 @@ sti_post_and_comments: author_id: 1 title: sti me body: hello + comments_count: 2 type: StiPost sti_habtm: @@ -50,6 +52,8 @@ eager_other: title: eager loading with OR'd conditions body: hello type: Post + comments_count: 1 + tags_count: 3 misc_by_bob: id: 8 @@ -57,6 +61,7 @@ misc_by_bob: title: misc post by bob body: hello type: Post + tags_count: 1 misc_by_mary: id: 9 @@ -64,6 +69,7 @@ misc_by_mary: title: misc post by mary body: hello type: Post + tags_count: 1 other_by_bob: id: 10 @@ -71,6 +77,7 @@ other_by_bob: title: other post by bob body: hello type: Post + tags_count: 1 other_by_mary: id: 11 @@ -78,3 +85,4 @@ other_by_mary: title: other post by mary body: hello type: Post + tags_count: 1 -- 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/attribute_methods.rb | 16 ++ activerecord/lib/active_record/core.rb | 9 ++ activerecord/lib/active_record/querying.rb | 21 ++- .../lib/active_record/relation/calculations.rb | 42 +++-- .../lib/active_record/relation/query_methods.rb | 49 ++++++ activerecord/test/cases/unsafe_raw_sql_test.rb | 177 +++++++++++++++++++++ 6 files changed, 293 insertions(+), 21 deletions(-) create mode 100644 activerecord/test/cases/unsafe_raw_sql_test.rb (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 23d2aef214..fa0d79ba5f 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -167,6 +167,22 @@ module ActiveRecord end end + # Can the given name be treated as a column name? Returns true if name + # is attribute or attribute alias. + # + # class Person < ActiveRecord::Base + # end + # + # Person.respond_to_attribute?(:name) + # # => true + # + # Person.respond_to_attribute?("foo") + # # => false + def respond_to_attribute?(name) + name = name.to_s + attribute_names.include?(name) || attribute_aliases.include?(name) + end + # Returns true if the given attribute exists, otherwise false. # # class Person < ActiveRecord::Base diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 0f7a503c90..b1e3f71dfe 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -76,6 +76,15 @@ module ActiveRecord # scope being ignored is error-worthy, rather than a warning. mattr_accessor :error_on_ignored_order, instance_writer: false, default: false + # :singleton-method: + # Specify the behavior for unsafe raw query methods. Values are as follows + # enabled - Unsafe raw SQL can be passed to query methods. + # deprecated - Warnings are logged when unsafe raw SQL is passed to + # query methods. + # disabled - Unsafe raw SQL passed to query methods results in + # ArguementError. + mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :enabled + ## # :singleton-method: # Specify whether or not to use timestamps for migration versions diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 3996d5661f..0233835bcf 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -2,18 +2,25 @@ module ActiveRecord module Querying - delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :none?, :one?, to: :all - delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all + delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, + :any?, :many?, :none?, :one?, to: :all + delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, + :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, + :second_to_last, :second_to_last!, to: :all delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all - delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all + delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, + to: :all delegate :find_by, :find_by!, to: :all delegate :destroy_all, :delete_all, :update_all, to: :all delegate :find_each, :find_in_batches, :in_batches, to: :all - delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or, - :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending, - :having, :create_with, :distinct, :references, :none, :unscope, :merge, to: :all + delegate :select, :group, :order, :unsafe_raw_order, :except, :reorder, + :unsafe_raw_reorder, :limit, :offset, :joins, :left_joins, + :left_outer_joins, :or, :where, :rewhere, :preload, :eager_load, + :includes, :from, :lock, :readonly, :extending, :having, + :create_with, :distinct, :references, :none, :unscope, :merge, + to: :all delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all - delegate :pluck, :ids, to: :all + delegate :pluck, :unsafe_raw_pluck, :ids, to: :all # Executes a custom SQL query against your database and returns all the results. The results will # be returned as an array with columns requested encapsulated as attributes of the model you call 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. diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb new file mode 100644 index 0000000000..d05f0f12d9 --- /dev/null +++ b/activerecord/test/cases/unsafe_raw_sql_test.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +require "cases/helper" +require "models/post" +require "models/comment" + +class UnsafeRawSqlTest < ActiveRecord::TestCase + fixtures :posts, :comments + + test "order: allows string column name" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.order("title").pluck(:id) + end + + assert_equal enabled, disabled + assert_equal disabled, Post.unsafe_raw_order("title").pluck(:id) + end + + test "order: allows symbol column name" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.order(:title).pluck(:id) + end + + assert_equal enabled, disabled + assert_equal disabled, Post.unsafe_raw_order(:title).pluck(:id) + end + + test "order: allows downcase symbol direction" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.order(title: :asc).pluck(:id) + end + + assert_equal enabled, disabled + assert_equal disabled, Post.unsafe_raw_order(title: :asc).pluck(:id) + end + + test "order: allows upcase symbol direction" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.order(title: :ASC).pluck(:id) + end + + assert_equal enabled, disabled + assert_equal disabled, Post.unsafe_raw_order(title: :ASC).pluck(:id) + end + + test "order: allows string direction" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.order(title: "asc").pluck(:id) + end + + assert_equal enabled, disabled + assert_equal disabled, Post.unsafe_raw_order(title: "asc").pluck(:id) + end + + test "order: allows multiple columns" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.order(:author_id, :title).pluck(:id) + end + + assert_equal enabled, disabled + assert_equal disabled, Post.unsafe_raw_order(:author_id, :title).pluck(:id) + end + + test "order: allows mixed" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.order(:author_id, title: :asc).pluck(:id) + end + + assert_equal enabled, disabled + assert_equal disabled, Post.unsafe_raw_order(:author_id, title: :asc).pluck(:id) + end + + test "order: disallows invalid column name" do + with_config(:disabled) do + assert_raises(ArgumentError) do + Post.order("title asc").pluck(:id) + end + end + end + + test "order: disallows invalid direction" do + with_config(:disabled) do + assert_raises(ArgumentError) do + Post.order(title: :foo).pluck(:id) + end + end + end + + test "order: disallows invalid column with direction" do + with_config(:disabled) do + assert_raises(ArgumentError) do + Post.order(foo: :asc).pluck(:id) + end + end + end + + test "pluck: allows string column name" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.pluck("title") + end + + assert_equal enabled, disabled + assert_equal disabled, Post.unsafe_raw_pluck("title") + end + + test "pluck: allows symbol column name" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.pluck(:title) + end + + assert_equal enabled, disabled + assert_equal disabled, Post.unsafe_raw_pluck(:title) + end + + test "pluck: allows multiple column names" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.pluck(:title, :id) + end + + assert_equal enabled, disabled + assert_equal disabled, Post.unsafe_raw_pluck(:title, :id) + end + + test "pluck: allows column names with includes" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.includes(:comments).pluck(:title, :id) + end + + assert_equal enabled, disabled + assert_equal disabled, Post.includes(:comments).unsafe_raw_pluck(:title, :id) + end + + test "pluck: allows auto-generated attributes" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.pluck(:tags_count) + end + + assert_equal enabled, disabled + assert_equal disabled, Post.unsafe_raw_pluck(:tags_count) + end + + test "pluck: disallows invalid column name" do + with_config(:disabled) do + assert_raises(ArgumentError) do + Post.pluck("length(title)") + end + end + end + + test "pluck: disallows invalid column name amongst valid names" do + with_config(:disabled) do + assert_raises(ArgumentError) do + Post.pluck(:title, "length(title)") + end + end + end + + test "pluck: disallows invalid column names with includes" do + with_config(:disabled) do + assert_raises(ArgumentError) do + Post.includes(:comments).pluck(:title, "length(title)") + end + end + end + + def with_configs(*new_values, &blk) + new_values.map { |nv| with_config(nv, &blk) } + end + + def with_config(new_value, &blk) + old_value = ActiveRecord::Base.allow_unsafe_raw_sql + ActiveRecord::Base.allow_unsafe_raw_sql = new_value + blk.call + ensure + ActiveRecord::Base.allow_unsafe_raw_sql = old_value + end +end -- 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/attribute_methods.rb | 38 +++++++++++- activerecord/lib/active_record/errors.rb | 25 ++++++++ activerecord/lib/active_record/querying.rb | 21 +++---- .../lib/active_record/relation/calculations.rb | 20 +++++-- .../lib/active_record/relation/query_methods.rb | 54 +++-------------- activerecord/test/cases/calculations_test.rb | 24 ++++---- activerecord/test/cases/relation/merging_test.rb | 8 +-- activerecord/test/cases/unsafe_raw_sql_test.rb | 70 ++++++++++++++++------ 8 files changed, 159 insertions(+), 101 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index fa0d79ba5f..b3d3c0559f 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -167,6 +167,30 @@ module ActiveRecord end end + def enforce_raw_sql_whitelist(args, whitelist: attribute_names_and_aliases) # :nodoc: + return if allow_unsafe_raw_sql == :enabled + + unexpected = args.reject do |arg| + whitelist.include?(arg.to_s) || + arg.kind_of?(Arel::Node) || arg.is_a?(Arel::Nodes::SqlLiteral) + end + + return if unexpected.none? + + if allow_unsafe_raw_sql == :deprecated + ActiveSupport::Deprecation.warn( + "Dangerous query method used with non-attribute argument(s): " + + "#{unexpected.map(&:inspect).join(", ")}. Non-argument " + + "arguments will be disallowed in Rails 5.3." + ) + else + raise(ActiveRecord::UnknownAttributeReference, + "Query method called with non-attribute argument(s): " + + unexpected.map(&:inspect).join(", ") + ) + end + end + # Can the given name be treated as a column name? Returns true if name # is attribute or attribute alias. # @@ -178,7 +202,7 @@ module ActiveRecord # # Person.respond_to_attribute?("foo") # # => false - def respond_to_attribute?(name) + def respond_to_attribute?(name) # :nodoc: name = name.to_s attribute_names.include?(name) || attribute_aliases.include?(name) end @@ -214,6 +238,18 @@ module ActiveRecord ConnectionAdapters::NullColumn.new(name) end end + + # An Array of String attribute names and aliases for accessing those + # attributes. + # + # class Person < ActiveRecord::Base + # end + # + # Person.attribute_names_and_aliases + # # => ["id", "created_at", "updated_at", "name", "age"] + def attribute_names_and_aliases # :nodoc: + attribute_names + attribute_aliases.keys + end end # A Person object with a name attribute can ask person.respond_to?(:name), diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 9ef3316393..3c039eef82 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -339,4 +339,29 @@ module ActiveRecord # Wait time value is set by innodb_lock_wait_timeout. class TransactionTimeout < StatementInvalid end + + # UnknownAttributeReference is raised when an unknown and potentially unsafe + # value is passed to a query method when allow_unsafe_raw_sql is set to + # :disabled. For example, passing a non column name value to a relation's + # #order method might cause this exception. + # + # When working around this exception, caution should be taken to avoid SQL + # injection vulnerabilities when passing user-provided values to query + # methods. Known-safe values can be passed to query methods by wrapping them + # in Arel.sql. + # + # For example, with allow_unsafe_raw_sql set to :disabled, the following + # code would raise this exception: + # + # Post.order("length(title)").first + # + # The desired result can be accomplished by wrapping the known-safe string + # in Arel.sql: + # + # Post.order(Arel.sql("length(title)")).first + # + # Again, such a workaround should *not* be used when passing user-provided + # values, such as request parameters or model attributes to query methods. + class UnknownAttributeReference < ActiveRecordError + end end diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 0233835bcf..3996d5661f 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -2,25 +2,18 @@ module ActiveRecord module Querying - delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, - :any?, :many?, :none?, :one?, to: :all - delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, - :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, - :second_to_last, :second_to_last!, to: :all + delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :none?, :one?, to: :all + delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all - delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, - to: :all + delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all delegate :find_by, :find_by!, to: :all delegate :destroy_all, :delete_all, :update_all, to: :all delegate :find_each, :find_in_batches, :in_batches, to: :all - delegate :select, :group, :order, :unsafe_raw_order, :except, :reorder, - :unsafe_raw_reorder, :limit, :offset, :joins, :left_joins, - :left_outer_joins, :or, :where, :rewhere, :preload, :eager_load, - :includes, :from, :lock, :readonly, :extending, :having, - :create_with, :distinct, :references, :none, :unscope, :merge, - to: :all + delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or, + :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending, + :having, :create_with, :distinct, :references, :none, :unscope, :merge, to: :all delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all - delegate :pluck, :unsafe_raw_pluck, :ids, to: :all + delegate :pluck, :ids, to: :all # Executes a custom SQL query against your database and returns all the results. The results will # be returned as an array with columns requested encapsulated as attributes of the model you call 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. diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 66bc14b5ab..20dcb0080b 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -144,7 +144,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_order_by_calculation - c = Account.group(:firm_id).order("sum_credit_limit desc, firm_id").sum(:credit_limit) + c = Account.group(:firm_id).order(Arel.sql("sum_credit_limit desc, firm_id")).sum(:credit_limit) assert_equal [105, 60, 53, 50, 50], c.keys.collect { |k| c[k] } assert_equal [6, 2, 9, 1], c.keys.compact end @@ -644,7 +644,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_pluck_with_qualified_column_name - assert_equal [1, 2, 3, 4, 5], Topic.order(:id).pluck("topics.id") + assert_equal [1, 2, 3, 4, 5], Topic.order(:id).pluck(Arel.sql("topics.id")) end def test_pluck_auto_table_name_prefix @@ -659,18 +659,18 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_not_auto_table_name_prefix_if_column_joined Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) - assert_equal [7], Company.joins(:contracts).pluck(:developer_id) + assert_equal [7], Company.joins(:contracts).pluck(Arel.sql("developer_id")) end def test_pluck_with_selection_clause - assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT credit_limit").sort - assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT accounts.credit_limit").sort - assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT(credit_limit)").sort + assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT credit_limit")).sort + assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT accounts.credit_limit")).sort + assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT(credit_limit)")).sort # MySQL returns "SUM(DISTINCT(credit_limit))" as the column name unless # an alias is provided. Without the alias, the column cannot be found # and properly typecast. - assert_equal [50 + 53 + 55 + 60], Account.pluck("SUM(DISTINCT(credit_limit)) as credit_limit") + assert_equal [50 + 53 + 55 + 60], Account.pluck(Arel.sql("SUM(DISTINCT(credit_limit)) as credit_limit")) end def test_plucks_with_ids @@ -684,7 +684,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_not_auto_table_name_prefix_if_column_included Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) - ids = Company.includes(:contracts).pluck(:developer_id) + ids = Company.includes(:contracts).pluck(Arel.sql("developer_id")) assert_equal Company.count, ids.length assert_equal [7], ids.compact end @@ -704,12 +704,12 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_with_multiple_columns_and_selection_clause assert_equal [[1, 50], [2, 50], [3, 50], [4, 60], [5, 55], [6, 53]], - Account.pluck("id, credit_limit") + Account.pluck(Arel.sql("id, credit_limit")) end def test_pluck_with_multiple_columns_and_includes Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) - companies_and_developers = Company.order("companies.id").includes(:contracts).pluck(:name, :developer_id) + companies_and_developers = Company.order(Arel.sql("companies.id")).includes(:contracts).pluck(:name, Arel.sql("developer_id")) assert_equal Company.count, companies_and_developers.length assert_equal ["37signals", nil], companies_and_developers.first @@ -731,7 +731,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_columns_with_same_name expected = [["The First Topic", "The Second Topic of the day"], ["The Third Topic of the day", "The Fourth Topic of the day"]] actual = Topic.joins(:replies) - .pluck("topics.title", "replies_topics.title") + .pluck(Arel.sql("topics.title"), Arel.sql("replies_topics.title")) assert_equal expected, actual end @@ -772,7 +772,7 @@ class CalculationsTest < ActiveRecord::TestCase companies = Company.order(:name).limit(3).load assert_queries 1 do - assert_equal ["37signals", "Apex", "Ex Nihilo"], companies.pluck("DISTINCT name") + assert_equal ["37signals", "Apex", "Ex Nihilo"], companies.pluck(Arel.sql("DISTINCT name")) end end diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index b68b3723f6..953e0fee76 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -118,7 +118,7 @@ class MergingDifferentRelationsTest < ActiveRecord::TestCase test "merging where relations" do hello_by_bob = Post.where(body: "hello").joins(:author). - merge(Author.where(name: "Bob")).order("posts.id").pluck("posts.id") + merge(Author.where(name: "Bob")).order("posts.id").pluck(Arel.sql("posts.id")) assert_equal [posts(:misc_by_bob).id, posts(:other_by_bob).id], hello_by_bob @@ -126,19 +126,19 @@ class MergingDifferentRelationsTest < ActiveRecord::TestCase test "merging order relations" do posts_by_author_name = Post.limit(3).joins(:author). - merge(Author.order(:name)).pluck("authors.name") + merge(Author.order(:name)).pluck(Arel.sql("authors.name")) assert_equal ["Bob", "Bob", "David"], posts_by_author_name posts_by_author_name = Post.limit(3).joins(:author). - merge(Author.order("name")).pluck("authors.name") + merge(Author.order("name")).pluck(Arel.sql("authors.name")) assert_equal ["Bob", "Bob", "David"], posts_by_author_name end test "merging order relations (using a hash argument)" do posts_by_author_name = Post.limit(4).joins(:author). - merge(Author.order(name: :desc)).pluck("authors.name") + merge(Author.order(name: :desc)).pluck(Arel.sql("authors.name")) assert_equal ["Mary", "Mary", "Mary", "David"], posts_by_author_name end diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb index d05f0f12d9..89eb02594a 100644 --- a/activerecord/test/cases/unsafe_raw_sql_test.rb +++ b/activerecord/test/cases/unsafe_raw_sql_test.rb @@ -13,7 +13,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end assert_equal enabled, disabled - assert_equal disabled, Post.unsafe_raw_order("title").pluck(:id) + assert_equal disabled, Post.order(Arel.sql("title")).pluck(:id) end test "order: allows symbol column name" do @@ -22,7 +22,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end assert_equal enabled, disabled - assert_equal disabled, Post.unsafe_raw_order(:title).pluck(:id) + assert_equal disabled, Post.order(Arel.sql("title")).pluck(:id) end test "order: allows downcase symbol direction" do @@ -31,7 +31,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end assert_equal enabled, disabled - assert_equal disabled, Post.unsafe_raw_order(title: :asc).pluck(:id) + assert_equal disabled, Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id) end test "order: allows upcase symbol direction" do @@ -40,7 +40,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end assert_equal enabled, disabled - assert_equal disabled, Post.unsafe_raw_order(title: :ASC).pluck(:id) + assert_equal disabled, Post.order(Arel.sql("title") => Arel.sql("ASC")).pluck(:id) end test "order: allows string direction" do @@ -49,7 +49,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end assert_equal enabled, disabled - assert_equal disabled, Post.unsafe_raw_order(title: "asc").pluck(:id) + assert_equal disabled, Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id) end test "order: allows multiple columns" do @@ -58,7 +58,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end assert_equal enabled, disabled - assert_equal disabled, Post.unsafe_raw_order(:author_id, :title).pluck(:id) + assert_equal disabled, Post.order(Arel.sql("author_id"), Arel.sql("title")).pluck(:id) end test "order: allows mixed" do @@ -67,12 +67,12 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end assert_equal enabled, disabled - assert_equal disabled, Post.unsafe_raw_order(:author_id, title: :asc).pluck(:id) + assert_equal disabled, Post.order(Arel.sql("author_id"), Arel.sql("title") => Arel.sql("asc")).pluck(:id) end test "order: disallows invalid column name" do with_config(:disabled) do - assert_raises(ArgumentError) do + assert_raises(ActiveRecord::UnknownAttributeReference) do Post.order("title asc").pluck(:id) end end @@ -88,19 +88,37 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase test "order: disallows invalid column with direction" do with_config(:disabled) do - assert_raises(ArgumentError) do + assert_raises(ActiveRecord::UnknownAttributeReference) do Post.order(foo: :asc).pluck(:id) end end end + test "order: always allows Arel" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.order(Arel.sql("length(title)")).pluck(:title) + end + + assert_equal enabled, disabled + end + + test "order: logs deprecation warning for unrecognized column" do + with_config(:deprecated) do + ActiveSupport::Deprecation.expects(:warn).with do |msg| + msg =~ /\ADangerous query method used with .*length\(title\)/ + end + + Post.order("length(title)") + end + end + test "pluck: allows string column name" do enabled, disabled = with_configs(:enabled, :disabled) do Post.pluck("title") end assert_equal enabled, disabled - assert_equal disabled, Post.unsafe_raw_pluck("title") + assert_equal disabled, Post.pluck(Arel.sql("title")) end test "pluck: allows symbol column name" do @@ -109,7 +127,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end assert_equal enabled, disabled - assert_equal disabled, Post.unsafe_raw_pluck(:title) + assert_equal disabled, Post.pluck(Arel.sql("title")) end test "pluck: allows multiple column names" do @@ -118,7 +136,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end assert_equal enabled, disabled - assert_equal disabled, Post.unsafe_raw_pluck(:title, :id) + assert_equal disabled, Post.pluck(Arel.sql("title"), Arel.sql("id")) end test "pluck: allows column names with includes" do @@ -127,7 +145,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end assert_equal enabled, disabled - assert_equal disabled, Post.includes(:comments).unsafe_raw_pluck(:title, :id) + assert_equal disabled, Post.includes(:comments).pluck(Arel.sql("title"), Arel.sql("id")) end test "pluck: allows auto-generated attributes" do @@ -136,12 +154,12 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end assert_equal enabled, disabled - assert_equal disabled, Post.unsafe_raw_pluck(:tags_count) + assert_equal disabled, Post.pluck(Arel.sql("tags_count")) end test "pluck: disallows invalid column name" do with_config(:disabled) do - assert_raises(ArgumentError) do + assert_raises(ActiveRecord::UnknownAttributeReference) do Post.pluck("length(title)") end end @@ -149,7 +167,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase test "pluck: disallows invalid column name amongst valid names" do with_config(:disabled) do - assert_raises(ArgumentError) do + assert_raises(ActiveRecord::UnknownAttributeReference) do Post.pluck(:title, "length(title)") end end @@ -157,12 +175,30 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase test "pluck: disallows invalid column names with includes" do with_config(:disabled) do - assert_raises(ArgumentError) do + assert_raises(ActiveRecord::UnknownAttributeReference) do Post.includes(:comments).pluck(:title, "length(title)") end end end + test "pluck: always allows Arel" do + enabled, disabled = with_configs(:enabled, :disabled) do + Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) + end + + assert_equal enabled, disabled + end + + test "pluck: logs deprecation warning" do + with_config(:deprecated) do + ActiveSupport::Deprecation.expects(:warn).with do |msg| + msg =~ /\ADangerous query method used with .*length\(title\)/ + end + + Post.includes(:comments).pluck(:title, "length(title)") + end + end + def with_configs(*new_values, &blk) new_values.map { |nv| with_config(nv, &blk) } end -- cgit v1.2.3 From 0cf5f2f048ab547edb0d017095a642126a87a879 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Tue, 8 Aug 2017 13:58:07 -0600 Subject: make tests more verbose/explicit --- activerecord/test/cases/unsafe_raw_sql_test.rb | 174 ++++++++++++++----------- 1 file changed, 96 insertions(+), 78 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb index 89eb02594a..53418bb914 100644 --- a/activerecord/test/cases/unsafe_raw_sql_test.rb +++ b/activerecord/test/cases/unsafe_raw_sql_test.rb @@ -8,70 +8,77 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase fixtures :posts, :comments test "order: allows string column name" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.order("title").pluck(:id) - end + ids_expected = Post.order(Arel.sql("title")).pluck(:id) + + ids_enabled = with_unsafe_raw_sql_enabled { Post.order("title").pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order("title").pluck(:id) } - assert_equal enabled, disabled - assert_equal disabled, Post.order(Arel.sql("title")).pluck(:id) + assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_disabled end test "order: allows symbol column name" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.order(:title).pluck(:id) - end + ids_expected = Post.order(Arel.sql("title")).pluck(:id) + + ids_enabled = with_unsafe_raw_sql_enabled { Post.order(:title).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:title).pluck(:id) } - assert_equal enabled, disabled - assert_equal disabled, Post.order(Arel.sql("title")).pluck(:id) + assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_disabled end test "order: allows downcase symbol direction" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.order(title: :asc).pluck(:id) - end + ids_expected = Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id) + + ids_enabled = with_unsafe_raw_sql_enabled { Post.order(title: :asc).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: :asc).pluck(:id) } - assert_equal enabled, disabled - assert_equal disabled, Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id) + assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_disabled end test "order: allows upcase symbol direction" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.order(title: :ASC).pluck(:id) - end + ids_expected = Post.order(Arel.sql("title") => Arel.sql("ASC")).pluck(:id) + + ids_enabled = with_unsafe_raw_sql_enabled { Post.order(title: :ASC).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: :ASC).pluck(:id) } - assert_equal enabled, disabled - assert_equal disabled, Post.order(Arel.sql("title") => Arel.sql("ASC")).pluck(:id) + assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_disabled end test "order: allows string direction" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.order(title: "asc").pluck(:id) - end + ids_expected = Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id) + + ids_enabled = with_unsafe_raw_sql_enabled { Post.order(title: "asc").pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: "asc").pluck(:id) } - assert_equal enabled, disabled - assert_equal disabled, Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id) + assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_disabled end test "order: allows multiple columns" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.order(:author_id, :title).pluck(:id) - end + ids_expected = Post.order(Arel.sql("author_id"), Arel.sql("title")).pluck(:id) + + ids_enabled = with_unsafe_raw_sql_enabled { Post.order(:author_id, :title).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:author_id, :title).pluck(:id) } - assert_equal enabled, disabled - assert_equal disabled, Post.order(Arel.sql("author_id"), Arel.sql("title")).pluck(:id) + assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_disabled end test "order: allows mixed" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.order(:author_id, title: :asc).pluck(:id) - end + ids_expected = Post.order(Arel.sql("author_id"), Arel.sql("title") => Arel.sql("asc")).pluck(:id) + + ids_enabled = with_unsafe_raw_sql_enabled { Post.order(:author_id, title: :asc).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:author_id, title: :asc).pluck(:id) } - assert_equal enabled, disabled - assert_equal disabled, Post.order(Arel.sql("author_id"), Arel.sql("title") => Arel.sql("asc")).pluck(:id) + assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_disabled end test "order: disallows invalid column name" do - with_config(:disabled) do + with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do Post.order("title asc").pluck(:id) end @@ -79,7 +86,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "order: disallows invalid direction" do - with_config(:disabled) do + with_unsafe_raw_sql_disabled do assert_raises(ArgumentError) do Post.order(title: :foo).pluck(:id) end @@ -87,7 +94,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "order: disallows invalid column with direction" do - with_config(:disabled) do + with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do Post.order(foo: :asc).pluck(:id) end @@ -95,15 +102,14 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "order: always allows Arel" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.order(Arel.sql("length(title)")).pluck(:title) - end + ids_enabled = with_unsafe_raw_sql_enabled { Post.order(Arel.sql("length(title)")).pluck(:title) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("length(title)")).pluck(:title) } - assert_equal enabled, disabled + assert_equal ids_enabled, ids_disabled end test "order: logs deprecation warning for unrecognized column" do - with_config(:deprecated) do + with_unsafe_raw_sql_deprecated do ActiveSupport::Deprecation.expects(:warn).with do |msg| msg =~ /\ADangerous query method used with .*length\(title\)/ end @@ -113,52 +119,57 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "pluck: allows string column name" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.pluck("title") - end + titles_expected = Post.pluck(Arel.sql("title")) + + titles_enabled = with_unsafe_raw_sql_enabled { Post.pluck("title") } + titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("title") } - assert_equal enabled, disabled - assert_equal disabled, Post.pluck(Arel.sql("title")) + assert_equal titles_expected, titles_enabled + assert_equal titles_expected, titles_disabled end test "pluck: allows symbol column name" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.pluck(:title) - end + titles_expected = Post.pluck(Arel.sql("title")) + + titles_enabled = with_unsafe_raw_sql_enabled { Post.pluck(:title) } + titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:title) } - assert_equal enabled, disabled - assert_equal disabled, Post.pluck(Arel.sql("title")) + assert_equal titles_expected, titles_enabled + assert_equal titles_expected, titles_disabled end test "pluck: allows multiple column names" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.pluck(:title, :id) - end + values_expected = Post.pluck(Arel.sql("title"), Arel.sql("id")) + + values_enabled = with_unsafe_raw_sql_enabled { Post.pluck(:title, :id) } + values_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:title, :id) } - assert_equal enabled, disabled - assert_equal disabled, Post.pluck(Arel.sql("title"), Arel.sql("id")) + assert_equal values_expected, values_enabled + assert_equal values_expected, values_disabled end test "pluck: allows column names with includes" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.includes(:comments).pluck(:title, :id) - end + values_expected = Post.includes(:comments).pluck(Arel.sql("title"), Arel.sql("id")) + + values_enabled = with_unsafe_raw_sql_enabled { Post.includes(:comments).pluck(:title, :id) } + values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, :id) } - assert_equal enabled, disabled - assert_equal disabled, Post.includes(:comments).pluck(Arel.sql("title"), Arel.sql("id")) + assert_equal values_expected, values_enabled + assert_equal values_expected, values_disabled end test "pluck: allows auto-generated attributes" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.pluck(:tags_count) - end + values_expected = Post.pluck(Arel.sql("tags_count")) + + values_enabled = with_unsafe_raw_sql_enabled { Post.pluck(:tags_count) } + values_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:tags_count) } - assert_equal enabled, disabled - assert_equal disabled, Post.pluck(Arel.sql("tags_count")) + assert_equal values_expected, values_enabled + assert_equal values_expected, values_disabled end test "pluck: disallows invalid column name" do - with_config(:disabled) do + with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do Post.pluck("length(title)") end @@ -166,7 +177,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "pluck: disallows invalid column name amongst valid names" do - with_config(:disabled) do + with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do Post.pluck(:title, "length(title)") end @@ -174,7 +185,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "pluck: disallows invalid column names with includes" do - with_config(:disabled) do + with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do Post.includes(:comments).pluck(:title, "length(title)") end @@ -182,15 +193,14 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "pluck: always allows Arel" do - enabled, disabled = with_configs(:enabled, :disabled) do - Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) - end + values_enabled = with_unsafe_raw_sql_enabled { Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) } + values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) } - assert_equal enabled, disabled + assert_equal values_enabled, values_disabled end test "pluck: logs deprecation warning" do - with_config(:deprecated) do + with_unsafe_raw_sql_deprecated do ActiveSupport::Deprecation.expects(:warn).with do |msg| msg =~ /\ADangerous query method used with .*length\(title\)/ end @@ -199,8 +209,16 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end end - def with_configs(*new_values, &blk) - new_values.map { |nv| with_config(nv, &blk) } + def with_unsafe_raw_sql_enabled(&blk) + with_config(:enabled, &blk) + end + + def with_unsafe_raw_sql_disabled(&blk) + with_config(:disabled, &blk) + end + + def with_unsafe_raw_sql_deprecated(&blk) + with_config(:deprecated, &blk) end def with_config(new_value, &blk) -- cgit v1.2.3 From 310c3a8f2d043f3d00d3f703052a1e160430a2c2 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Thu, 10 Aug 2017 11:21:22 -0600 Subject: beef up deprecation warning --- activerecord/lib/active_record/attribute_methods.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index b3d3c0559f..e167c4ad6f 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -179,9 +179,13 @@ module ActiveRecord if allow_unsafe_raw_sql == :deprecated ActiveSupport::Deprecation.warn( - "Dangerous query method used with non-attribute argument(s): " + - "#{unexpected.map(&:inspect).join(", ")}. Non-argument " + - "arguments will be disallowed in Rails 5.3." + "Dangerous query method (method whose arguments are used as raw " \ + "SQL) called with non-attribute argument(s): " \ + "#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \ + "arguments will be disallowed in Rails 6.0. This method should " \ + "not be called with user-provided values, such as request " \ + "parameters or model attributes. Known-safe values can be passed " \ + "by wrapping them in Arel.sql()." ) else raise(ActiveRecord::UnknownAttributeReference, -- cgit v1.2.3 From 0d8626ad3f1bac3b814d554147ff7926c0380fad Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Fri, 11 Aug 2017 09:55:54 -0600 Subject: remove :enabled option --- .../lib/active_record/attribute_methods.rb | 2 - activerecord/lib/active_record/core.rb | 5 +- activerecord/test/cases/unsafe_raw_sql_test.rb | 104 ++++++++++----------- 3 files changed, 52 insertions(+), 59 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index e167c4ad6f..5ce3ba7a63 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -168,8 +168,6 @@ module ActiveRecord end def enforce_raw_sql_whitelist(args, whitelist: attribute_names_and_aliases) # :nodoc: - return if allow_unsafe_raw_sql == :enabled - unexpected = args.reject do |arg| whitelist.include?(arg.to_s) || arg.kind_of?(Arel::Node) || arg.is_a?(Arel::Nodes::SqlLiteral) diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index b1e3f71dfe..b97b14644e 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -78,12 +78,11 @@ module ActiveRecord # :singleton-method: # Specify the behavior for unsafe raw query methods. Values are as follows - # enabled - Unsafe raw SQL can be passed to query methods. # deprecated - Warnings are logged when unsafe raw SQL is passed to # query methods. # disabled - Unsafe raw SQL passed to query methods results in - # ArguementError. - mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :enabled + # UnknownAttributeReference exception. + mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :deprecated ## # :singleton-method: diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb index 53418bb914..74471b0ace 100644 --- a/activerecord/test/cases/unsafe_raw_sql_test.rb +++ b/activerecord/test/cases/unsafe_raw_sql_test.rb @@ -10,75 +10,75 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase test "order: allows string column name" do ids_expected = Post.order(Arel.sql("title")).pluck(:id) - ids_enabled = with_unsafe_raw_sql_enabled { Post.order("title").pluck(:id) } - ids_disabled = with_unsafe_raw_sql_disabled { Post.order("title").pluck(:id) } + ids_depr = with_unsafe_raw_sql_deprecated { Post.order("title").pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order("title").pluck(:id) } - assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_depr assert_equal ids_expected, ids_disabled end test "order: allows symbol column name" do ids_expected = Post.order(Arel.sql("title")).pluck(:id) - ids_enabled = with_unsafe_raw_sql_enabled { Post.order(:title).pluck(:id) } - ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:title).pluck(:id) } + ids_depr = with_unsafe_raw_sql_deprecated { Post.order(:title).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:title).pluck(:id) } - assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_depr assert_equal ids_expected, ids_disabled end test "order: allows downcase symbol direction" do ids_expected = Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id) - ids_enabled = with_unsafe_raw_sql_enabled { Post.order(title: :asc).pluck(:id) } - ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: :asc).pluck(:id) } + ids_depr = with_unsafe_raw_sql_deprecated { Post.order(title: :asc).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: :asc).pluck(:id) } - assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_depr assert_equal ids_expected, ids_disabled end test "order: allows upcase symbol direction" do ids_expected = Post.order(Arel.sql("title") => Arel.sql("ASC")).pluck(:id) - ids_enabled = with_unsafe_raw_sql_enabled { Post.order(title: :ASC).pluck(:id) } - ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: :ASC).pluck(:id) } + ids_depr = with_unsafe_raw_sql_deprecated { Post.order(title: :ASC).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: :ASC).pluck(:id) } - assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_depr assert_equal ids_expected, ids_disabled end test "order: allows string direction" do ids_expected = Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id) - ids_enabled = with_unsafe_raw_sql_enabled { Post.order(title: "asc").pluck(:id) } - ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: "asc").pluck(:id) } + ids_depr = with_unsafe_raw_sql_deprecated { Post.order(title: "asc").pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: "asc").pluck(:id) } - assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_depr assert_equal ids_expected, ids_disabled end test "order: allows multiple columns" do ids_expected = Post.order(Arel.sql("author_id"), Arel.sql("title")).pluck(:id) - ids_enabled = with_unsafe_raw_sql_enabled { Post.order(:author_id, :title).pluck(:id) } - ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:author_id, :title).pluck(:id) } + ids_depr = with_unsafe_raw_sql_deprecated { Post.order(:author_id, :title).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:author_id, :title).pluck(:id) } - assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_depr assert_equal ids_expected, ids_disabled end test "order: allows mixed" do ids_expected = Post.order(Arel.sql("author_id"), Arel.sql("title") => Arel.sql("asc")).pluck(:id) - ids_enabled = with_unsafe_raw_sql_enabled { Post.order(:author_id, title: :asc).pluck(:id) } - ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:author_id, title: :asc).pluck(:id) } + ids_depr = with_unsafe_raw_sql_deprecated { Post.order(:author_id, title: :asc).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:author_id, title: :asc).pluck(:id) } - assert_equal ids_expected, ids_enabled + assert_equal ids_expected, ids_depr assert_equal ids_expected, ids_disabled end test "order: disallows invalid column name" do - with_unsafe_raw_sql_disabled do + with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do Post.order("title asc").pluck(:id) end @@ -86,7 +86,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "order: disallows invalid direction" do - with_unsafe_raw_sql_disabled do + with_unsafe_raw_sql_disabled do assert_raises(ArgumentError) do Post.order(title: :foo).pluck(:id) end @@ -94,7 +94,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "order: disallows invalid column with direction" do - with_unsafe_raw_sql_disabled do + with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do Post.order(foo: :asc).pluck(:id) end @@ -102,16 +102,16 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "order: always allows Arel" do - ids_enabled = with_unsafe_raw_sql_enabled { Post.order(Arel.sql("length(title)")).pluck(:title) } - ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("length(title)")).pluck(:title) } + ids_depr = with_unsafe_raw_sql_deprecated { Post.order(Arel.sql("length(title)")).pluck(:title) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("length(title)")).pluck(:title) } - assert_equal ids_enabled, ids_disabled + assert_equal ids_depr, ids_disabled end test "order: logs deprecation warning for unrecognized column" do with_unsafe_raw_sql_deprecated do ActiveSupport::Deprecation.expects(:warn).with do |msg| - msg =~ /\ADangerous query method used with .*length\(title\)/ + msg =~ /\ADangerous query method/ end Post.order("length(title)") @@ -121,55 +121,55 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase test "pluck: allows string column name" do titles_expected = Post.pluck(Arel.sql("title")) - titles_enabled = with_unsafe_raw_sql_enabled { Post.pluck("title") } - titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("title") } + titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("title") } + titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("title") } - assert_equal titles_expected, titles_enabled + assert_equal titles_expected, titles_depr assert_equal titles_expected, titles_disabled end test "pluck: allows symbol column name" do titles_expected = Post.pluck(Arel.sql("title")) - titles_enabled = with_unsafe_raw_sql_enabled { Post.pluck(:title) } - titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:title) } + titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck(:title) } + titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:title) } - assert_equal titles_expected, titles_enabled + assert_equal titles_expected, titles_depr assert_equal titles_expected, titles_disabled end test "pluck: allows multiple column names" do values_expected = Post.pluck(Arel.sql("title"), Arel.sql("id")) - values_enabled = with_unsafe_raw_sql_enabled { Post.pluck(:title, :id) } - values_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:title, :id) } + values_depr = with_unsafe_raw_sql_deprecated { Post.pluck(:title, :id) } + values_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:title, :id) } - assert_equal values_expected, values_enabled + assert_equal values_expected, values_depr assert_equal values_expected, values_disabled end test "pluck: allows column names with includes" do values_expected = Post.includes(:comments).pluck(Arel.sql("title"), Arel.sql("id")) - values_enabled = with_unsafe_raw_sql_enabled { Post.includes(:comments).pluck(:title, :id) } - values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, :id) } + values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, :id) } + values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, :id) } - assert_equal values_expected, values_enabled + assert_equal values_expected, values_depr assert_equal values_expected, values_disabled end test "pluck: allows auto-generated attributes" do values_expected = Post.pluck(Arel.sql("tags_count")) - values_enabled = with_unsafe_raw_sql_enabled { Post.pluck(:tags_count) } - values_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:tags_count) } + values_depr = with_unsafe_raw_sql_deprecated { Post.pluck(:tags_count) } + values_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:tags_count) } - assert_equal values_expected, values_enabled + assert_equal values_expected, values_depr assert_equal values_expected, values_disabled end test "pluck: disallows invalid column name" do - with_unsafe_raw_sql_disabled do + with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do Post.pluck("length(title)") end @@ -177,7 +177,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "pluck: disallows invalid column name amongst valid names" do - with_unsafe_raw_sql_disabled do + with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do Post.pluck(:title, "length(title)") end @@ -185,7 +185,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "pluck: disallows invalid column names with includes" do - with_unsafe_raw_sql_disabled do + with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do Post.includes(:comments).pluck(:title, "length(title)") end @@ -193,26 +193,22 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "pluck: always allows Arel" do - values_enabled = with_unsafe_raw_sql_enabled { Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) } - values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) } + values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) } + values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) } - assert_equal values_enabled, values_disabled + assert_equal values_depr, values_disabled end test "pluck: logs deprecation warning" do with_unsafe_raw_sql_deprecated do ActiveSupport::Deprecation.expects(:warn).with do |msg| - msg =~ /\ADangerous query method used with .*length\(title\)/ + msg =~ /\ADangerous query method/ end Post.includes(:comments).pluck(:title, "length(title)") end end - def with_unsafe_raw_sql_enabled(&blk) - with_config(:enabled, &blk) - end - def with_unsafe_raw_sql_disabled(&blk) with_config(:disabled, &blk) end -- 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') 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') 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 40d302d880f247ef9547708b1d26a390945b6fe9 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Mon, 25 Sep 2017 10:54:53 -0600 Subject: always allow Arel::Attributes::Attribute also --- activerecord/lib/active_record/attribute_methods.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 5ce3ba7a63..ff381b4e0b 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -170,7 +170,9 @@ module ActiveRecord def enforce_raw_sql_whitelist(args, whitelist: attribute_names_and_aliases) # :nodoc: unexpected = args.reject do |arg| whitelist.include?(arg.to_s) || - arg.kind_of?(Arel::Node) || arg.is_a?(Arel::Nodes::SqlLiteral) + arg.kind_of?(Arel::Node) || + arg.is_a?(Arel::Nodes::SqlLiteral) || + arg.is_a?(Arel::Attributes::Attribute) end return if unexpected.none? -- cgit v1.2.3 From 02a17492a937d4b423590644ad1481b82facd394 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Mon, 25 Sep 2017 10:58:08 -0600 Subject: work around deprecation warnings in a bunch of tests --- .../associations/cascaded_eager_loading_test.rb | 32 +++--- .../associations/eager_load_nested_include_test.rb | 2 +- activerecord/test/cases/associations/eager_test.rb | 104 ++++++++--------- .../has_and_belongs_to_many_associations_test.rb | 2 +- .../associations/has_many_associations_test.rb | 10 +- .../has_many_through_associations_test.rb | 4 +- .../has_one_through_associations_test.rb | 6 +- .../associations/inverse_associations_test.rb | 8 +- .../test/cases/associations/join_model_test.rb | 32 +++--- .../nested_through_associations_test.rb | 30 ++--- activerecord/test/cases/base_test.rb | 24 ++-- activerecord/test/cases/batches_test.rb | 14 +-- activerecord/test/cases/finder_test.rb | 40 +++---- activerecord/test/cases/relation/merging_test.rb | 14 +-- activerecord/test/cases/relation/mutation_test.rb | 2 +- activerecord/test/cases/relation/or_test.rb | 14 +-- activerecord/test/cases/relations_test.rb | 125 +++++++++++---------- .../test/cases/scoping/default_scoping_test.rb | 62 +++++----- .../test/cases/scoping/named_scoping_test.rb | 4 +- .../test/cases/scoping/relation_scoping_test.rb | 16 +-- activerecord/test/models/author.rb | 24 ++-- activerecord/test/models/car.rb | 6 +- activerecord/test/models/category.rb | 2 +- activerecord/test/models/comment.rb | 2 +- activerecord/test/models/company.rb | 2 +- activerecord/test/models/company_in_module.rb | 2 +- activerecord/test/models/developer.rb | 22 ++-- activerecord/test/models/membership.rb | 2 +- activerecord/test/models/owner.rb | 2 +- activerecord/test/models/person.rb | 2 +- activerecord/test/models/pirate.rb | 4 +- activerecord/test/models/post.rb | 12 +- activerecord/test/models/project.rb | 4 +- activerecord/test/models/tag.rb | 2 +- 34 files changed, 320 insertions(+), 313 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index e69cfe5e52..3eba5ed466 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -18,7 +18,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase :categorizations, :people, :categories, :edges, :vertices def test_eager_association_loading_with_cascaded_two_levels - authors = Author.all.merge!(includes: { posts: :comments }, order: "authors.id").to_a + authors = Author.all.merge!(includes: { posts: :comments }, order: Arel.sql("authors.id")).to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size @@ -26,7 +26,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_and_one_level - authors = Author.all.merge!(includes: [{ posts: :comments }, :categorizations], order: "authors.id").to_a + authors = Author.all.merge!(includes: [{ posts: :comments }, :categorizations], order: Arel.sql("authors.id")).to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size @@ -42,7 +42,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_grafts_stashed_associations_to_correct_parent - assert_equal people(:michael), Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").first + assert_equal people(:michael), Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order(Arel.sql("people.id")).first end def test_cascaded_eager_association_loading_with_join_for_count @@ -78,7 +78,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations - authors = Author.all.merge!(includes: { posts: [:comments, :categorizations] }, order: "authors.id").to_a + authors = Author.all.merge!(includes: { posts: [:comments, :categorizations] }, order: Arel.sql("authors.id")).to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size @@ -86,7 +86,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference - authors = Author.all.merge!(includes: { posts: [:comments, :author] }, order: "authors.id").to_a + authors = Author.all.merge!(includes: { posts: [:comments, :author] }, order: Arel.sql("authors.id")).to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal authors(:david).name, authors[0].name @@ -94,13 +94,13 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_with_condition - authors = Author.all.merge!(includes: { posts: :comments }, where: "authors.id=1", order: "authors.id").to_a + authors = Author.all.merge!(includes: { posts: :comments }, where: "authors.id=1", order: Arel.sql("authors.id")).to_a assert_equal 1, authors.size assert_equal 5, authors[0].posts.size end def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong - firms = Firm.all.merge!(includes: { account: { firm: :account } }, order: "companies.id").to_a + firms = Firm.all.merge!(includes: { account: { firm: :account } }, order: Arel.sql("companies.id")).to_a assert_equal 2, firms.size assert_equal firms.first.account, firms.first.account.firm.account assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account } @@ -108,7 +108,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_has_many_sti - topics = Topic.all.merge!(includes: :replies, order: "topics.id").to_a + topics = Topic.all.merge!(includes: :replies, order: Arel.sql("topics.id")).to_a first, second, = topics(:first).replies.size, topics(:second).replies.size assert_no_queries do assert_equal first, topics[0].replies.size @@ -121,7 +121,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase silly.parent_id = 1 assert silly.save - topics = Topic.all.merge!(includes: :replies, order: ["topics.id", "replies_topics.id"]).to_a + topics = Topic.all.merge!(includes: :replies, order: [Arel.sql("topics.id"), Arel.sql("replies_topics.id")]).to_a assert_no_queries do assert_equal 2, topics[0].replies.size assert_equal 0, topics[1].replies.size @@ -129,14 +129,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_belongs_to_sti - replies = Reply.all.merge!(includes: :topic, order: "topics.id").to_a + replies = Reply.all.merge!(includes: :topic, order: Arel.sql("topics.id")).to_a assert_includes replies, topics(:second) assert_not_includes replies, topics(:first) assert_equal topics(:first), assert_no_queries { replies.first.topic } end def test_eager_association_loading_with_multiple_stis_and_order - author = Author.all.merge!(includes: { posts: [ :special_comments , :very_special_comment ] }, order: ["authors.name", "comments.body", "very_special_comments_posts.body"], where: "posts.id = 4").first + author = Author.all.merge!(includes: { posts: [ :special_comments , :very_special_comment ] }, order: [Arel.sql("authors.name"), Arel.sql("comments.body"), Arel.sql("very_special_comments_posts.body")], where: "posts.id = 4").first assert_equal authors(:david), author assert_no_queries do author.posts.first.special_comments @@ -145,7 +145,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_of_stis_with_multiple_references - authors = Author.all.merge!(includes: { posts: { special_comments: { post: [ :special_comments, :very_special_comment ] } } }, order: "comments.body, very_special_comments_posts.body", where: "posts.id = 4").to_a + authors = Author.all.merge!(includes: { posts: { special_comments: { post: [ :special_comments, :very_special_comment ] } } }, order: Arel.sql("comments.body, very_special_comments_posts.body"), where: "posts.id = 4").to_a assert_equal [authors(:david)], authors assert_no_queries do authors.first.posts.first.special_comments.first.post.special_comments @@ -154,7 +154,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_where_first_level_returns_nil - authors = Author.all.merge!(includes: { post_about_thinking: :comments }, order: "authors.id DESC").to_a + authors = Author.all.merge!(includes: { post_about_thinking: :comments }, order: Arel.sql("authors.id DESC")).to_a assert_equal [authors(:bob), authors(:mary), authors(:david)], authors assert_no_queries do authors[2].post_about_thinking.comments.first @@ -162,17 +162,17 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through - source = Vertex.all.merge!(includes: { sinks: { sinks: { sinks: :sinks } } }, order: "vertices.id").first + source = Vertex.all.merge!(includes: { sinks: { sinks: { sinks: :sinks } } }, order: Arel.sql("vertices.id")).first assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first } end def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many - sink = Vertex.all.merge!(includes: { sources: { sources: { sources: :sources } } }, order: "vertices.id DESC").first + sink = Vertex.all.merge!(includes: { sources: { sources: { sources: :sources } } }, order: Arel.sql("vertices.id DESC")).first assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first } end def test_eager_association_loading_with_cascaded_interdependent_one_level_and_two_levels - authors_relation = Author.all.merge!(includes: [:comments, { posts: :categorizations }], order: "authors.id") + authors_relation = Author.all.merge!(includes: [:comments, { posts: :categorizations }], order: Arel.sql("authors.id")) authors = authors_relation.to_a assert_equal 3, authors.size assert_equal 10, authors[0].comments.size diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb index c5b2b77bd4..b1809401fb 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -120,7 +120,7 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase assert_nothing_raised do # @davey_mcdave doesn't have any author_favorites includes = { posts: :comments, categorizations: :category, author_favorites: :favorite_author } - Author.all.merge!(includes: includes, where: { authors: { name: @davey_mcdave.name } }, order: "categories.name").to_a + Author.all.merge!(includes: includes, where: { authors: { name: @davey_mcdave.name } }, order: Arel.sql("categories.name")).to_a end end end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 9afe6a893c..d5ca87900e 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -62,7 +62,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_loading_with_one_association_with_non_preload - posts = Post.all.merge!(includes: :last_comment, order: "comments.id DESC").to_a + posts = Post.all.merge!(includes: :last_comment, order: Arel.sql("comments.id DESC")).to_a post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end @@ -82,7 +82,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_with_ordering - list = Post.all.merge!(includes: :comments, order: "posts.id DESC").to_a + list = Post.all.merge!(includes: :comments, order: Arel.sql("posts.id DESC")).to_a [:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments, :authorless, :thinking, :welcome ].each_with_index do |post, index| @@ -108,12 +108,12 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_with_two_tables_in_from_without_getting_double_quoted - posts = Post.select("posts.*").from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").order("posts.id").to_a + posts = Post.select("posts.*").from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").order(Arel.sql("posts.id")).to_a assert_equal 2, posts.first.comments.size end def test_loading_with_multiple_associations - posts = Post.all.merge!(includes: [ :comments, :author, :categories ], order: "posts.id").to_a + posts = Post.all.merge!(includes: [ :comments, :author, :categories ], order: Arel.sql("posts.id")).to_a assert_equal 2, posts.first.comments.size assert_equal 2, posts.first.categories.size assert_includes posts.first.comments, comments(:greetings) @@ -279,7 +279,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_loading_from_an_association - posts = authors(:david).posts.merge(includes: :comments, order: "posts.id").to_a + posts = authors(:david).posts.merge(includes: :comments, order: Arel.sql("posts.id")).to_a assert_equal 2, posts.first.comments.size end @@ -312,17 +312,17 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_nested_loading_through_has_one_association_with_order - aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: "author_addresses.id").find(author_addresses(:david_address).id) + aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: Arel.sql("author_addresses.id")).find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_order_on_association - aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: "authors.id").find(author_addresses(:david_address).id) + aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: Arel.sql("authors.id")).find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_order_on_nested_association - aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: "posts.id").find(author_addresses(:david_address).id) + aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: Arel.sql("posts.id")).find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end @@ -364,31 +364,31 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_association_loading_with_belongs_to_and_limit - comments = Comment.all.merge!(includes: :post, limit: 5, order: "comments.id").to_a + comments = Comment.all.merge!(includes: :post, limit: 5, order: Arel.sql("comments.id")).to_a assert_equal 5, comments.length assert_equal [1, 2, 3, 5, 6], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_conditions - comments = Comment.all.merge!(includes: :post, where: "post_id = 4", limit: 3, order: "comments.id").to_a + comments = Comment.all.merge!(includes: :post, where: "post_id = 4", limit: 3, order: Arel.sql("comments.id")).to_a assert_equal 3, comments.length assert_equal [5, 6, 7], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset - comments = Comment.all.merge!(includes: :post, limit: 3, offset: 2, order: "comments.id").to_a + comments = Comment.all.merge!(includes: :post, limit: 3, offset: 2, order: Arel.sql("comments.id")).to_a assert_equal 3, comments.length assert_equal [3, 5, 6], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions - comments = Comment.all.merge!(includes: :post, where: "post_id = 4", limit: 3, offset: 1, order: "comments.id").to_a + comments = Comment.all.merge!(includes: :post, where: "post_id = 4", limit: 3, offset: 1, order: Arel.sql("comments.id")).to_a assert_equal 3, comments.length assert_equal [6, 7, 8], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array - comments = Comment.all.merge!(includes: :post, where: ["post_id = ?", 4], limit: 3, offset: 1, order: "comments.id").to_a + comments = Comment.all.merge!(includes: :post, where: ["post_id = ?", 4], limit: 3, offset: 1, order: Arel.sql("comments.id")).to_a assert_equal 3, comments.length assert_equal [6, 7, 8], comments.collect(&:id) end @@ -402,7 +402,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_conditions_hash comments = [] assert_nothing_raised do - comments = Comment.all.merge!(includes: :post, where: { posts: { id: 4 } }, limit: 3, order: "comments.id").to_a + comments = Comment.all.merge!(includes: :post, where: { posts: { id: 4 } }, limit: 3, order: Arel.sql("comments.id")).to_a end assert_equal 3, comments.length assert_equal [5, 6, 7], comments.collect(&:id) @@ -420,25 +420,25 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_order_string_with_unquoted_table_name assert_nothing_raised do - Comment.all.merge!(includes: :post, order: "posts.id").to_a + Comment.all.merge!(includes: :post, order: Arel.sql("posts.id")).to_a end end def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name quoted_posts_id = Comment.connection.quote_table_name("posts") + "." + Comment.connection.quote_column_name("id") assert_nothing_raised do - Comment.includes(:post).references(:posts).order(quoted_posts_id) + Comment.includes(:post).references(:posts).order(Arel.sql(quoted_posts_id)) end end def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations - posts = Post.all.merge!(includes: [:author, :very_special_comment], limit: 1, order: "posts.id").to_a + posts = Post.all.merge!(includes: [:author, :very_special_comment], limit: 1, order: Arel.sql("posts.id")).to_a assert_equal 1, posts.length assert_equal [1], posts.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations - posts = Post.all.merge!(includes: [:author, :very_special_comment], limit: 1, offset: 1, order: "posts.id").to_a + posts = Post.all.merge!(includes: [:author, :very_special_comment], limit: 1, offset: 1, order: Arel.sql("posts.id")).to_a assert_equal 1, posts.length assert_equal [2], posts.collect(&:id) end @@ -508,9 +508,9 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_through - posts_with_comments = people(:michael).posts.merge(includes: :comments, order: "posts.id").to_a - posts_with_author = people(:michael).posts.merge(includes: :author, order: "posts.id").to_a - posts_with_comments_and_author = people(:michael).posts.merge(includes: [ :comments, :author ], order: "posts.id").to_a + posts_with_comments = people(:michael).posts.merge(includes: :comments, order: Arel.sql("posts.id")).to_a + posts_with_author = people(:michael).posts.merge(includes: :author, order: Arel.sql("posts.id")).to_a + posts_with_comments_and_author = people(:michael).posts.merge(includes: [ :comments, :author ], order: Arel.sql("posts.id")).to_a assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum + post.comments.size } assert_equal authors(:david), assert_no_queries { posts_with_author.first.author } assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author } @@ -526,7 +526,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_through_an_sti_join_model - author = Author.all.merge!(includes: :special_post_comments, order: "authors.id").first + author = Author.all.merge!(includes: :special_post_comments, order: Arel.sql("authors.id")).first assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments } end @@ -539,14 +539,14 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both - author = Author.all.merge!(includes: :special_nonexistent_post_comments, order: "authors.id").first + author = Author.all.merge!(includes: :special_nonexistent_post_comments, order: Arel.sql("authors.id")).first assert_equal [], author.special_nonexistent_post_comments end def test_eager_with_has_many_through_join_model_with_conditions assert_equal Author.all.merge!(includes: :hello_post_comments, - order: "authors.id").first.hello_post_comments.sort_by(&:id), - Author.all.merge!(order: "authors.id").first.hello_post_comments.sort_by(&:id) + order: Arel.sql("authors.id")).first.hello_post_comments.sort_by(&:id), + Author.all.merge!(order: Arel.sql("authors.id")).first.hello_post_comments.sort_by(&:id) end def test_eager_with_has_many_through_join_model_with_conditions_on_top_level @@ -573,19 +573,19 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_and_limit - posts = Post.all.merge!(order: "posts.id asc", includes: [ :author, :comments ], limit: 2).to_a + posts = Post.all.merge!(order: Arel.sql("posts.id asc"), includes: [ :author, :comments ], limit: 2).to_a assert_equal 2, posts.size assert_equal 3, posts.inject(0) { |sum, post| sum + post.comments.size } end def test_eager_with_has_many_and_limit_and_conditions - posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, where: "posts.body = 'hello'", order: "posts.id").to_a + posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, where: "posts.body = 'hello'", order: Arel.sql("posts.id")).to_a assert_equal 2, posts.size assert_equal [4, 5], posts.collect(&:id) end def test_eager_with_has_many_and_limit_and_conditions_array - posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, where: [ "posts.body = ?", "hello" ], order: "posts.id").to_a + posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, where: [ "posts.body = ?", "hello" ], order: Arel.sql("posts.id")).to_a assert_equal 2, posts.size assert_equal [4, 5], posts.collect(&:id) end @@ -643,7 +643,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_and_belongs_to_many_and_limit - posts = Post.all.merge!(includes: :categories, order: "posts.id", limit: 3).to_a + posts = Post.all.merge!(includes: :categories, order: Arel.sql("posts.id"), limit: 3).to_a assert_equal 3, posts.size assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size @@ -709,7 +709,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_association_loading_with_habtm - posts = Post.all.merge!(includes: :categories, order: "posts.id").to_a + posts = Post.all.merge!(includes: :categories, order: Arel.sql("posts.id")).to_a assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size assert_equal 0, posts[2].categories.size @@ -874,14 +874,14 @@ class EagerAssociationTest < ActiveRecord::TestCase posts(:thinking, :sti_comments), Post.all.merge!( includes: [:author, :comments], where: { "authors.name" => "David" }, - order: "UPPER(posts.title)", limit: 2, offset: 1 + order: Arel.sql("UPPER(posts.title)"), limit: 2, offset: 1 ).to_a ) assert_equal( posts(:sti_post_and_comments, :sti_comments), Post.all.merge!( includes: [:author, :comments], where: { "authors.name" => "David" }, - order: "UPPER(posts.title) DESC", limit: 2, offset: 1 + order: Arel.sql("UPPER(posts.title) DESC"), limit: 2, offset: 1 ).to_a ) end @@ -891,14 +891,14 @@ class EagerAssociationTest < ActiveRecord::TestCase posts(:thinking, :sti_comments), Post.all.merge!( includes: [:author, :comments], where: { "authors.name" => "David" }, - order: ["UPPER(posts.title)", "posts.id"], limit: 2, offset: 1 + order: [Arel.sql("UPPER(posts.title)"), Arel.sql("posts.id")], limit: 2, offset: 1 ).to_a ) assert_equal( posts(:sti_post_and_comments, :sti_comments), Post.all.merge!( includes: [:author, :comments], where: { "authors.name" => "David" }, - order: ["UPPER(posts.title) DESC", "posts.id"], limit: 2, offset: 1 + order: [Arel.sql("UPPER(posts.title) DESC"), Arel.sql("posts.id")], limit: 2, offset: 1 ).to_a ) end @@ -909,7 +909,7 @@ class EagerAssociationTest < ActiveRecord::TestCase Person.references(:number1_fans_people).merge( includes: [:readers, :primary_contact, :number1_fan], where: "number1_fans_people.first_name like 'M%'", - order: "people.id", limit: 2, offset: 0 + order: Arel.sql("people.id"), limit: 2, offset: 0 ).to_a ) end @@ -1089,12 +1089,12 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_order_on_join_table_with_include_and_limit - assert_equal 5, Developer.all.merge!(includes: "projects", order: "developers_projects.joined_on DESC", limit: 5).to_a.size + assert_equal 5, Developer.all.merge!(includes: "projects", order: Arel.sql("developers_projects.joined_on DESC"), limit: 5).to_a.size end def test_eager_loading_with_order_on_joined_table_preloads posts = assert_queries(2) do - Post.all.merge!(joins: :comments, includes: :author, order: "comments.id DESC").to_a + Post.all.merge!(joins: :comments, includes: :author, order: Arel.sql("comments.id DESC")).to_a end assert_equal posts(:eager_other), posts[1] assert_equal authors(:mary), assert_no_queries { posts[1].author } @@ -1102,18 +1102,18 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_loading_with_conditions_on_joined_table_preloads posts = assert_queries(2) do - Post.all.merge!(select: "distinct posts.*", includes: :author, joins: [:comments], where: "comments.body like 'Thank you%'", order: "posts.id").to_a + Post.all.merge!(select: "distinct posts.*", includes: :author, joins: [:comments], where: "comments.body like 'Thank you%'", order: Arel.sql("posts.id")).to_a end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author } posts = assert_queries(2) do - Post.all.merge!(includes: :author, joins: { taggings: :tag }, where: "tags.name = 'General'", order: "posts.id").to_a + Post.all.merge!(includes: :author, joins: { taggings: :tag }, where: "tags.name = 'General'", order: Arel.sql("posts.id")).to_a end assert_equal posts(:welcome, :thinking), posts posts = assert_queries(2) do - Post.all.merge!(includes: :author, joins: { taggings: { tag: :taggings } }, where: "taggings_tags.super_tag_id=2", order: "posts.id").to_a + Post.all.merge!(includes: :author, joins: { taggings: { tag: :taggings } }, where: "taggings_tags.super_tag_id=2", order: Arel.sql("posts.id")).to_a end assert_equal posts(:welcome, :thinking), posts end @@ -1132,13 +1132,13 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_loading_with_conditions_on_string_joined_table_preloads posts = assert_queries(2) do - Post.all.merge!(select: "distinct posts.*", includes: :author, joins: "INNER JOIN comments on comments.post_id = posts.id", where: "comments.body like 'Thank you%'", order: "posts.id").to_a + Post.all.merge!(select: "distinct posts.*", includes: :author, joins: "INNER JOIN comments on comments.post_id = posts.id", where: "comments.body like 'Thank you%'", order: Arel.sql("posts.id")).to_a end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author } posts = assert_queries(2) do - Post.all.merge!(select: "distinct posts.*", includes: :author, joins: ["INNER JOIN comments on comments.post_id = posts.id"], where: "comments.body like 'Thank you%'", order: "posts.id").to_a + Post.all.merge!(select: "distinct posts.*", includes: :author, joins: ["INNER JOIN comments on comments.post_id = posts.id"], where: "comments.body like 'Thank you%'", order: Arel.sql("posts.id")).to_a end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author } @@ -1146,7 +1146,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_loading_with_select_on_joined_table_preloads posts = assert_queries(2) do - Post.all.merge!(select: "posts.*, authors.name as author_name", includes: :comments, joins: :author, order: "posts.id").to_a + Post.all.merge!(select: "posts.*, authors.name as author_name", includes: :comments, joins: :author, order: Arel.sql("posts.id")).to_a end assert_equal "David", posts[0].author_name assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments } @@ -1190,7 +1190,7 @@ class EagerAssociationTest < ActiveRecord::TestCase if current_adapter?(:OracleAdapter) firm = Firm.all.merge!(includes: :clients_using_primary_key, order: "clients_using_primary_keys_companies"[0, 30] + ".name").find(1) else - firm = Firm.all.merge!(includes: :clients_using_primary_key, order: "clients_using_primary_keys_companies.name").find(1) + firm = Firm.all.merge!(includes: :clients_using_primary_key, order: Arel.sql("clients_using_primary_keys_companies.name")).find(1) end assert_no_queries do assert_equal expected, firm.clients_using_primary_key @@ -1199,7 +1199,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_preload_has_one_using_primary_key expected = accounts(:signals37) - firm = Firm.all.merge!(includes: :account_using_primary_key, order: "companies.id").first + firm = Firm.all.merge!(includes: :account_using_primary_key, order: Arel.sql("companies.id")).first assert_no_queries do assert_equal expected, firm.account_using_primary_key end @@ -1207,7 +1207,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_include_has_one_using_primary_key expected = accounts(:signals37) - firm = Firm.all.merge!(includes: :account_using_primary_key, order: "accounts.id").to_a.detect { |f| f.id == 1 } + firm = Firm.all.merge!(includes: :account_using_primary_key, order: Arel.sql("accounts.id")).to_a.detect { |f| f.id == 1 } assert_no_queries do assert_equal expected, firm.account_using_primary_key end @@ -1269,7 +1269,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_joins_with_includes_should_preload_via_joins - post = assert_queries(1) { Post.includes(:comments).joins(:comments).order("posts.id desc").to_a.first } + post = assert_queries(1) { Post.includes(:comments).joins(:comments).order(Arel.sql("posts.id desc")).to_a.first } assert_queries(0) do assert_not_equal 0, post.comments.to_a.count @@ -1278,16 +1278,16 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_join_eager_with_empty_order_should_generate_valid_sql assert_nothing_raised do - Post.includes(:comments).order("").where(comments: { body: "Thank you for the welcome" }).first + Post.includes(:comments).order(Arel.sql("")).where(comments: { body: "Thank you for the welcome" }).first end end def test_deep_including_through_habtm # warm up habtm cache - posts = Post.all.merge!(includes: { categories: :categorizations }, order: "posts.id").to_a + posts = Post.all.merge!(includes: { categories: :categorizations }, order: Arel.sql("posts.id")).to_a posts[0].categories[0].categorizations.length - posts = Post.all.merge!(includes: { categories: :categorizations }, order: "posts.id").to_a + posts = Post.all.merge!(includes: { categories: :categorizations }, order: Arel.sql("posts.id")).to_a assert_no_queries { assert_equal 2, posts[0].categories[0].categorizations.length } assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length } assert_no_queries { assert_equal 2, posts[1].categories[0].categorizations.length } @@ -1382,7 +1382,7 @@ class EagerAssociationTest < ActiveRecord::TestCase test "preloading associations with string joins and order references" do author = assert_queries(2) { - Author.includes(:posts).joins("LEFT JOIN posts ON posts.author_id = authors.id").order("posts.title DESC").first + Author.includes(:posts).joins("LEFT JOIN posts ON posts.author_id = authors.id").order(Arel.sql("posts.title DESC")).first } assert_no_queries { assert_equal 5, author.posts.size @@ -1513,6 +1513,6 @@ class EagerAssociationTest < ActiveRecord::TestCase private def find_all_ordered(klass, include = nil) - klass.order("#{klass.table_name}.#{klass.primary_key}").includes(include).to_a + klass.order(Arel.sql("#{klass.table_name}.#{klass.primary_key}")).includes(include).to_a end end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index c817d7267b..7e5655bb08 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -587,7 +587,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_find_should_append_to_association_order - ordered_developers = projects(:active_record).developers.order("projects.id") + ordered_developers = projects(:active_record).developers.order(Arel.sql("projects.id")) assert_equal ["developers.name desc, developers.id desc", "projects.id"], ordered_developers.order_values end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 6bd11a5d81..85733f9056 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -48,7 +48,7 @@ class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCa author = authors(:david) # this can fail on adapters which require ORDER BY expressions to be included in the SELECT expression # if the reorder clauses are not correctly handled - assert author.posts_with_comments_sorted_by_comment_id.where("comments.id > 0").reorder("posts.comments_count DESC", "posts.tags_count DESC").last + assert author.posts_with_comments_sorted_by_comment_id.where("comments.id > 0").reorder(Arel.sql("posts.comments_count DESC"), Arel.sql("posts.tags_count DESC")).last end end @@ -550,7 +550,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_should_append_to_association_order - ordered_clients = companies(:first_firm).clients_sorted_desc.order("companies.id") + ordered_clients = companies(:first_firm).clients_sorted_desc.order(Arel.sql("companies.id")) assert_equal ["id DESC", "companies.id"], ordered_clients.order_values end @@ -1123,7 +1123,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_deleting_updates_counter_cache - topic = Topic.order("id ASC").first + topic = Topic.order(Arel.sql("id ASC")).first assert_equal topic.replies.to_a.size, topic.replies_count topic.replies.delete(topic.replies.first) @@ -1162,7 +1162,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_pushing_association_updates_counter_cache - topic = Topic.order("id ASC").first + topic = Topic.order(Arel.sql("id ASC")).first reply = Reply.create! assert_difference "topic.reload.replies_count", 1 do @@ -1212,7 +1212,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_calling_update_attributes_on_id_changes_the_counter_cache - topic = Topic.order("id ASC").first + topic = Topic.order(Arel.sql("id ASC")).first original_count = topic.replies.to_a.size assert_equal original_count, topic.replies_count diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 046020e310..ed96eb54c1 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -71,7 +71,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def self.name; "Person"; end has_many :readers - has_many :posts, -> { order("posts.id DESC") }, through: :readers + has_many :posts, -> { order(Arel.sql("posts.id DESC")) }, through: :readers end posts = person_prime.includes(:posts).first.posts @@ -985,7 +985,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_joining_has_many_through_belongs_to - posts = Post.joins(:author_categorizations).order("posts.id"). + posts = Post.joins(:author_categorizations).order(Arel.sql("posts.id")). where("categorizations.id" => categorizations(:mary_thinking_sti).id) assert_equal [posts(:eager_other), posts(:misc_by_mary), posts(:other_by_mary)], posts diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index fe24c465b2..bef20b2ebe 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -139,7 +139,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eagerloading members = assert_queries(1) do - Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback + Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: Arel.sql("clubs.name")).to_a #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].club } @@ -147,7 +147,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eager_loading_through_polymorphic members = assert_queries(1) do - Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback + Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: Arel.sql("clubs.name")).to_a #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].sponsor_club } @@ -156,7 +156,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record Sponsor.new(sponsor_club: clubs(:crazy_club), sponsorable: members(:groucho)).save! members = assert_queries(1) do - Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name DESC").to_a #force fallback + Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: Arel.sql("clubs.name DESC")).to_a #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].sponsor_club } diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index e13cf93dcf..c39e967569 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -223,7 +223,7 @@ class InverseHasOneTests < ActiveRecord::TestCase f.man.name = "Mungo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance" - m = Man.all.merge!(where: { name: "Gordon" }, includes: :face, order: "faces.id").first + m = Man.all.merge!(where: { name: "Gordon" }, includes: :face, order: Arel.sql("faces.id")).first f = m.face assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = "Bongo" @@ -329,7 +329,7 @@ class InverseHasManyTests < ActiveRecord::TestCase assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance" end - m = Man.all.merge!(where: { name: "Gordon" }, includes: :interests, order: "interests.id").first + m = Man.all.merge!(where: { name: "Gordon" }, includes: :interests, order: Arel.sql("interests.id")).first is = m.interests is.each do |i| assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" @@ -554,7 +554,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase m.face.description = "pleasing" assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance" - f = Face.all.merge!(includes: :man, order: "men.id", where: { description: "trusting" }).first + f = Face.all.merge!(includes: :man, order: Arel.sql("men.id"), where: { description: "trusting" }).first m = f.man assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" f.description = "gormless" @@ -637,7 +637,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase m.polymorphic_face.description = "pleasing" assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance" - f = Face.all.merge!(where: { description: "confused" }, includes: :man, order: "men.id").first + f = Face.all.merge!(where: { description: "confused" }, includes: :man, order: Arel.sql("men.id")).first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" f.description = "gormless" diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 87694b0788..8f08684820 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -244,8 +244,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_has_many_through - posts = Post.all.merge!(order: "posts.id").to_a - posts_with_authors = Post.all.merge!(includes: :authors, order: "posts.id").to_a + posts = Post.all.merge!(order: Arel.sql("posts.id")).to_a + posts_with_authors = Post.all.merge!(includes: :authors, order: Arel.sql("posts.id")).to_a assert_equal posts.length, posts_with_authors.length posts.length.times do |i| assert_equal posts[i].authors.length, assert_no_queries { posts_with_authors[i].authors.length } @@ -269,8 +269,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_many_through - posts = Post.all.merge!(order: "posts.id").to_a - posts_with_tags = Post.all.merge!(includes: :tags, order: "posts.id").to_a + posts = Post.all.merge!(order: Arel.sql("posts.id")).to_a + posts_with_tags = Post.all.merge!(includes: :tags, order: Arel.sql("posts.id")).to_a assert_equal posts.length, posts_with_tags.length posts.length.times do |i| assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length } @@ -278,8 +278,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_many - posts = Post.all.merge!(order: "posts.id").to_a - posts_with_taggings = Post.all.merge!(includes: :taggings, order: "posts.id").to_a + posts = Post.all.merge!(order: Arel.sql("posts.id")).to_a + posts_with_taggings = Post.all.merge!(includes: :taggings, order: Arel.sql("posts.id")).to_a assert_equal posts.length, posts_with_taggings.length posts.length.times do |i| assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length } @@ -326,7 +326,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_with_custom_primary_key_on_has_many_source - assert_equal [authors(:david), authors(:bob)], posts(:thinking).authors_using_custom_pk.order("authors.id") + assert_equal [authors(:david), authors(:bob)], posts(:thinking).authors_using_custom_pk.order(Arel.sql("authors.id")) end def test_belongs_to_polymorphic_with_counter_cache @@ -383,19 +383,19 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_has_many_find_all - assert_equal comments(:greetings), authors(:david).comments.order("comments.id").to_a.first + assert_equal comments(:greetings), authors(:david).comments.order(Arel.sql("comments.id")).to_a.first end def test_has_many_through_has_many_find_all_with_custom_class - assert_equal comments(:greetings), authors(:david).funky_comments.order("comments.id").to_a.first + assert_equal comments(:greetings), authors(:david).funky_comments.order(Arel.sql("comments.id")).to_a.first end def test_has_many_through_has_many_find_first - assert_equal comments(:greetings), authors(:david).comments.order("comments.id").first + assert_equal comments(:greetings), authors(:david).comments.order(Arel.sql("comments.id")).first end def test_has_many_through_has_many_find_conditions - options = { where: "comments.#{QUOTED_TYPE}='SpecialComment'", order: "comments.id" } + options = { where: "comments.#{QUOTED_TYPE}='SpecialComment'", order: Arel.sql("comments.id") } assert_equal comments(:does_it_hurt), authors(:david).comments.merge(options).first end @@ -420,7 +420,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_eager_load_has_many_through_has_many - author = Author.all.merge!(where: ["name = ?", "David"], includes: :comments, order: "comments.id").first + author = Author.all.merge!(where: ["name = ?", "David"], includes: :comments, order: Arel.sql("comments.id")).first SpecialComment.new; VerySpecialComment.new assert_no_queries do assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], author.comments.collect(&:id) @@ -661,8 +661,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_preload_polymorphic_has_many_through - posts = Post.all.merge!(order: "posts.id").to_a - posts_with_tags = Post.all.merge!(includes: :tags, order: "posts.id").to_a + posts = Post.all.merge!(order: Arel.sql("posts.id")).to_a + posts_with_tags = Post.all.merge!(includes: :tags, order: Arel.sql("posts.id")).to_a assert_equal posts.length, posts_with_tags.length posts.length.times do |i| assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length } @@ -688,8 +688,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_preload_polymorphic_has_many - posts = Post.all.merge!(order: "posts.id").to_a - posts_with_taggings = Post.all.merge!(includes: :taggings, order: "posts.id").to_a + posts = Post.all.merge!(order: Arel.sql("posts.id")).to_a + posts_with_taggings = Post.all.merge!(includes: :taggings, order: Arel.sql("posts.id")).to_a assert_equal posts.length, posts_with_taggings.length posts.length.times do |i| assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length } diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index 65d30d011b..b8e8f48ae5 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -86,7 +86,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase # Through: has_many through def test_has_many_through_has_many_through_with_has_many_source_reflection luke, david = subscribers(:first), subscribers(:second) - assert_equal [luke, david, david], authors(:david).subscribers.order("subscribers.nick") + assert_equal [luke, david, david], authors(:david).subscribers.order(Arel.sql("subscribers.nick")) end def test_has_many_through_has_many_through_with_has_many_source_reflection_preload @@ -156,7 +156,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy) assert_equal [groucho_details, other_details], - members(:groucho).organization_member_details.order("member_details.id") + members(:groucho).organization_member_details.order(Arel.sql("member_details.id")) end def test_has_many_through_has_one_with_has_many_through_source_reflection_preload @@ -171,7 +171,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_one_with_has_many_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Member.where("member_details.id" => member_details(:groucho).id).order("member_details.id"), + Member.where("member_details.id" => member_details(:groucho).id).order(Arel.sql("member_details.id")), [members(:groucho), members(:some_other_guy)], :organization_member_details ) @@ -187,7 +187,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy) assert_equal [groucho_details, other_details], - members(:groucho).organization_member_details_2.order("member_details.id") + members(:groucho).organization_member_details_2.order(Arel.sql("member_details.id")) end def test_has_many_through_has_one_through_with_has_many_source_reflection_preload @@ -203,7 +203,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_one_through_with_has_many_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Member.where("member_details.id" => member_details(:groucho).id).order("member_details.id"), + Member.where("member_details.id" => member_details(:groucho).id).order(Arel.sql("member_details.id")), [members(:groucho), members(:some_other_guy)], :organization_member_details_2 ) @@ -218,7 +218,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection general, cooking = categories(:general), categories(:cooking) - assert_equal [general, cooking], authors(:bob).post_categories.order("categories.id") + assert_equal [general, cooking], authors(:bob).post_categories.order(Arel.sql("categories.id")) end def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection_preload @@ -246,7 +246,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection greetings, more = comments(:greetings), comments(:more_greetings) - assert_equal [greetings, more], categories(:technology).post_comments.order("comments.id") + assert_equal [greetings, more], categories(:technology).post_comments.order(Arel.sql("comments.id")) end def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload @@ -264,7 +264,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase Category.joins(:post_comments).first assert_includes_and_joins_equal( - Category.where("comments.id" => comments(:more_greetings).id).order("categories.id"), + Category.where("comments.id" => comments(:more_greetings).id).order(Arel.sql("categories.id")), [categories(:general), categories(:technology)], :post_comments ) end @@ -275,7 +275,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection greetings, more = comments(:greetings), comments(:more_greetings) - assert_equal [greetings, more], authors(:bob).category_post_comments.order("comments.id") + assert_equal [greetings, more], authors(:bob).category_post_comments.order(Arel.sql("comments.id")) end def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload @@ -292,7 +292,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase Author.joins(:category_post_comments).first assert_includes_and_joins_equal( - Author.where("comments.id" => comments(:does_it_hurt).id).order("authors.id"), + Author.where("comments.id" => comments(:does_it_hurt).id).order(Arel.sql("authors.id")), [authors(:david), authors(:mary)], :category_post_comments ) end @@ -327,7 +327,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase welcome_general, thinking_general = taggings(:welcome_general), taggings(:thinking_general) assert_equal [welcome_general, thinking_general], - categorizations(:david_welcome_general).post_taggings.order("taggings.id") + categorizations(:david_welcome_general).post_taggings.order(Arel.sql("taggings.id")) end def test_has_many_through_belongs_to_with_has_many_through_source_reflection_preload @@ -341,7 +341,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_belongs_to_with_has_many_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Categorization.where("taggings.id" => taggings(:welcome_general).id).order("taggings.id"), + Categorization.where("taggings.id" => taggings(:welcome_general).id).order(Arel.sql("taggings.id")), [categorizations(:david_welcome_general)], :post_taggings ) end @@ -411,7 +411,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_distinct_has_many_through_a_has_many_through_association_on_through_reflection author = authors(:david) assert_equal [subscribers(:first), subscribers(:second)], - author.distinct_subscribers.order("subscribers.nick") + author.distinct_subscribers.order(Arel.sql("subscribers.nick")) end def test_nested_has_many_through_with_a_table_referenced_multiple_times @@ -436,7 +436,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase end def test_has_many_through_with_foreign_key_option_on_through_reflection - assert_equal [posts(:welcome), posts(:authorless)], people(:david).agents_posts.order("posts.id") + assert_equal [posts(:welcome), posts(:authorless)], people(:david).agents_posts.order(Arel.sql("posts.id")) assert_equal [authors(:david)], references(:david_unicyclist).agents_posts_authors references = Reference.joins(:agents_posts_authors).where("authors.id" => authors(:david).id) @@ -444,7 +444,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase end def test_has_many_through_with_foreign_key_option_on_source_reflection - assert_equal [people(:michael), people(:susan)], jobs(:unicyclist).agents.order("people.id") + assert_equal [people(:michael), people(:susan)], jobs(:unicyclist).agents.order(Arel.sql("people.id")) jobs = Job.joins(:agents) assert_equal [jobs(:unicyclist), jobs(:unicyclist)], jobs diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index f0ef522515..e2e4aa22f4 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -439,7 +439,7 @@ class BasicsTest < ActiveRecord::TestCase if current_adapter?(:Mysql2Adapter) def test_update_all_with_order_and_limit - assert_equal 1, Topic.limit(1).order("id DESC").update_all(content: "bulk updated!") + assert_equal 1, Topic.limit(1).order(Arel.sql("id DESC")).update_all(content: "bulk updated!") end end @@ -1081,11 +1081,11 @@ class BasicsTest < ActiveRecord::TestCase def test_find_last last = Developer.last - assert_equal last, Developer.all.merge!(order: "id desc").first + assert_equal last, Developer.all.merge!(order: Arel.sql("id desc")).first end def test_last - assert_equal Developer.all.merge!(order: "id desc").first, Developer.last + assert_equal Developer.all.merge!(order: Arel.sql("id desc")).first, Developer.last end def test_all @@ -1095,27 +1095,27 @@ class BasicsTest < ActiveRecord::TestCase end def test_all_with_conditions - assert_equal Developer.all.merge!(order: "id desc").to_a, Developer.order("id desc").to_a + assert_equal Developer.all.merge!(order: Arel.sql("id desc")).to_a, Developer.order(Arel.sql("id desc")).to_a end def test_find_ordered_last - last = Developer.all.merge!(order: "developers.salary ASC").last - assert_equal last, Developer.all.merge!(order: "developers.salary ASC").to_a.last + last = Developer.all.merge!(order: Arel.sql("developers.salary ASC")).last + assert_equal last, Developer.all.merge!(order: Arel.sql("developers.salary ASC")).to_a.last end def test_find_reverse_ordered_last - last = Developer.all.merge!(order: "developers.salary DESC").last - assert_equal last, Developer.all.merge!(order: "developers.salary DESC").to_a.last + last = Developer.all.merge!(order: Arel.sql("developers.salary DESC")).last + assert_equal last, Developer.all.merge!(order: Arel.sql("developers.salary DESC")).to_a.last end def test_find_multiple_ordered_last - last = Developer.all.merge!(order: "developers.name, developers.salary DESC").last - assert_equal last, Developer.all.merge!(order: "developers.name, developers.salary DESC").to_a.last + last = Developer.all.merge!(order: Arel.sql("developers.name, developers.salary DESC")).last + assert_equal last, Developer.all.merge!(order: Arel.sql("developers.name, developers.salary DESC")).to_a.last end def test_find_keeps_multiple_order_values - combined = Developer.all.merge!(order: "developers.name, developers.salary").to_a - assert_equal combined, Developer.all.merge!(order: ["developers.name", "developers.salary"]).to_a + combined = Developer.all.merge!(order: Arel.sql("developers.name, developers.salary")).to_a + assert_equal combined, Developer.all.merge!(order: [Arel.sql("developers.name"), Arel.sql("developers.salary")]).to_a end def test_find_keeps_multiple_group_values diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index be8aeed5ac..ff345d5f0e 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -9,7 +9,7 @@ class EachTest < ActiveRecord::TestCase fixtures :posts, :subscribers def setup - @posts = Post.order("id asc") + @posts = Post.order(Arel.sql("id asc")) @total = Post.count Post.count("id") # preheat arel's table cache end @@ -101,7 +101,7 @@ class EachTest < ActiveRecord::TestCase previous_logger = ActiveRecord::Base.logger ActiveRecord::Base.logger = nil assert_nothing_raised do - Post.order("comments_count DESC").find_each { |post| post } + Post.order(Arel.sql("comments_count DESC")).find_each { |post| post } end ensure ActiveRecord::Base.logger = previous_logger @@ -233,7 +233,7 @@ class EachTest < ActiveRecord::TestCase end def test_find_in_batches_should_use_any_column_as_primary_key - nick_order_subscribers = Subscriber.order("nick asc") + nick_order_subscribers = Subscriber.order(Arel.sql("nick asc")) start_nick = nick_order_subscribers.second.nick subscribers = [] @@ -329,7 +329,7 @@ class EachTest < ActiveRecord::TestCase end def test_in_batches_each_record_should_be_ordered_by_id - ids = Post.order("id ASC").pluck(:id) + ids = Post.order(Arel.sql("id ASC")).pluck(:id) assert_queries(6) do Post.in_batches(of: 2).each_record.with_index do |post, i| assert_equal ids[i], post.id @@ -384,7 +384,7 @@ class EachTest < ActiveRecord::TestCase end def test_in_batches_should_start_from_the_start_option - post = Post.order("id ASC").where("id >= ?", 2).first + post = Post.order(Arel.sql("id ASC")).where("id >= ?", 2).first assert_queries(2) do relation = Post.in_batches(of: 1, start: 2).first assert_equal post, relation.first @@ -392,7 +392,7 @@ class EachTest < ActiveRecord::TestCase end def test_in_batches_should_end_at_the_finish_option - post = Post.order("id DESC").where("id <= ?", 5).first + post = Post.order(Arel.sql("id DESC")).where("id <= ?", 5).first assert_queries(7) do relation = Post.in_batches(of: 1, finish: 5, load: true).reverse_each.first assert_equal post, relation.last @@ -451,7 +451,7 @@ class EachTest < ActiveRecord::TestCase end def test_in_batches_should_use_any_column_as_primary_key - nick_order_subscribers = Subscriber.order("nick asc") + nick_order_subscribers = Subscriber.order(Arel.sql("nick asc")) start_nick = nick_order_subscribers.second.nick subscribers = [] diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index d8bc917e7f..65ebfcd989 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -239,19 +239,19 @@ class FinderTest < ActiveRecord::TestCase # Ensure +exists?+ runs without an error by excluding order value. def test_exists_with_order - assert_equal true, Topic.order("invalid sql here").exists? + assert_equal true, Topic.order(Arel.sql("invalid sql here")).exists? end def test_exists_with_joins - assert_equal true, Topic.joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists? + assert_equal true, Topic.joins(:replies).where(replies_topics: { approved: true }).order(Arel.sql("replies_topics.created_at DESC")).exists? end def test_exists_with_left_joins - assert_equal true, Topic.left_joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists? + assert_equal true, Topic.left_joins(:replies).where(replies_topics: { approved: true }).order(Arel.sql("replies_topics.created_at DESC")).exists? end def test_exists_with_eager_load - assert_equal true, Topic.eager_load(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists? + assert_equal true, Topic.eager_load(:replies).where(replies_topics: { approved: true }).order(Arel.sql("replies_topics.created_at DESC")).exists? end def test_exists_with_includes_limit_and_empty_result @@ -267,8 +267,8 @@ class FinderTest < ActiveRecord::TestCase def test_exists_with_distinct_association_includes_limit_and_order author = Author.first - assert_equal false, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(0).exists? - assert_equal true, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(1).exists? + assert_equal false, author.unique_categorized_posts.includes(:special_comments).order(Arel.sql("comments.tags_count DESC")).limit(0).exists? + assert_equal true, author.unique_categorized_posts.includes(:special_comments).order(Arel.sql("comments.tags_count DESC")).limit(1).exists? end def test_exists_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association @@ -652,7 +652,7 @@ class FinderTest < ActiveRecord::TestCase def test_last_with_irreversible_order assert_raises(ActiveRecord::IrreversibleOrderError) do - Topic.order("coalesce(author_name, title)").last + Topic.order(Arel.sql("coalesce(author_name, title)")).last end end @@ -813,9 +813,9 @@ class FinderTest < ActiveRecord::TestCase end def test_hash_condition_find_with_array - p1, p2 = Post.limit(2).order("id asc").to_a - assert_equal [p1, p2], Post.where(id: [p1, p2]).order("id asc").to_a - assert_equal [p1, p2], Post.where(id: [p1, p2.id]).order("id asc").to_a + p1, p2 = Post.limit(2).order(Arel.sql("id asc")).to_a + assert_equal [p1, p2], Post.where(id: [p1, p2]).order(Arel.sql("id asc")).to_a + assert_equal [p1, p2], Post.where(id: [p1, p2.id]).order(Arel.sql("id asc")).to_a end def test_hash_condition_find_with_nil @@ -1013,7 +1013,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_one_attribute_with_several_options - assert_equal accounts(:unknown), Account.order("id DESC").where("id != ?", 3).find_by_credit_limit(50) + assert_equal accounts(:unknown), Account.order(Arel.sql("id DESC")).where("id != ?", 3).find_by_credit_limit(50) end def test_find_by_one_missing_attribute @@ -1041,7 +1041,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal devs[2], Developer.offset(2).first assert_equal devs[-3], Developer.offset(2).last - assert_equal devs[-3], Developer.offset(2).order("id DESC").first + assert_equal devs[-3], Developer.offset(2).order(Arel.sql("id DESC")).first end def test_find_by_nil_attribute @@ -1094,9 +1094,9 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_records - p1, p2 = Post.limit(2).order("id asc").to_a - assert_equal [p1, p2], Post.where(["id in (?)", [p1, p2]]).order("id asc") - assert_equal [p1, p2], Post.where(["id in (?)", [p1, p2.id]]).order("id asc") + p1, p2 = Post.limit(2).order(Arel.sql("id asc")).to_a + assert_equal [p1, p2], Post.where(["id in (?)", [p1, p2]]).order(Arel.sql("id asc")) + assert_equal [p1, p2], Post.where(["id in (?)", [p1, p2.id]]).order(Arel.sql("id asc")) end def test_select_value @@ -1125,18 +1125,18 @@ class FinderTest < ActiveRecord::TestCase def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct assert_equal 2, Post.includes(authors: :author_address). where.not(author_addresses: { id: nil }). - order("author_addresses.id DESC").limit(2).to_a.size + order(Arel.sql("author_addresses.id DESC")).limit(2).to_a.size assert_equal 3, Post.includes(author: :author_address, authors: :author_address). where.not(author_addresses_authors: { id: nil }). - order("author_addresses_authors.id DESC").limit(3).to_a.size + order(Arel.sql("author_addresses_authors.id DESC")).limit(3).to_a.size end def test_find_with_nil_inside_set_passed_for_one_attribute client_of = Company. where(client_of: [2, 1, nil], name: ["37signals", "Summit", "Microsoft"]). - order("client_of DESC"). + order(Arel.sql("client_of DESC")). map(&:client_of) assert_includes client_of, nil @@ -1146,7 +1146,7 @@ class FinderTest < ActiveRecord::TestCase def test_find_with_nil_inside_set_passed_for_attribute client_of = Company. where(client_of: [nil]). - order("client_of DESC"). + order(Arel.sql("client_of DESC")). map(&:client_of) assert_equal [], client_of.compact @@ -1155,7 +1155,7 @@ class FinderTest < ActiveRecord::TestCase def test_with_limiting_with_custom_select posts = Post.references(:authors).merge( includes: :author, select: 'posts.*, authors.id as "author_id"', - limit: 3, order: "posts.id" + limit: 3, order: Arel.sql("posts.id") ).to_a assert_equal 3, posts.size assert_equal [0, 1, 1], posts.map(&:author_id).sort diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index 953e0fee76..3b35df526f 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -13,10 +13,10 @@ class RelationMergingTest < ActiveRecord::TestCase fixtures :developers, :comments, :authors, :author_addresses, :posts def test_relation_merging - devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order("id ASC").where("id < 3")) + devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order(Arel.sql("id ASC")).where("id < 3")) assert_equal [developers(:david), developers(:jamis)], devs.to_a - dev_with_count = Developer.limit(1).merge(Developer.order("id DESC")).merge(Developer.select("developers.*")) + dev_with_count = Developer.limit(1).merge(Developer.order(Arel.sql("id DESC"))).merge(Developer.select("developers.*")) assert_equal [developers(:poor_jamis)], dev_with_count.to_a end @@ -47,8 +47,8 @@ class RelationMergingTest < ActiveRecord::TestCase def test_relation_merging_with_eager_load relations = [] - relations << Post.order("comments.id DESC").merge(Post.eager_load(:last_comment)).merge(Post.all) - relations << Post.eager_load(:last_comment).merge(Post.order("comments.id DESC")).merge(Post.all) + relations << Post.order(Arel.sql("comments.id DESC")).merge(Post.eager_load(:last_comment)).merge(Post.all) + relations << Post.eager_load(:last_comment).merge(Post.order(Arel.sql("comments.id DESC"))).merge(Post.all) relations.each do |posts| post = posts.find { |p| p.id == 1 } @@ -57,7 +57,7 @@ class RelationMergingTest < ActiveRecord::TestCase end def test_relation_merging_with_locks - devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2)) + devs = Developer.lock.where("salary >= 80000").order(Arel.sql("id DESC")).merge(Developer.limit(2)) assert devs.locked? end @@ -118,7 +118,7 @@ class MergingDifferentRelationsTest < ActiveRecord::TestCase test "merging where relations" do hello_by_bob = Post.where(body: "hello").joins(:author). - merge(Author.where(name: "Bob")).order("posts.id").pluck(Arel.sql("posts.id")) + merge(Author.where(name: "Bob")).order(Arel.sql("posts.id")).pluck(Arel.sql("posts.id")) assert_equal [posts(:misc_by_bob).id, posts(:other_by_bob).id], hello_by_bob @@ -131,7 +131,7 @@ class MergingDifferentRelationsTest < ActiveRecord::TestCase assert_equal ["Bob", "Bob", "David"], posts_by_author_name posts_by_author_name = Post.limit(3).joins(:author). - merge(Author.order("name")).pluck(Arel.sql("authors.name")) + merge(Author.order(Arel.sql("name"))).pluck(Arel.sql("authors.name")) assert_equal ["Bob", "Bob", "David"], posts_by_author_name end diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index ad3700b73a..d845c07a50 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -94,7 +94,7 @@ module ActiveRecord end test "reverse_order!" do - @relation = Post.order("title ASC, comments_count DESC") + @relation = Post.order(Arel.sql("title ASC, comments_count DESC")) relation.reverse_order! diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb index 7e418f9c7d..2abc3b7fe8 100644 --- a/activerecord/test/cases/relation/or_test.rb +++ b/activerecord/test/cases/relation/or_test.rb @@ -50,15 +50,15 @@ module ActiveRecord end def test_or_preserves_other_querying_methods - expected = Post.where("id = 1 or id = 2 or id = 3").order("body asc").to_a - partial = Post.order("body asc") + expected = Post.where("id = 1 or id = 2 or id = 3").order(Arel.sql("body asc")).to_a + partial = Post.order(Arel.sql("body asc")) assert_equal expected, partial.where("id = 1").or(partial.where(id: [2, 3])).to_a - assert_equal expected, Post.order("body asc").where("id = 1").or(Post.order("body asc").where(id: [2, 3])).to_a + assert_equal expected, Post.order(Arel.sql("body asc")).where("id = 1").or(Post.order(Arel.sql("body asc")).where(id: [2, 3])).to_a end def test_or_with_incompatible_relations error = assert_raises ArgumentError do - Post.order("body asc").where("id = 1").or(Post.order("id desc").where(id: [2, 3])).to_a + Post.order(Arel.sql("body asc")).where("id = 1").or(Post.order(Arel.sql("id desc")).where(id: [2, 3])).to_a end assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message @@ -78,12 +78,12 @@ module ActiveRecord def test_or_with_unscope_order expected = Post.where("id = 1 or id = 2") - assert_equal expected, Post.order("body asc").where("id = 1").unscope(:order).or(Post.where("id = 2")).to_a + assert_equal expected, Post.order(Arel.sql("body asc")).where("id = 1").unscope(:order).or(Post.where("id = 2")).to_a end def test_or_with_incompatible_unscope error = assert_raises ArgumentError do - Post.order("body asc").where("id = 1").or(Post.order("body asc").where("id = 2").unscope(:order)).to_a + Post.order(Arel.sql("body asc")).where("id = 1").or(Post.order(Arel.sql("body asc")).where("id = 2").unscope(:order)).to_a end assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message @@ -101,7 +101,7 @@ module ActiveRecord end def test_or_inside_named_scope - expected = Post.where("body LIKE '\%a\%' OR title LIKE ?", "%'%").order("id DESC").to_a + expected = Post.where("body LIKE '\%a\%' OR title LIKE ?", "%'%").order(Arel.sql("id DESC")).to_a assert_equal expected, Post.order(id: :desc).typographically_interesting end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 72433d1e8e..906f3499dd 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -102,7 +102,7 @@ class RelationTest < ActiveRecord::TestCase end def test_scoped_first - topics = Topic.all.order("id ASC") + topics = Topic.all.order(Arel.sql("id ASC")) assert_queries(1) do 2.times { assert_equal "The First Topic", topics.first.title } @@ -112,7 +112,7 @@ class RelationTest < ActiveRecord::TestCase end def test_loaded_first - topics = Topic.all.order("id ASC") + topics = Topic.all.order(Arel.sql("id ASC")) topics.load # force load assert_no_queries do @@ -123,7 +123,7 @@ class RelationTest < ActiveRecord::TestCase end def test_loaded_first_with_limit - topics = Topic.all.order("id ASC") + topics = Topic.all.order(Arel.sql("id ASC")) topics.load # force load assert_no_queries do @@ -135,7 +135,7 @@ class RelationTest < ActiveRecord::TestCase end def test_first_get_more_than_available - topics = Topic.all.order("id ASC") + topics = Topic.all.order(Arel.sql("id ASC")) unloaded_first = topics.first(10) topics.load # force load @@ -238,7 +238,7 @@ class RelationTest < ActiveRecord::TestCase end def test_reverse_order_with_function - topics = Topic.order("length(title)").reverse_order + topics = Topic.order(Arel.sql("length(title)")).reverse_order assert_equal topics(:second).title, topics.first.title end @@ -248,24 +248,24 @@ class RelationTest < ActiveRecord::TestCase end def test_reverse_order_with_function_other_predicates - topics = Topic.order("author_name, length(title), id").reverse_order + topics = Topic.order(Arel.sql("author_name, length(title), id")).reverse_order assert_equal topics(:second).title, topics.first.title - topics = Topic.order("length(author_name), id, length(title)").reverse_order + topics = Topic.order(Arel.sql("length(author_name), id, length(title)")).reverse_order assert_equal topics(:fifth).title, topics.first.title end def test_reverse_order_with_multiargument_function assert_raises(ActiveRecord::IrreversibleOrderError) do - Topic.order("concat(author_name, title)").reverse_order + Topic.order(Arel.sql("concat(author_name, title)")).reverse_order end assert_raises(ActiveRecord::IrreversibleOrderError) do - Topic.order("concat(lower(author_name), title)").reverse_order + Topic.order(Arel.sql("concat(lower(author_name), title)")).reverse_order end assert_raises(ActiveRecord::IrreversibleOrderError) do - Topic.order("concat(author_name, lower(title))").reverse_order + Topic.order(Arel.sql("concat(author_name, lower(title))")).reverse_order end assert_raises(ActiveRecord::IrreversibleOrderError) do - Topic.order("concat(lower(author_name), title, length(title)").reverse_order + Topic.order(Arel.sql("concat(lower(author_name), title, length(title)")).reverse_order end end @@ -277,10 +277,10 @@ class RelationTest < ActiveRecord::TestCase def test_reverse_order_with_nulls_first_or_last assert_raises(ActiveRecord::IrreversibleOrderError) do - Topic.order("title NULLS FIRST").reverse_order + Topic.order(Arel.sql("title NULLS FIRST")).reverse_order end assert_raises(ActiveRecord::IrreversibleOrderError) do - Topic.order("title nulls last").reverse_order + Topic.order(Arel.sql("title nulls last")).reverse_order end end @@ -319,7 +319,7 @@ class RelationTest < ActiveRecord::TestCase end def test_raising_exception_on_invalid_hash_params - e = assert_raise(ArgumentError) { Topic.order(:name, "id DESC", id: :asfsdf) } + e = assert_raise(ArgumentError) { Topic.order(Arel.sql("name"), Arel.sql("id DESC"), id: :asfsdf) } assert_equal 'Direction "asfsdf" is invalid. Valid directions are: [:asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]', e.message end @@ -365,7 +365,7 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_order_and_take - entrants = Entrant.order("id ASC").limit(2).to_a + entrants = Entrant.order(Arel.sql("id ASC")).limit(2).to_a assert_equal 2, entrants.size assert_equal entrants(:first).name, entrants.first.name @@ -373,18 +373,21 @@ class RelationTest < ActiveRecord::TestCase def test_finding_with_cross_table_order_and_limit tags = Tag.includes(:taggings). - order("tags.name asc", "taggings.taggable_id asc", "REPLACE('abc', taggings.taggable_type, taggings.taggable_type)"). - limit(1).to_a + order( + Arel.sql("tags.name asc"), + Arel.sql("taggings.taggable_id asc"), + Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)") + ).limit(1).to_a assert_equal 1, tags.length end def test_finding_with_complex_order_and_limit - tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a + tags = Tag.includes(:taggings).references(:taggings).order(Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)")).limit(1).to_a assert_equal 1, tags.length end def test_finding_with_complex_order - tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a + tags = Tag.includes(:taggings).references(:taggings).order(Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)")).to_a assert_equal 3, tags.length end @@ -400,12 +403,12 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_order_limit_and_offset - entrants = Entrant.order("id ASC").limit(2).offset(1) + entrants = Entrant.order(Arel.sql("id ASC")).limit(2).offset(1) assert_equal 2, entrants.to_a.size assert_equal entrants(:second).name, entrants.first.name - entrants = Entrant.order("id ASC").limit(2).offset(2) + entrants = Entrant.order(Arel.sql("id ASC")).limit(2).offset(2) assert_equal 1, entrants.to_a.size assert_equal entrants(:third).name, entrants.first.name end @@ -504,7 +507,7 @@ class RelationTest < ActiveRecord::TestCase def test_eager_association_loading_of_stis_with_multiple_references authors = Author.eager_load(posts: { special_comments: { post: [ :special_comments, :very_special_comment ] } }). - order("comments.body, very_special_comments_posts.body").where("posts.id = 4").to_a + order(Arel.sql("comments.body, very_special_comments_posts.body")).where("posts.id = 4").to_a assert_equal [authors(:david)], authors assert_no_queries do @@ -515,27 +518,27 @@ class RelationTest < ActiveRecord::TestCase def test_find_with_preloaded_associations assert_queries(2) do - posts = Post.preload(:comments).order("posts.id") + posts = Post.preload(:comments).order(Arel.sql("posts.id")) assert posts.first.comments.first end assert_queries(2) do - posts = Post.preload(:comments).order("posts.id") + posts = Post.preload(:comments).order(Arel.sql("posts.id")) assert posts.first.comments.first end assert_queries(2) do - posts = Post.preload(:author).order("posts.id") + posts = Post.preload(:author).order(Arel.sql("posts.id")) assert posts.first.author end assert_queries(2) do - posts = Post.preload(:author).order("posts.id") + posts = Post.preload(:author).order(Arel.sql("posts.id")) assert posts.first.author end assert_queries(3) do - posts = Post.preload(:author, :comments).order("posts.id") + posts = Post.preload(:author, :comments).order(Arel.sql("posts.id")) assert posts.first.author assert posts.first.comments.first end @@ -550,22 +553,22 @@ class RelationTest < ActiveRecord::TestCase def test_find_with_included_associations assert_queries(2) do - posts = Post.includes(:comments).order("posts.id") + posts = Post.includes(:comments).order(Arel.sql("posts.id")) assert posts.first.comments.first end assert_queries(2) do - posts = Post.all.includes(:comments).order("posts.id") + posts = Post.all.includes(:comments).order(Arel.sql("posts.id")) assert posts.first.comments.first end assert_queries(2) do - posts = Post.includes(:author).order("posts.id") + posts = Post.includes(:author).order(Arel.sql("posts.id")) assert posts.first.author end assert_queries(3) do - posts = Post.includes(:author, :comments).order("posts.id") + posts = Post.includes(:author, :comments).order(Arel.sql("posts.id")) assert posts.first.author assert posts.first.comments.first end @@ -577,7 +580,7 @@ class RelationTest < ActiveRecord::TestCase end def test_includes_with_select - query = Post.select("comments_count AS ranking").order("ranking").includes(:comments) + query = Post.select("comments_count AS ranking").order(Arel.sql("ranking")).includes(:comments) .where(comments: { id: 1 }) assert_equal ["comments_count AS ranking"], query.select_values @@ -646,9 +649,9 @@ class RelationTest < ActiveRecord::TestCase def test_to_sql_on_eager_join expected = assert_sql { - Post.eager_load(:last_comment).order("comments.id DESC").to_a + Post.eager_load(:last_comment).order(Arel.sql("comments.id DESC")).to_a }.first - actual = Post.eager_load(:last_comment).order("comments.id DESC").to_sql + actual = Post.eager_load(:last_comment).order(Arel.sql("comments.id DESC")).to_sql assert_equal expected, actual end @@ -659,7 +662,7 @@ class RelationTest < ActiveRecord::TestCase end def test_loading_with_one_association_with_non_preload - posts = Post.eager_load(:last_comment).order("comments.id DESC") + posts = Post.eager_load(:last_comment).order(Arel.sql("comments.id DESC")) post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end @@ -695,7 +698,7 @@ class RelationTest < ActiveRecord::TestCase end def test_find_ids - authors = Author.order("id ASC") + authors = Author.order(Arel.sql("id ASC")) results = authors.find(authors(:david).id, authors(:mary).id) assert_kind_of Array, results @@ -979,7 +982,7 @@ class RelationTest < ActiveRecord::TestCase end def test_multiple_selects - post = Post.all.select("comments_count").select("title").order("id ASC").first + post = Post.all.select("comments_count").select("title").order(Arel.sql("id ASC")).first assert_equal "Welcome to the weblog", post.title assert_equal 2, post.comments_count end @@ -1341,7 +1344,7 @@ class RelationTest < ActiveRecord::TestCase end def test_except - relation = Post.where(author_id: 1).order("id ASC").limit(1) + relation = Post.where(author_id: 1).order(Arel.sql("id ASC")).limit(1) assert_equal [posts(:welcome)], relation.to_a author_posts = relation.except(:order, :limit) @@ -1352,7 +1355,7 @@ class RelationTest < ActiveRecord::TestCase end def test_only - relation = Post.where(author_id: 1).order("id ASC").limit(1) + relation = Post.where(author_id: 1).order(Arel.sql("id ASC")).limit(1) assert_equal [posts(:welcome)], relation.to_a author_posts = relation.only(:where) @@ -1363,7 +1366,7 @@ class RelationTest < ActiveRecord::TestCase end def test_anonymous_extension - relation = Post.where(author_id: 1).order("id ASC").extending do + relation = Post.where(author_id: 1).order(Arel.sql("id ASC")).extending do def author "lifo" end @@ -1374,7 +1377,7 @@ class RelationTest < ActiveRecord::TestCase end def test_named_extension - relation = Post.where(author_id: 1).order("id ASC").extending(Post::NamedExtension) + relation = Post.where(author_id: 1).order(Arel.sql("id ASC")).extending(Post::NamedExtension) assert_equal "lifo", relation.author assert_equal "lifo", relation.limit(1).author end @@ -1389,13 +1392,13 @@ class RelationTest < ActiveRecord::TestCase end def test_order_using_scoping - car1 = CoolCar.order("id DESC").scoping do - CoolCar.all.merge!(order: "id asc").first + car1 = CoolCar.order(Arel.sql("id DESC")).scoping do + CoolCar.all.merge!(order: Arel.sql("id asc")).first end assert_equal "zyke", car1.name - car2 = FastCar.order("id DESC").scoping do - FastCar.all.merge!(order: "id asc").first + car2 = FastCar.order(Arel.sql("id DESC")).scoping do + FastCar.all.merge!(order: Arel.sql("id asc")).first end assert_equal "zyke", car2.name end @@ -1418,7 +1421,7 @@ class RelationTest < ActiveRecord::TestCase end def test_ordering_with_extra_spaces - assert_equal authors(:david), Author.order("id DESC , name DESC").last + assert_equal authors(:david), Author.order(Arel.sql("id DESC , name DESC")).last end def test_update_all_with_blank_argument @@ -1439,7 +1442,7 @@ class RelationTest < ActiveRecord::TestCase end def test_update_all_with_joins_and_limit_and_order - comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("comments.id").limit(1) + comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order(Arel.sql("comments.id")).limit(1) assert_equal 1, comments.update_all(post_id: posts(:thinking).id) assert_equal posts(:thinking), comments(:greetings).post assert_equal posts(:welcome), comments(:more_greetings).post @@ -1454,7 +1457,7 @@ class RelationTest < ActiveRecord::TestCase end def test_update_all_with_joins_and_offset_and_order - all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("posts.id", "comments.id") + all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order(Arel.sql("posts.id"), Arel.sql("comments.id")) count = all_comments.count comments = all_comments.offset(1) @@ -1564,52 +1567,52 @@ class RelationTest < ActiveRecord::TestCase end def test_automatically_added_order_references - scope = Post.order("comments.body") + scope = Post.order(Arel.sql("comments.body")) assert_equal ["comments"], scope.references_values - scope = Post.order("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}") + scope = Post.order(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")) if current_adapter?(:OracleAdapter) assert_equal ["COMMENTS"], scope.references_values else assert_equal ["comments"], scope.references_values end - scope = Post.order("comments.body", "yaks.body") + scope = Post.order(Arel.sql("comments.body"), Arel.sql("yaks.body")) assert_equal ["comments", "yaks"], scope.references_values # Don't infer yaks, let's not go down that road again... - scope = Post.order("comments.body, yaks.body") + scope = Post.order(Arel.sql("comments.body, yaks.body")) assert_equal ["comments"], scope.references_values - scope = Post.order("comments.body asc") + scope = Post.order(Arel.sql("comments.body asc")) assert_equal ["comments"], scope.references_values - scope = Post.order("foo(comments.body)") + scope = Post.order(Arel.sql("foo(comments.body)")) assert_equal [], scope.references_values end def test_automatically_added_reorder_references - scope = Post.reorder("comments.body") + scope = Post.reorder(Arel.sql("comments.body")) assert_equal %w(comments), scope.references_values - scope = Post.reorder("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}") + scope = Post.reorder(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")) if current_adapter?(:OracleAdapter) assert_equal ["COMMENTS"], scope.references_values else assert_equal ["comments"], scope.references_values end - scope = Post.reorder("comments.body", "yaks.body") + scope = Post.reorder(Arel.sql("comments.body"), Arel.sql("yaks.body")) assert_equal %w(comments yaks), scope.references_values # Don't infer yaks, let's not go down that road again... - scope = Post.reorder("comments.body, yaks.body") + scope = Post.reorder(Arel.sql("comments.body, yaks.body")) assert_equal %w(comments), scope.references_values - scope = Post.reorder("comments.body asc") + scope = Post.reorder(Arel.sql("comments.body asc")) assert_equal %w(comments), scope.references_values - scope = Post.reorder("foo(comments.body)") + scope = Post.reorder(Arel.sql("foo(comments.body)")) assert_equal [], scope.references_values end @@ -1811,7 +1814,7 @@ class RelationTest < ActiveRecord::TestCase end test "joins with select" do - posts = Post.joins(:author).select("id", "authors.author_address_id").order("posts.id").limit(3) + posts = Post.joins(:author).select("id", "authors.author_address_id").order(Arel.sql("posts.id")).limit(3) assert_equal [1, 2, 4], posts.map(&:id) assert_equal [1, 1, 1], posts.map(&:author_address_id) end diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 716ca29eda..26f0b86703 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -13,7 +13,7 @@ class DefaultScopingTest < ActiveRecord::TestCase fixtures :developers, :posts, :comments def test_default_scope - expected = Developer.all.merge!(order: "salary DESC").to_a.collect(&:salary) + expected = Developer.all.merge!(order: Arel.sql("salary DESC")).to_a.collect(&:salary) received = DeveloperOrderedBySalary.all.collect(&:salary) assert_equal expected, received end @@ -80,20 +80,20 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_scope_overwrites_default - expected = Developer.all.merge!(order: "salary DESC, name DESC").to_a.collect(&:name) + expected = Developer.all.merge!(order: Arel.sql("salary DESC, name DESC")).to_a.collect(&:name) received = DeveloperOrderedBySalary.by_name.to_a.collect(&:name) assert_equal expected, received end def test_reorder_overrides_default_scope_order - expected = Developer.order("name DESC").collect(&:name) - received = DeveloperOrderedBySalary.reorder("name DESC").collect(&:name) + expected = Developer.order(Arel.sql("name DESC")).collect(&:name) + received = DeveloperOrderedBySalary.reorder(Arel.sql("name DESC")).collect(&:name) assert_equal expected, received end def test_order_after_reorder_combines_orders - expected = Developer.order("name DESC, id DESC").collect { |dev| [dev.name, dev.id] } - received = Developer.order("name ASC").reorder("name DESC").order("id DESC").collect { |dev| [dev.name, dev.id] } + expected = Developer.order(Arel.sql("name DESC, id DESC")).collect { |dev| [dev.name, dev.id] } + received = Developer.order(Arel.sql("name ASC")).reorder(Arel.sql("name DESC")).order(Arel.sql("id DESC")).collect { |dev| [dev.name, dev.id] } assert_equal expected, received end @@ -104,69 +104,69 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_unscope_after_reordering_and_combining - expected = Developer.order("id DESC, name DESC").collect { |dev| [dev.name, dev.id] } - received = DeveloperOrderedBySalary.reorder("name DESC").unscope(:order).order("id DESC, name DESC").collect { |dev| [dev.name, dev.id] } + expected = Developer.order(Arel.sql("id DESC, name DESC")).collect { |dev| [dev.name, dev.id] } + received = DeveloperOrderedBySalary.reorder(Arel.sql("name DESC")).unscope(:order).order(Arel.sql("id DESC, name DESC")).collect { |dev| [dev.name, dev.id] } assert_equal expected, received expected_2 = Developer.all.collect { |dev| [dev.name, dev.id] } - received_2 = Developer.order("id DESC, name DESC").unscope(:order).collect { |dev| [dev.name, dev.id] } + received_2 = Developer.order(Arel.sql("id DESC, name DESC")).unscope(:order).collect { |dev| [dev.name, dev.id] } assert_equal expected_2, received_2 expected_3 = Developer.all.collect { |dev| [dev.name, dev.id] } - received_3 = Developer.reorder("name DESC").unscope(:order).collect { |dev| [dev.name, dev.id] } + received_3 = Developer.reorder(Arel.sql("name DESC")).unscope(:order).collect { |dev| [dev.name, dev.id] } assert_equal expected_3, received_3 end def test_unscope_with_where_attributes - expected = Developer.order("salary DESC").collect(&:name) + expected = Developer.order(Arel.sql("salary DESC")).collect(&:name) received = DeveloperOrderedBySalary.where(name: "David").unscope(where: :name).collect(&:name) assert_equal expected, received - expected_2 = Developer.order("salary DESC").collect(&:name) + expected_2 = Developer.order(Arel.sql("salary DESC")).collect(&:name) received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({ where: :name }, :select).collect(&:name) assert_equal expected_2, received_2 - expected_3 = Developer.order("salary DESC").collect(&:name) + expected_3 = Developer.order(Arel.sql("salary DESC")).collect(&:name) received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect(&:name) assert_equal expected_3, received_3 - expected_4 = Developer.order("salary DESC").collect(&:name) + expected_4 = Developer.order(Arel.sql("salary DESC")).collect(&:name) received_4 = DeveloperOrderedBySalary.where.not("name" => "Jamis").unscope(where: :name).collect(&:name) assert_equal expected_4, received_4 - expected_5 = Developer.order("salary DESC").collect(&:name) + expected_5 = Developer.order(Arel.sql("salary DESC")).collect(&:name) received_5 = DeveloperOrderedBySalary.where.not("name" => ["Jamis", "David"]).unscope(where: :name).collect(&:name) assert_equal expected_5, received_5 - expected_6 = Developer.order("salary DESC").collect(&:name) + expected_6 = Developer.order(Arel.sql("salary DESC")).collect(&:name) received_6 = DeveloperOrderedBySalary.where(Developer.arel_table["name"].eq("David")).unscope(where: :name).collect(&:name) assert_equal expected_6, received_6 - expected_7 = Developer.order("salary DESC").collect(&:name) + expected_7 = Developer.order(Arel.sql("salary DESC")).collect(&:name) received_7 = DeveloperOrderedBySalary.where(Developer.arel_table[:name].eq("David")).unscope(where: :name).collect(&:name) assert_equal expected_7, received_7 end def test_unscope_comparison_where_clauses # unscoped for WHERE (`developers`.`id` <= 2) - expected = Developer.order("salary DESC").collect(&:name) + expected = Developer.order(Arel.sql("salary DESC")).collect(&:name) received = DeveloperOrderedBySalary.where(id: -Float::INFINITY..2).unscope(where: :id).collect { |dev| dev.name } assert_equal expected, received # unscoped for WHERE (`developers`.`id` < 2) - expected = Developer.order("salary DESC").collect(&:name) + expected = Developer.order(Arel.sql("salary DESC")).collect(&:name) received = DeveloperOrderedBySalary.where(id: -Float::INFINITY...2).unscope(where: :id).collect { |dev| dev.name } assert_equal expected, received end def test_unscope_multiple_where_clauses - expected = Developer.order("salary DESC").collect(&:name) + expected = Developer.order(Arel.sql("salary DESC")).collect(&:name) received = DeveloperOrderedBySalary.where(name: "Jamis").where(id: 1).unscope(where: [:name, :id]).collect(&:name) assert_equal expected, received end def test_unscope_string_where_clauses_involved - dev_relation = Developer.order("salary DESC").where("created_at > ?", 1.year.ago) + dev_relation = Developer.order(Arel.sql("salary DESC")).where("created_at > ?", 1.year.ago) expected = dev_relation.collect(&:name) dev_ordered_relation = DeveloperOrderedBySalary.where(name: "Jamis").where("created_at > ?", 1.year.ago) @@ -176,35 +176,35 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_unscope_with_grouping_attributes - expected = Developer.order("salary DESC").collect(&:name) + expected = Developer.order(Arel.sql("salary DESC")).collect(&:name) received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect(&:name) assert_equal expected, received - expected_2 = Developer.order("salary DESC").collect(&:name) + expected_2 = Developer.order(Arel.sql("salary DESC")).collect(&:name) received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect(&:name) assert_equal expected_2, received_2 end def test_unscope_with_limit_in_query - expected = Developer.order("salary DESC").collect(&:name) + expected = Developer.order(Arel.sql("salary DESC")).collect(&:name) received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect(&:name) assert_equal expected, received end def test_order_to_unscope_reordering - scope = DeveloperOrderedBySalary.order("salary DESC, name ASC").reverse_order.unscope(:order) + scope = DeveloperOrderedBySalary.order(Arel.sql("salary DESC, name ASC")).reverse_order.unscope(:order) assert !/order/i.match?(scope.to_sql) end def test_unscope_reverse_order expected = Developer.all.collect(&:name) - received = Developer.order("salary DESC").reverse_order.unscope(:order).collect(&:name) + received = Developer.order(Arel.sql("salary DESC")).reverse_order.unscope(:order).collect(&:name) assert_equal expected, received end def test_unscope_select - expected = Developer.order("salary ASC").collect(&:name) - received = Developer.order("salary DESC").reverse_order.select(:name).unscope(:select).collect(&:name) + expected = Developer.order(Arel.sql("salary ASC")).collect(&:name) + received = Developer.order(Arel.sql("salary DESC")).reverse_order.select(:name).unscope(:select).collect(&:name) assert_equal expected, received expected_2 = Developer.all.collect(&:id) @@ -256,11 +256,11 @@ class DefaultScopingTest < ActiveRecord::TestCase end assert_raises(ArgumentError) do - Developer.order("name DESC").reverse_order.unscope(:reverse_order) + Developer.order(Arel.sql("name DESC")).reverse_order.unscope(:reverse_order) end assert_raises(ArgumentError) do - Developer.order("name DESC").where(name: "Jamis").unscope() + Developer.order(Arel.sql("name DESC")).where(name: "Jamis").unscope() end end @@ -295,7 +295,7 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_order_in_default_scope_should_not_prevail - expected = Developer.all.merge!(order: "salary desc").to_a.collect(&:salary) + expected = Developer.all.merge!(order: Arel.sql("salary desc")).to_a.collect(&:salary) received = DeveloperOrderedBySalary.all.merge!(order: "salary").to_a.collect(&:salary) assert_equal expected, received end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index b0431a4e34..4079bb477e 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -472,7 +472,7 @@ class NamedScopingTest < ActiveRecord::TestCase def test_scopes_on_relations # Topic.replied - approved_topics = Topic.all.approved.order("id DESC") + approved_topics = Topic.all.approved.order(Arel.sql("id DESC")) assert_equal topics(:fifth), approved_topics.first replied_approved_topics = approved_topics.replied @@ -480,7 +480,7 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_index_on_scope - approved = Topic.approved.order("id ASC") + approved = Topic.approved.order(Arel.sql("id ASC")) assert_equal topics(:second), approved[0] assert approved.loaded? end diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index 116f8e83aa..2a95204d0d 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -39,23 +39,23 @@ class RelationScopingTest < ActiveRecord::TestCase end def test_reverse_order - assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order + assert_equal Developer.order(Arel.sql("id DESC")).to_a.reverse, Developer.order(Arel.sql("id DESC")).reverse_order end def test_reverse_order_with_arel_node - assert_equal Developer.order("id DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).reverse_order + assert_equal Developer.order(Arel.sql("id DESC")).to_a.reverse, Developer.order(Developer.arel_table[:id].desc).reverse_order end def test_reverse_order_with_multiple_arel_nodes - assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).order(Developer.arel_table[:name].desc).reverse_order + assert_equal Developer.order(Arel.sql("id DESC")).order(Arel.sql("name DESC")).to_a.reverse, Developer.order(Developer.arel_table[:id].desc).order(Developer.arel_table[:name].desc).reverse_order end def test_reverse_order_with_arel_nodes_and_strings - assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order("id DESC").order(Developer.arel_table[:name].desc).reverse_order + assert_equal Developer.order(Arel.sql("id DESC")).order(Arel.sql("name DESC")).to_a.reverse, Developer.order(Arel.sql("id DESC")).order(Developer.arel_table[:name].desc).reverse_order end def test_double_reverse_order_produces_original_order - assert_equal Developer.order("name DESC"), Developer.order("name DESC").reverse_order.reverse_order + assert_equal Developer.order(Arel.sql("name DESC")), Developer.order(Arel.sql("name DESC")).reverse_order.reverse_order end def test_scoped_find @@ -72,7 +72,7 @@ class RelationScopingTest < ActiveRecord::TestCase end def test_scoped_find_last - highest_salary = Developer.order("salary DESC").first + highest_salary = Developer.order(Arel.sql("salary DESC")).first Developer.order("salary").scoping do assert_equal highest_salary, Developer.last @@ -80,8 +80,8 @@ class RelationScopingTest < ActiveRecord::TestCase end def test_scoped_find_last_preserves_scope - lowest_salary = Developer.order("salary ASC").first - highest_salary = Developer.order("salary DESC").first + lowest_salary = Developer.order(Arel.sql("salary ASC")).first + highest_salary = Developer.order(Arel.sql("salary DESC")).first Developer.order("salary").scoping do assert_equal highest_salary, Developer.last diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index cb8686f315..11fe073ab0 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -7,10 +7,10 @@ class Author < ActiveRecord::Base has_many :very_special_comments, through: :posts has_many :posts_with_comments, -> { includes(:comments) }, class_name: "Post" has_many :popular_grouped_posts, -> { includes(:comments).group("type").having("SUM(comments_count) > 1").select("type") }, class_name: "Post" - has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order("comments.id") }, class_name: "Post" - has_many :posts_sorted_by_id_limited, -> { order("posts.id").limit(1) }, class_name: "Post" + has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order(Arel.sql("comments.id")) }, class_name: "Post" + has_many :posts_sorted_by_id_limited, -> { order(Arel.sql("posts.id")).limit(1) }, class_name: "Post" has_many :posts_with_categories, -> { includes(:categories) }, class_name: "Post" - has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, class_name: "Post" + has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order(Arel.sql("posts.id")) }, class_name: "Post" has_many :posts_with_special_categorizations, class_name: "PostWithSpecialCategorization" has_one :post_about_thinking, -> { where("posts.title like '%thinking%'") }, class_name: "Post" has_one :post_about_thinking_with_last_comment, -> { where("posts.title like '%thinking%'").includes(:last_comment) }, class_name: "Post" @@ -20,15 +20,15 @@ class Author < ActiveRecord::Base end end has_many :comments_containing_the_letter_e, through: :posts, source: :comments - has_many :comments_with_order_and_conditions, -> { order("comments.body").where("comments.body like 'Thank%'") }, through: :posts, source: :comments + has_many :comments_with_order_and_conditions, -> { order(Arel.sql("comments.body")).where("comments.body like 'Thank%'") }, through: :posts, source: :comments has_many :comments_with_include, -> { includes(:post).where(posts: { type: "Post" }) }, through: :posts, source: :comments has_many :comments_for_first_author, -> { for_first_author }, through: :posts, source: :comments has_many :first_posts - has_many :comments_on_first_posts, -> { order("posts.id desc, comments.id asc") }, through: :first_posts, source: :comments + has_many :comments_on_first_posts, -> { order(Arel.sql("posts.id desc, comments.id asc")) }, through: :first_posts, source: :comments has_one :first_post - has_one :comment_on_first_post, -> { order("posts.id desc, comments.id asc") }, through: :first_post, source: :comments + has_one :comment_on_first_post, -> { order(Arel.sql("posts.id desc, comments.id asc")) }, through: :first_post, source: :comments has_many :thinking_posts, -> { where(title: "So I was thinking") }, dependent: :delete_all, class_name: "Post" has_many :welcome_posts, -> { where(title: "Welcome to the weblog") }, class_name: "Post" @@ -40,11 +40,11 @@ class Author < ActiveRecord::Base -> { where(title: "Welcome to the weblog").where(Post.arel_table[:comments_count].gt(0)) }, class_name: "Post" - has_many :comments_desc, -> { order("comments.id DESC") }, through: :posts, source: :comments + has_many :comments_desc, -> { order(Arel.sql("comments.id DESC")) }, through: :posts, source: :comments has_many :unordered_comments, -> { unscope(:order).distinct }, through: :posts_sorted_by_id_limited, source: :comments has_many :funky_comments, through: :posts, source: :comments - has_many :ordered_uniq_comments, -> { distinct.order("comments.id") }, through: :posts, source: :comments - has_many :ordered_uniq_comments_desc, -> { distinct.order("comments.id DESC") }, through: :posts, source: :comments + has_many :ordered_uniq_comments, -> { distinct.order(Arel.sql("comments.id")) }, through: :posts, source: :comments + has_many :ordered_uniq_comments_desc, -> { distinct.order(Arel.sql("comments.id DESC")) }, through: :posts, source: :comments has_many :readonly_comments, -> { readonly }, through: :posts, source: :comments has_many :special_posts @@ -107,15 +107,15 @@ class Author < ActiveRecord::Base has_many :similar_posts, -> { distinct }, through: :tags, source: :tagged_posts has_many :ordered_posts, -> { distinct }, through: :ordered_tags, source: :tagged_posts - has_many :distinct_tags, -> { select("DISTINCT tags.*").order("tags.name") }, through: :posts, source: :tags + has_many :distinct_tags, -> { select("DISTINCT tags.*").order(Arel.sql("tags.name")) }, through: :posts, source: :tags has_many :tags_with_primary_key, through: :posts has_many :books has_many :unpublished_books, -> { where(status: [:proposed, :written]) }, class_name: "Book" has_many :subscriptions, through: :books - has_many :subscribers, -> { order("subscribers.nick") }, through: :subscriptions - has_many :distinct_subscribers, -> { select("DISTINCT subscribers.*").order("subscribers.nick") }, through: :subscriptions, source: :subscriber + has_many :subscribers, -> { order(Arel.sql("subscribers.nick")) }, through: :subscriptions + has_many :distinct_subscribers, -> { select("DISTINCT subscribers.*").order(Arel.sql("subscribers.nick")) }, through: :subscriptions, source: :subscriber has_one :essay, primary_key: :name, as: :writer has_one :essay_category, through: :essay, source: :category diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index 3d6a7a96c2..0be943a321 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -19,13 +19,13 @@ class Car < ActiveRecord::Base scope :incl_tyres, -> { includes(:tyres) } scope :incl_engines, -> { includes(:engines) } - scope :order_using_new_style, -> { order("name asc") } + scope :order_using_new_style, -> { order(Arel.sql("name asc")) } end class CoolCar < Car - default_scope { order("name desc") } + default_scope { order(Arel.sql("name desc")) } end class FastCar < Car - default_scope { order("name desc") } + default_scope { order(Arel.sql("name desc")) } end diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb index 2ccc00bed9..3038264694 100644 --- a/activerecord/test/models/category.rb +++ b/activerecord/test/models/category.rb @@ -4,7 +4,7 @@ class Category < ActiveRecord::Base has_and_belongs_to_many :posts has_and_belongs_to_many :special_posts, class_name: "Post" has_and_belongs_to_many :other_posts, class_name: "Post" - has_and_belongs_to_many :posts_with_authors_sorted_by_author_id, -> { includes(:authors).order("authors.id") }, class_name: "Post" + has_and_belongs_to_many :posts_with_authors_sorted_by_author_id, -> { includes(:authors).order(Arel.sql("authors.id")) }, class_name: "Post" has_and_belongs_to_many :select_testing_posts, -> { select "posts.*, 1 as correctness_marker" }, diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index 5ab433f2d9..d5acfc0749 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -81,7 +81,7 @@ class CommentThatAutomaticallyAltersPostBody < Comment end class CommentWithDefaultScopeReferencesAssociation < Comment - default_scope -> { includes(:developer).order("developers.name").references(:developer) } + default_scope -> { includes(:developer).order(Arel.sql("developers.name")).references(:developer) } belongs_to :developer end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index bbc5fc2b2d..1a82a9e646 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -50,7 +50,7 @@ class Firm < Company has_many :clients, -> { order "id" }, dependent: :destroy, before_remove: :log_before_remove, after_remove: :log_after_remove has_many :unsorted_clients, class_name: "Client" has_many :unsorted_clients_with_symbol, class_name: :Client - has_many :clients_sorted_desc, -> { order "id DESC" }, class_name: "Client" + has_many :clients_sorted_desc, -> { order Arel.sql("id DESC") }, class_name: "Client" has_many :clients_of_firm, -> { order "id" }, foreign_key: "client_of", class_name: "Client", inverse_of: :firm has_many :clients_ordered_by_name, -> { order "name" }, class_name: "Client" has_many :unvalidated_clients_of_firm, foreign_key: "client_of", class_name: "Client", validate: false diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb index 52b7e06a63..9108eb5249 100644 --- a/activerecord/test/models/company_in_module.rb +++ b/activerecord/test/models/company_in_module.rb @@ -9,7 +9,7 @@ module MyApplication class Firm < Company has_many :clients, -> { order("id") }, dependent: :destroy - has_many :clients_sorted_desc, -> { order("id DESC") }, class_name: "Client" + has_many :clients_sorted_desc, -> { order(Arel.sql("id DESC")) }, class_name: "Client" has_many :clients_of_firm, -> { order "id" }, foreign_key: "client_of", class_name: "Client" has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, class_name: "Client" has_one :account, class_name: "MyApplication::Billing::Account", dependent: :destroy diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 8881c69368..c2a21eac04 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -4,7 +4,7 @@ require "ostruct" module DeveloperProjectsAssociationExtension2 def find_least_recent - order("id ASC").first + order(Arel.sql("id ASC")).first end end @@ -13,7 +13,7 @@ class Developer < ActiveRecord::Base has_and_belongs_to_many :projects do def find_most_recent - order("id DESC").first + order(Arel.sql("id DESC")).first end end @@ -41,7 +41,7 @@ class Developer < ActiveRecord::Base join_table: "developers_projects", association_foreign_key: "project_id" do def find_least_recent - order("id ASC").first + order(Arel.sql("id ASC")).first end end @@ -126,7 +126,7 @@ end class DeveloperFilteredOnJoins < ActiveRecord::Base self.table_name = "developers" - has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" + has_and_belongs_to_many :projects, -> { order(Arel.sql("projects.id")) }, foreign_key: "developer_id", join_table: "developers_projects" def self.default_scope joins(:projects).where(projects: { name: "Active Controller" }) @@ -135,9 +135,9 @@ end class DeveloperOrderedBySalary < ActiveRecord::Base self.table_name = "developers" - default_scope { order("salary DESC") } + default_scope { order(Arel.sql("salary DESC")) } - scope :by_name, -> { order("name DESC") } + scope :by_name, -> { order(Arel.sql("name DESC")) } end class DeveloperCalledDavid < ActiveRecord::Base @@ -225,14 +225,14 @@ end class EagerDeveloperWithDefaultScope < ActiveRecord::Base self.table_name = "developers" - has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" + has_and_belongs_to_many :projects, -> { order(Arel.sql("projects.id")) }, foreign_key: "developer_id", join_table: "developers_projects" default_scope { includes(:projects) } end class EagerDeveloperWithClassMethodDefaultScope < ActiveRecord::Base self.table_name = "developers" - has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" + has_and_belongs_to_many :projects, -> { order(Arel.sql("projects.id")) }, foreign_key: "developer_id", join_table: "developers_projects" def self.default_scope includes(:projects) @@ -241,21 +241,21 @@ end class EagerDeveloperWithLambdaDefaultScope < ActiveRecord::Base self.table_name = "developers" - has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" + has_and_belongs_to_many :projects, -> { order(Arel.sql("projects.id")) }, foreign_key: "developer_id", join_table: "developers_projects" default_scope lambda { includes(:projects) } end class EagerDeveloperWithBlockDefaultScope < ActiveRecord::Base self.table_name = "developers" - has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" + has_and_belongs_to_many :projects, -> { order(Arel.sql("projects.id")) }, foreign_key: "developer_id", join_table: "developers_projects" default_scope { includes(:projects) } end class EagerDeveloperWithCallableDefaultScope < ActiveRecord::Base self.table_name = "developers" - has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" + has_and_belongs_to_many :projects, -> { order(Arel.sql("projects.id")) }, foreign_key: "developer_id", join_table: "developers_projects" default_scope OpenStruct.new(call: includes(:projects)) end diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb index 09ee7544b3..47cb4d2146 100644 --- a/activerecord/test/models/membership.rb +++ b/activerecord/test/models/membership.rb @@ -12,7 +12,7 @@ class CurrentMembership < Membership end class SuperMembership < Membership - belongs_to :member, -> { order("members.id DESC") } + belongs_to :member, -> { order(Arel.sql("members.id DESC")) } belongs_to :club end diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb index 5fa50d9918..ebaafdec5e 100644 --- a/activerecord/test/models/owner.rb +++ b/activerecord/test/models/owner.rb @@ -2,7 +2,7 @@ class Owner < ActiveRecord::Base self.primary_key = :owner_id - has_many :pets, -> { order "pets.name desc" } + has_many :pets, -> { order Arel.sql("pets.name desc") } has_many :toys, through: :pets has_many :persons, through: :pets diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 5cba1e440e..7067f1b6b0 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -19,7 +19,7 @@ class Person < ActiveRecord::Base has_many :bad_references has_many :fixed_bad_references, -> { where favourite: true }, class_name: "BadReference" has_one :favourite_reference, -> { where "favourite=?", true }, class_name: "Reference" - has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order("comments.id") }, through: :readers, source: :post + has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order(Arel.sql("comments.id")) }, through: :readers, source: :post has_many :first_posts, -> { where(id: [1, 2]) }, through: :readers has_many :jobs, through: :references diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index c8617d1cfe..99de37a52f 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -3,7 +3,7 @@ class Pirate < ActiveRecord::Base belongs_to :parrot, validate: true belongs_to :non_validated_parrot, class_name: "Parrot" - has_and_belongs_to_many :parrots, -> { order("parrots.id ASC") }, validate: true + has_and_belongs_to_many :parrots, -> { order(Arel.sql("parrots.id ASC")) }, validate: true has_and_belongs_to_many :non_validated_parrots, class_name: "Parrot" has_and_belongs_to_many :parrots_with_method_callbacks, class_name: "Parrot", before_add: :log_before_add, @@ -23,7 +23,7 @@ class Pirate < ActiveRecord::Base has_one :ship has_one :update_only_ship, class_name: "Ship" has_one :non_validated_ship, class_name: "Ship" - has_many :birds, -> { order("birds.id ASC") } + has_many :birds, -> { order(Arel.sql("birds.id ASC")) } has_many :birds_with_method_callbacks, class_name: "Bird", before_add: :log_before_add, after_add: :log_after_add, diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 7f064bf3dd..49bbbaaab7 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -35,8 +35,8 @@ class Post < ActiveRecord::Base def first_comment super.body end - has_one :first_comment, -> { order("id ASC") }, class_name: "Comment" - has_one :last_comment, -> { order("id desc") }, class_name: "Comment" + has_one :first_comment, -> { order(Arel.sql("id ASC")) }, class_name: "Comment" + has_one :last_comment, -> { order(Arel.sql("id desc")) }, class_name: "Comment" scope :with_special_comments, -> { joins(:comments).where(comments: { type: "SpecialComment" }) } scope :with_very_special_comments, -> { joins(:comments).where(comments: { type: "VerySpecialComment" }) } @@ -52,7 +52,7 @@ class Post < ActiveRecord::Base has_many :comments do def find_most_recent - order("id DESC").first + order(Arel.sql("id DESC")).first end def newest @@ -85,7 +85,7 @@ class Post < ActiveRecord::Base has_one :very_special_comment has_one :very_special_comment_with_post, -> { includes(:post) }, class_name: "VerySpecialComment" - has_one :very_special_comment_with_post_with_joins, -> { joins(:post).order("posts.id") }, class_name: "VerySpecialComment" + has_one :very_special_comment_with_post_with_joins, -> { joins(:post).order(Arel.sql("posts.id")) }, class_name: "VerySpecialComment" has_many :special_comments has_many :nonexistent_comments, -> { where "comments.id < 0" }, class_name: "Comment" @@ -319,5 +319,9 @@ class FakeKlass def arel_attribute(name, table) table[name] end + + def enforce_raw_sql_whitelist(*args) + # noop + end end end diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index 846cef625b..9b282a6729 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -2,9 +2,9 @@ class Project < ActiveRecord::Base belongs_to :mentor - has_and_belongs_to_many :developers, -> { distinct.order "developers.name desc, developers.id desc" } + has_and_belongs_to_many :developers, -> { distinct.order Arel.sql("developers.name desc, developers.id desc") } has_and_belongs_to_many :readonly_developers, -> { readonly }, class_name: "Developer" - has_and_belongs_to_many :non_unique_developers, -> { order "developers.name desc, developers.id desc" }, class_name: "Developer" + has_and_belongs_to_many :non_unique_developers, -> { order Arel.sql("developers.name desc, developers.id desc") }, class_name: "Developer" has_and_belongs_to_many :limited_developers, -> { limit 1 }, class_name: "Developer" has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, class_name: "Developer" has_and_belongs_to_many :developers_named_david_with_hash_conditions, -> { where(name: "David").distinct }, class_name: "Developer" diff --git a/activerecord/test/models/tag.rb b/activerecord/test/models/tag.rb index bc13c3a42d..e0d42f4f66 100644 --- a/activerecord/test/models/tag.rb +++ b/activerecord/test/models/tag.rb @@ -11,6 +11,6 @@ end class OrderedTag < Tag self.table_name = "tags" - has_many :taggings, -> { order("taggings.id DESC") }, foreign_key: "tag_id" + has_many :taggings, -> { order(Arel.sql("taggings.id DESC")) }, foreign_key: "tag_id" has_many :tagged_posts, through: :taggings, source: "taggable", source_type: "Post" end -- 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 +++++++- .../associations/cascaded_eager_loading_test.rb | 28 ++++---- activerecord/test/cases/associations/eager_test.rb | 78 +++++++++++----------- .../associations/has_many_associations_test.rb | 8 +-- .../has_many_through_associations_test.rb | 4 +- .../test/cases/associations/join_model_test.rb | 30 ++++----- .../nested_through_associations_test.rb | 24 +++---- activerecord/test/cases/base_test.rb | 18 ++--- activerecord/test/cases/batches_test.rb | 14 ++-- activerecord/test/cases/finder_test.rb | 22 +++--- activerecord/test/cases/relation/merging_test.rb | 8 +-- activerecord/test/cases/relation/or_test.rb | 14 ++-- activerecord/test/cases/relations_test.rb | 66 +++++++++--------- .../test/cases/scoping/default_scoping_test.rb | 52 +++++++-------- .../test/cases/scoping/named_scoping_test.rb | 4 +- .../test/cases/scoping/relation_scoping_test.rb | 16 ++--- activerecord/test/cases/unsafe_raw_sql_test.rb | 42 +++++++++++- activerecord/test/models/author.rb | 4 +- activerecord/test/models/car.rb | 6 +- activerecord/test/models/company.rb | 2 +- activerecord/test/models/company_in_module.rb | 2 +- activerecord/test/models/developer.rb | 22 +++--- activerecord/test/models/membership.rb | 2 +- activerecord/test/models/pirate.rb | 4 +- activerecord/test/models/post.rb | 10 ++- activerecord/test/models/tag.rb | 2 +- 27 files changed, 295 insertions(+), 244 deletions(-) (limited to 'activerecord') 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 } diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 3eba5ed466..f08fd73da4 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -18,7 +18,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase :categorizations, :people, :categories, :edges, :vertices def test_eager_association_loading_with_cascaded_two_levels - authors = Author.all.merge!(includes: { posts: :comments }, order: Arel.sql("authors.id")).to_a + authors = Author.all.merge!(includes: { posts: :comments }, order: "authors.id").to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size @@ -26,7 +26,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_and_one_level - authors = Author.all.merge!(includes: [{ posts: :comments }, :categorizations], order: Arel.sql("authors.id")).to_a + authors = Author.all.merge!(includes: [{ posts: :comments }, :categorizations], order: "authors.id").to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size @@ -42,7 +42,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_grafts_stashed_associations_to_correct_parent - assert_equal people(:michael), Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order(Arel.sql("people.id")).first + assert_equal people(:michael), Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").first end def test_cascaded_eager_association_loading_with_join_for_count @@ -78,7 +78,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations - authors = Author.all.merge!(includes: { posts: [:comments, :categorizations] }, order: Arel.sql("authors.id")).to_a + authors = Author.all.merge!(includes: { posts: [:comments, :categorizations] }, order: "authors.id").to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size @@ -86,7 +86,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference - authors = Author.all.merge!(includes: { posts: [:comments, :author] }, order: Arel.sql("authors.id")).to_a + authors = Author.all.merge!(includes: { posts: [:comments, :author] }, order: "authors.id").to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal authors(:david).name, authors[0].name @@ -94,13 +94,13 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_with_condition - authors = Author.all.merge!(includes: { posts: :comments }, where: "authors.id=1", order: Arel.sql("authors.id")).to_a + authors = Author.all.merge!(includes: { posts: :comments }, where: "authors.id=1", order: "authors.id").to_a assert_equal 1, authors.size assert_equal 5, authors[0].posts.size end def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong - firms = Firm.all.merge!(includes: { account: { firm: :account } }, order: Arel.sql("companies.id")).to_a + firms = Firm.all.merge!(includes: { account: { firm: :account } }, order: "companies.id").to_a assert_equal 2, firms.size assert_equal firms.first.account, firms.first.account.firm.account assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account } @@ -108,7 +108,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_has_many_sti - topics = Topic.all.merge!(includes: :replies, order: Arel.sql("topics.id")).to_a + topics = Topic.all.merge!(includes: :replies, order: "topics.id").to_a first, second, = topics(:first).replies.size, topics(:second).replies.size assert_no_queries do assert_equal first, topics[0].replies.size @@ -121,7 +121,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase silly.parent_id = 1 assert silly.save - topics = Topic.all.merge!(includes: :replies, order: [Arel.sql("topics.id"), Arel.sql("replies_topics.id")]).to_a + topics = Topic.all.merge!(includes: :replies, order: ["topics.id", Arel.sql("replies_topics.id")]).to_a assert_no_queries do assert_equal 2, topics[0].replies.size assert_equal 0, topics[1].replies.size @@ -136,7 +136,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_multiple_stis_and_order - author = Author.all.merge!(includes: { posts: [ :special_comments , :very_special_comment ] }, order: [Arel.sql("authors.name"), Arel.sql("comments.body"), Arel.sql("very_special_comments_posts.body")], where: "posts.id = 4").first + author = Author.all.merge!(includes: { posts: [ :special_comments , :very_special_comment ] }, order: ["authors.name", Arel.sql("comments.body"), Arel.sql("very_special_comments_posts.body")], where: "posts.id = 4").first assert_equal authors(:david), author assert_no_queries do author.posts.first.special_comments @@ -154,7 +154,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_where_first_level_returns_nil - authors = Author.all.merge!(includes: { post_about_thinking: :comments }, order: Arel.sql("authors.id DESC")).to_a + authors = Author.all.merge!(includes: { post_about_thinking: :comments }, order: "authors.id DESC").to_a assert_equal [authors(:bob), authors(:mary), authors(:david)], authors assert_no_queries do authors[2].post_about_thinking.comments.first @@ -162,17 +162,17 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through - source = Vertex.all.merge!(includes: { sinks: { sinks: { sinks: :sinks } } }, order: Arel.sql("vertices.id")).first + source = Vertex.all.merge!(includes: { sinks: { sinks: { sinks: :sinks } } }, order: "vertices.id").first assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first } end def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many - sink = Vertex.all.merge!(includes: { sources: { sources: { sources: :sources } } }, order: Arel.sql("vertices.id DESC")).first + sink = Vertex.all.merge!(includes: { sources: { sources: { sources: :sources } } }, order: "vertices.id DESC").first assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first } end def test_eager_association_loading_with_cascaded_interdependent_one_level_and_two_levels - authors_relation = Author.all.merge!(includes: [:comments, { posts: :categorizations }], order: Arel.sql("authors.id")) + authors_relation = Author.all.merge!(includes: [:comments, { posts: :categorizations }], order: "authors.id") authors = authors_relation.to_a assert_equal 3, authors.size assert_equal 10, authors[0].comments.size diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index d5ca87900e..75f851fec7 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -82,7 +82,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_with_ordering - list = Post.all.merge!(includes: :comments, order: Arel.sql("posts.id DESC")).to_a + list = Post.all.merge!(includes: :comments, order: "posts.id DESC").to_a [:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments, :authorless, :thinking, :welcome ].each_with_index do |post, index| @@ -108,12 +108,12 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_with_two_tables_in_from_without_getting_double_quoted - posts = Post.select("posts.*").from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").order(Arel.sql("posts.id")).to_a + posts = Post.select("posts.*").from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").order("posts.id").to_a assert_equal 2, posts.first.comments.size end def test_loading_with_multiple_associations - posts = Post.all.merge!(includes: [ :comments, :author, :categories ], order: Arel.sql("posts.id")).to_a + posts = Post.all.merge!(includes: [ :comments, :author, :categories ], order: "posts.id").to_a assert_equal 2, posts.first.comments.size assert_equal 2, posts.first.categories.size assert_includes posts.first.comments, comments(:greetings) @@ -279,7 +279,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_loading_from_an_association - posts = authors(:david).posts.merge(includes: :comments, order: Arel.sql("posts.id")).to_a + posts = authors(:david).posts.merge(includes: :comments, order: "posts.id").to_a assert_equal 2, posts.first.comments.size end @@ -312,7 +312,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_nested_loading_through_has_one_association_with_order - aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: Arel.sql("author_addresses.id")).find(author_addresses(:david_address).id) + aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: "author_addresses.id").find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end @@ -364,31 +364,31 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_association_loading_with_belongs_to_and_limit - comments = Comment.all.merge!(includes: :post, limit: 5, order: Arel.sql("comments.id")).to_a + comments = Comment.all.merge!(includes: :post, limit: 5, order: "comments.id").to_a assert_equal 5, comments.length assert_equal [1, 2, 3, 5, 6], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_conditions - comments = Comment.all.merge!(includes: :post, where: "post_id = 4", limit: 3, order: Arel.sql("comments.id")).to_a + comments = Comment.all.merge!(includes: :post, where: "post_id = 4", limit: 3, order: "comments.id").to_a assert_equal 3, comments.length assert_equal [5, 6, 7], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset - comments = Comment.all.merge!(includes: :post, limit: 3, offset: 2, order: Arel.sql("comments.id")).to_a + comments = Comment.all.merge!(includes: :post, limit: 3, offset: 2, order: "comments.id").to_a assert_equal 3, comments.length assert_equal [3, 5, 6], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions - comments = Comment.all.merge!(includes: :post, where: "post_id = 4", limit: 3, offset: 1, order: Arel.sql("comments.id")).to_a + comments = Comment.all.merge!(includes: :post, where: "post_id = 4", limit: 3, offset: 1, order: "comments.id").to_a assert_equal 3, comments.length assert_equal [6, 7, 8], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array - comments = Comment.all.merge!(includes: :post, where: ["post_id = ?", 4], limit: 3, offset: 1, order: Arel.sql("comments.id")).to_a + comments = Comment.all.merge!(includes: :post, where: ["post_id = ?", 4], limit: 3, offset: 1, order: "comments.id").to_a assert_equal 3, comments.length assert_equal [6, 7, 8], comments.collect(&:id) end @@ -402,7 +402,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_conditions_hash comments = [] assert_nothing_raised do - comments = Comment.all.merge!(includes: :post, where: { posts: { id: 4 } }, limit: 3, order: Arel.sql("comments.id")).to_a + comments = Comment.all.merge!(includes: :post, where: { posts: { id: 4 } }, limit: 3, order: "comments.id").to_a end assert_equal 3, comments.length assert_equal [5, 6, 7], comments.collect(&:id) @@ -432,13 +432,13 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations - posts = Post.all.merge!(includes: [:author, :very_special_comment], limit: 1, order: Arel.sql("posts.id")).to_a + posts = Post.all.merge!(includes: [:author, :very_special_comment], limit: 1, order: "posts.id").to_a assert_equal 1, posts.length assert_equal [1], posts.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations - posts = Post.all.merge!(includes: [:author, :very_special_comment], limit: 1, offset: 1, order: Arel.sql("posts.id")).to_a + posts = Post.all.merge!(includes: [:author, :very_special_comment], limit: 1, offset: 1, order: "posts.id").to_a assert_equal 1, posts.length assert_equal [2], posts.collect(&:id) end @@ -508,9 +508,9 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_through - posts_with_comments = people(:michael).posts.merge(includes: :comments, order: Arel.sql("posts.id")).to_a - posts_with_author = people(:michael).posts.merge(includes: :author, order: Arel.sql("posts.id")).to_a - posts_with_comments_and_author = people(:michael).posts.merge(includes: [ :comments, :author ], order: Arel.sql("posts.id")).to_a + posts_with_comments = people(:michael).posts.merge(includes: :comments, order: "posts.id").to_a + posts_with_author = people(:michael).posts.merge(includes: :author, order: "posts.id").to_a + posts_with_comments_and_author = people(:michael).posts.merge(includes: [ :comments, :author ], order: "posts.id").to_a assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum + post.comments.size } assert_equal authors(:david), assert_no_queries { posts_with_author.first.author } assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author } @@ -526,7 +526,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_through_an_sti_join_model - author = Author.all.merge!(includes: :special_post_comments, order: Arel.sql("authors.id")).first + author = Author.all.merge!(includes: :special_post_comments, order: "authors.id").first assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments } end @@ -539,14 +539,14 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both - author = Author.all.merge!(includes: :special_nonexistent_post_comments, order: Arel.sql("authors.id")).first + author = Author.all.merge!(includes: :special_nonexistent_post_comments, order: "authors.id").first assert_equal [], author.special_nonexistent_post_comments end def test_eager_with_has_many_through_join_model_with_conditions assert_equal Author.all.merge!(includes: :hello_post_comments, - order: Arel.sql("authors.id")).first.hello_post_comments.sort_by(&:id), - Author.all.merge!(order: Arel.sql("authors.id")).first.hello_post_comments.sort_by(&:id) + order: "authors.id").first.hello_post_comments.sort_by(&:id), + Author.all.merge!(order: "authors.id").first.hello_post_comments.sort_by(&:id) end def test_eager_with_has_many_through_join_model_with_conditions_on_top_level @@ -573,19 +573,19 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_and_limit - posts = Post.all.merge!(order: Arel.sql("posts.id asc"), includes: [ :author, :comments ], limit: 2).to_a + posts = Post.all.merge!(order: "posts.id asc", includes: [ :author, :comments ], limit: 2).to_a assert_equal 2, posts.size assert_equal 3, posts.inject(0) { |sum, post| sum + post.comments.size } end def test_eager_with_has_many_and_limit_and_conditions - posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, where: "posts.body = 'hello'", order: Arel.sql("posts.id")).to_a + posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, where: "posts.body = 'hello'", order: "posts.id").to_a assert_equal 2, posts.size assert_equal [4, 5], posts.collect(&:id) end def test_eager_with_has_many_and_limit_and_conditions_array - posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, where: [ "posts.body = ?", "hello" ], order: Arel.sql("posts.id")).to_a + posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, where: [ "posts.body = ?", "hello" ], order: "posts.id").to_a assert_equal 2, posts.size assert_equal [4, 5], posts.collect(&:id) end @@ -643,7 +643,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_and_belongs_to_many_and_limit - posts = Post.all.merge!(includes: :categories, order: Arel.sql("posts.id"), limit: 3).to_a + posts = Post.all.merge!(includes: :categories, order: "posts.id", limit: 3).to_a assert_equal 3, posts.size assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size @@ -709,7 +709,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_association_loading_with_habtm - posts = Post.all.merge!(includes: :categories, order: Arel.sql("posts.id")).to_a + posts = Post.all.merge!(includes: :categories, order: "posts.id").to_a assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size assert_equal 0, posts[2].categories.size @@ -891,14 +891,14 @@ class EagerAssociationTest < ActiveRecord::TestCase posts(:thinking, :sti_comments), Post.all.merge!( includes: [:author, :comments], where: { "authors.name" => "David" }, - order: [Arel.sql("UPPER(posts.title)"), Arel.sql("posts.id")], limit: 2, offset: 1 + order: [Arel.sql("UPPER(posts.title)"), "posts.id"], limit: 2, offset: 1 ).to_a ) assert_equal( posts(:sti_post_and_comments, :sti_comments), Post.all.merge!( includes: [:author, :comments], where: { "authors.name" => "David" }, - order: [Arel.sql("UPPER(posts.title) DESC"), Arel.sql("posts.id")], limit: 2, offset: 1 + order: [Arel.sql("UPPER(posts.title) DESC"), "posts.id"], limit: 2, offset: 1 ).to_a ) end @@ -909,7 +909,7 @@ class EagerAssociationTest < ActiveRecord::TestCase Person.references(:number1_fans_people).merge( includes: [:readers, :primary_contact, :number1_fan], where: "number1_fans_people.first_name like 'M%'", - order: Arel.sql("people.id"), limit: 2, offset: 0 + order: "people.id", limit: 2, offset: 0 ).to_a ) end @@ -1102,18 +1102,18 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_loading_with_conditions_on_joined_table_preloads posts = assert_queries(2) do - Post.all.merge!(select: "distinct posts.*", includes: :author, joins: [:comments], where: "comments.body like 'Thank you%'", order: Arel.sql("posts.id")).to_a + Post.all.merge!(select: "distinct posts.*", includes: :author, joins: [:comments], where: "comments.body like 'Thank you%'", order: "posts.id").to_a end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author } posts = assert_queries(2) do - Post.all.merge!(includes: :author, joins: { taggings: :tag }, where: "tags.name = 'General'", order: Arel.sql("posts.id")).to_a + Post.all.merge!(includes: :author, joins: { taggings: :tag }, where: "tags.name = 'General'", order: "posts.id").to_a end assert_equal posts(:welcome, :thinking), posts posts = assert_queries(2) do - Post.all.merge!(includes: :author, joins: { taggings: { tag: :taggings } }, where: "taggings_tags.super_tag_id=2", order: Arel.sql("posts.id")).to_a + Post.all.merge!(includes: :author, joins: { taggings: { tag: :taggings } }, where: "taggings_tags.super_tag_id=2", order: "posts.id").to_a end assert_equal posts(:welcome, :thinking), posts end @@ -1132,13 +1132,13 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_loading_with_conditions_on_string_joined_table_preloads posts = assert_queries(2) do - Post.all.merge!(select: "distinct posts.*", includes: :author, joins: "INNER JOIN comments on comments.post_id = posts.id", where: "comments.body like 'Thank you%'", order: Arel.sql("posts.id")).to_a + Post.all.merge!(select: "distinct posts.*", includes: :author, joins: "INNER JOIN comments on comments.post_id = posts.id", where: "comments.body like 'Thank you%'", order: "posts.id").to_a end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author } posts = assert_queries(2) do - Post.all.merge!(select: "distinct posts.*", includes: :author, joins: ["INNER JOIN comments on comments.post_id = posts.id"], where: "comments.body like 'Thank you%'", order: Arel.sql("posts.id")).to_a + Post.all.merge!(select: "distinct posts.*", includes: :author, joins: ["INNER JOIN comments on comments.post_id = posts.id"], where: "comments.body like 'Thank you%'", order: "posts.id").to_a end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author } @@ -1146,7 +1146,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_loading_with_select_on_joined_table_preloads posts = assert_queries(2) do - Post.all.merge!(select: "posts.*, authors.name as author_name", includes: :comments, joins: :author, order: Arel.sql("posts.id")).to_a + Post.all.merge!(select: "posts.*, authors.name as author_name", includes: :comments, joins: :author, order: "posts.id").to_a end assert_equal "David", posts[0].author_name assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments } @@ -1199,7 +1199,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_preload_has_one_using_primary_key expected = accounts(:signals37) - firm = Firm.all.merge!(includes: :account_using_primary_key, order: Arel.sql("companies.id")).first + firm = Firm.all.merge!(includes: :account_using_primary_key, order: "companies.id").first assert_no_queries do assert_equal expected, firm.account_using_primary_key end @@ -1269,7 +1269,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_joins_with_includes_should_preload_via_joins - post = assert_queries(1) { Post.includes(:comments).joins(:comments).order(Arel.sql("posts.id desc")).to_a.first } + post = assert_queries(1) { Post.includes(:comments).joins(:comments).order("posts.id desc").to_a.first } assert_queries(0) do assert_not_equal 0, post.comments.to_a.count @@ -1284,10 +1284,10 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_deep_including_through_habtm # warm up habtm cache - posts = Post.all.merge!(includes: { categories: :categorizations }, order: Arel.sql("posts.id")).to_a + posts = Post.all.merge!(includes: { categories: :categorizations }, order: "posts.id").to_a posts[0].categories[0].categorizations.length - posts = Post.all.merge!(includes: { categories: :categorizations }, order: Arel.sql("posts.id")).to_a + posts = Post.all.merge!(includes: { categories: :categorizations }, order: "posts.id").to_a assert_no_queries { assert_equal 2, posts[0].categories[0].categorizations.length } assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length } assert_no_queries { assert_equal 2, posts[1].categories[0].categorizations.length } @@ -1513,6 +1513,6 @@ class EagerAssociationTest < ActiveRecord::TestCase private def find_all_ordered(klass, include = nil) - klass.order(Arel.sql("#{klass.table_name}.#{klass.primary_key}")).includes(include).to_a + klass.order("#{klass.table_name}.#{klass.primary_key}").includes(include).to_a end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 85733f9056..3597da7ff3 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -48,7 +48,7 @@ class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCa author = authors(:david) # this can fail on adapters which require ORDER BY expressions to be included in the SELECT expression # if the reorder clauses are not correctly handled - assert author.posts_with_comments_sorted_by_comment_id.where("comments.id > 0").reorder(Arel.sql("posts.comments_count DESC"), Arel.sql("posts.tags_count DESC")).last + assert author.posts_with_comments_sorted_by_comment_id.where("comments.id > 0").reorder("posts.comments_count DESC", "posts.tags_count DESC").last end end @@ -1123,7 +1123,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_deleting_updates_counter_cache - topic = Topic.order(Arel.sql("id ASC")).first + topic = Topic.order("id ASC").first assert_equal topic.replies.to_a.size, topic.replies_count topic.replies.delete(topic.replies.first) @@ -1162,7 +1162,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_pushing_association_updates_counter_cache - topic = Topic.order(Arel.sql("id ASC")).first + topic = Topic.order("id ASC").first reply = Reply.create! assert_difference "topic.reload.replies_count", 1 do @@ -1212,7 +1212,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_calling_update_attributes_on_id_changes_the_counter_cache - topic = Topic.order(Arel.sql("id ASC")).first + topic = Topic.order("id ASC").first original_count = topic.replies.to_a.size assert_equal original_count, topic.replies_count diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index ed96eb54c1..046020e310 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -71,7 +71,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def self.name; "Person"; end has_many :readers - has_many :posts, -> { order(Arel.sql("posts.id DESC")) }, through: :readers + has_many :posts, -> { order("posts.id DESC") }, through: :readers end posts = person_prime.includes(:posts).first.posts @@ -985,7 +985,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_joining_has_many_through_belongs_to - posts = Post.joins(:author_categorizations).order(Arel.sql("posts.id")). + posts = Post.joins(:author_categorizations).order("posts.id"). where("categorizations.id" => categorizations(:mary_thinking_sti).id) assert_equal [posts(:eager_other), posts(:misc_by_mary), posts(:other_by_mary)], posts diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 8f08684820..a3acb1a152 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -244,8 +244,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_has_many_through - posts = Post.all.merge!(order: Arel.sql("posts.id")).to_a - posts_with_authors = Post.all.merge!(includes: :authors, order: Arel.sql("posts.id")).to_a + posts = Post.all.merge!(order: "posts.id").to_a + posts_with_authors = Post.all.merge!(includes: :authors, order: "posts.id").to_a assert_equal posts.length, posts_with_authors.length posts.length.times do |i| assert_equal posts[i].authors.length, assert_no_queries { posts_with_authors[i].authors.length } @@ -269,8 +269,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_many_through - posts = Post.all.merge!(order: Arel.sql("posts.id")).to_a - posts_with_tags = Post.all.merge!(includes: :tags, order: Arel.sql("posts.id")).to_a + posts = Post.all.merge!(order: "posts.id").to_a + posts_with_tags = Post.all.merge!(includes: :tags, order: "posts.id").to_a assert_equal posts.length, posts_with_tags.length posts.length.times do |i| assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length } @@ -278,8 +278,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_many - posts = Post.all.merge!(order: Arel.sql("posts.id")).to_a - posts_with_taggings = Post.all.merge!(includes: :taggings, order: Arel.sql("posts.id")).to_a + posts = Post.all.merge!(order: "posts.id").to_a + posts_with_taggings = Post.all.merge!(includes: :taggings, order: "posts.id").to_a assert_equal posts.length, posts_with_taggings.length posts.length.times do |i| assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length } @@ -326,7 +326,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_with_custom_primary_key_on_has_many_source - assert_equal [authors(:david), authors(:bob)], posts(:thinking).authors_using_custom_pk.order(Arel.sql("authors.id")) + assert_equal [authors(:david), authors(:bob)], posts(:thinking).authors_using_custom_pk.order("authors.id") end def test_belongs_to_polymorphic_with_counter_cache @@ -383,19 +383,19 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_has_many_find_all - assert_equal comments(:greetings), authors(:david).comments.order(Arel.sql("comments.id")).to_a.first + assert_equal comments(:greetings), authors(:david).comments.order("comments.id").to_a.first end def test_has_many_through_has_many_find_all_with_custom_class - assert_equal comments(:greetings), authors(:david).funky_comments.order(Arel.sql("comments.id")).to_a.first + assert_equal comments(:greetings), authors(:david).funky_comments.order("comments.id").to_a.first end def test_has_many_through_has_many_find_first - assert_equal comments(:greetings), authors(:david).comments.order(Arel.sql("comments.id")).first + assert_equal comments(:greetings), authors(:david).comments.order("comments.id").first end def test_has_many_through_has_many_find_conditions - options = { where: "comments.#{QUOTED_TYPE}='SpecialComment'", order: Arel.sql("comments.id") } + options = { where: "comments.#{QUOTED_TYPE}='SpecialComment'", order: "comments.id" } assert_equal comments(:does_it_hurt), authors(:david).comments.merge(options).first end @@ -661,8 +661,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_preload_polymorphic_has_many_through - posts = Post.all.merge!(order: Arel.sql("posts.id")).to_a - posts_with_tags = Post.all.merge!(includes: :tags, order: Arel.sql("posts.id")).to_a + posts = Post.all.merge!(order: "posts.id").to_a + posts_with_tags = Post.all.merge!(includes: :tags, order: "posts.id").to_a assert_equal posts.length, posts_with_tags.length posts.length.times do |i| assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length } @@ -688,8 +688,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_preload_polymorphic_has_many - posts = Post.all.merge!(order: Arel.sql("posts.id")).to_a - posts_with_taggings = Post.all.merge!(includes: :taggings, order: Arel.sql("posts.id")).to_a + posts = Post.all.merge!(order: "posts.id").to_a + posts_with_taggings = Post.all.merge!(includes: :taggings, order: "posts.id").to_a assert_equal posts.length, posts_with_taggings.length posts.length.times do |i| assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length } diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index b8e8f48ae5..0254da9a99 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -86,7 +86,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase # Through: has_many through def test_has_many_through_has_many_through_with_has_many_source_reflection luke, david = subscribers(:first), subscribers(:second) - assert_equal [luke, david, david], authors(:david).subscribers.order(Arel.sql("subscribers.nick")) + assert_equal [luke, david, david], authors(:david).subscribers.order("subscribers.nick") end def test_has_many_through_has_many_through_with_has_many_source_reflection_preload @@ -156,7 +156,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy) assert_equal [groucho_details, other_details], - members(:groucho).organization_member_details.order(Arel.sql("member_details.id")) + members(:groucho).organization_member_details.order("member_details.id") end def test_has_many_through_has_one_with_has_many_through_source_reflection_preload @@ -187,7 +187,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy) assert_equal [groucho_details, other_details], - members(:groucho).organization_member_details_2.order(Arel.sql("member_details.id")) + members(:groucho).organization_member_details_2.order("member_details.id") end def test_has_many_through_has_one_through_with_has_many_source_reflection_preload @@ -218,7 +218,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection general, cooking = categories(:general), categories(:cooking) - assert_equal [general, cooking], authors(:bob).post_categories.order(Arel.sql("categories.id")) + assert_equal [general, cooking], authors(:bob).post_categories.order("categories.id") end def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection_preload @@ -246,7 +246,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection greetings, more = comments(:greetings), comments(:more_greetings) - assert_equal [greetings, more], categories(:technology).post_comments.order(Arel.sql("comments.id")) + assert_equal [greetings, more], categories(:technology).post_comments.order("comments.id") end def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload @@ -264,7 +264,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase Category.joins(:post_comments).first assert_includes_and_joins_equal( - Category.where("comments.id" => comments(:more_greetings).id).order(Arel.sql("categories.id")), + Category.where("comments.id" => comments(:more_greetings).id).order("categories.id"), [categories(:general), categories(:technology)], :post_comments ) end @@ -275,7 +275,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection greetings, more = comments(:greetings), comments(:more_greetings) - assert_equal [greetings, more], authors(:bob).category_post_comments.order(Arel.sql("comments.id")) + assert_equal [greetings, more], authors(:bob).category_post_comments.order("comments.id") end def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload @@ -292,7 +292,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase Author.joins(:category_post_comments).first assert_includes_and_joins_equal( - Author.where("comments.id" => comments(:does_it_hurt).id).order(Arel.sql("authors.id")), + Author.where("comments.id" => comments(:does_it_hurt).id).order("authors.id"), [authors(:david), authors(:mary)], :category_post_comments ) end @@ -327,7 +327,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase welcome_general, thinking_general = taggings(:welcome_general), taggings(:thinking_general) assert_equal [welcome_general, thinking_general], - categorizations(:david_welcome_general).post_taggings.order(Arel.sql("taggings.id")) + categorizations(:david_welcome_general).post_taggings.order("taggings.id") end def test_has_many_through_belongs_to_with_has_many_through_source_reflection_preload @@ -411,7 +411,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_distinct_has_many_through_a_has_many_through_association_on_through_reflection author = authors(:david) assert_equal [subscribers(:first), subscribers(:second)], - author.distinct_subscribers.order(Arel.sql("subscribers.nick")) + author.distinct_subscribers.order("subscribers.nick") end def test_nested_has_many_through_with_a_table_referenced_multiple_times @@ -436,7 +436,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase end def test_has_many_through_with_foreign_key_option_on_through_reflection - assert_equal [posts(:welcome), posts(:authorless)], people(:david).agents_posts.order(Arel.sql("posts.id")) + assert_equal [posts(:welcome), posts(:authorless)], people(:david).agents_posts.order("posts.id") assert_equal [authors(:david)], references(:david_unicyclist).agents_posts_authors references = Reference.joins(:agents_posts_authors).where("authors.id" => authors(:david).id) @@ -444,7 +444,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase end def test_has_many_through_with_foreign_key_option_on_source_reflection - assert_equal [people(:michael), people(:susan)], jobs(:unicyclist).agents.order(Arel.sql("people.id")) + assert_equal [people(:michael), people(:susan)], jobs(:unicyclist).agents.order("people.id") jobs = Job.joins(:agents) assert_equal [jobs(:unicyclist), jobs(:unicyclist)], jobs diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index e2e4aa22f4..a45b3a1644 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -439,7 +439,7 @@ class BasicsTest < ActiveRecord::TestCase if current_adapter?(:Mysql2Adapter) def test_update_all_with_order_and_limit - assert_equal 1, Topic.limit(1).order(Arel.sql("id DESC")).update_all(content: "bulk updated!") + assert_equal 1, Topic.limit(1).order("id DESC").update_all(content: "bulk updated!") end end @@ -1081,11 +1081,11 @@ class BasicsTest < ActiveRecord::TestCase def test_find_last last = Developer.last - assert_equal last, Developer.all.merge!(order: Arel.sql("id desc")).first + assert_equal last, Developer.all.merge!(order: "id desc").first end def test_last - assert_equal Developer.all.merge!(order: Arel.sql("id desc")).first, Developer.last + assert_equal Developer.all.merge!(order: "id desc").first, Developer.last end def test_all @@ -1095,17 +1095,17 @@ class BasicsTest < ActiveRecord::TestCase end def test_all_with_conditions - assert_equal Developer.all.merge!(order: Arel.sql("id desc")).to_a, Developer.order(Arel.sql("id desc")).to_a + assert_equal Developer.all.merge!(order: "id desc").to_a, Developer.order("id desc").to_a end def test_find_ordered_last - last = Developer.all.merge!(order: Arel.sql("developers.salary ASC")).last - assert_equal last, Developer.all.merge!(order: Arel.sql("developers.salary ASC")).to_a.last + last = Developer.all.merge!(order: "developers.salary ASC").last + assert_equal last, Developer.all.merge!(order: "developers.salary ASC").to_a.last end def test_find_reverse_ordered_last - last = Developer.all.merge!(order: Arel.sql("developers.salary DESC")).last - assert_equal last, Developer.all.merge!(order: Arel.sql("developers.salary DESC")).to_a.last + last = Developer.all.merge!(order: "developers.salary DESC").last + assert_equal last, Developer.all.merge!(order: "developers.salary DESC").to_a.last end def test_find_multiple_ordered_last @@ -1115,7 +1115,7 @@ class BasicsTest < ActiveRecord::TestCase def test_find_keeps_multiple_order_values combined = Developer.all.merge!(order: Arel.sql("developers.name, developers.salary")).to_a - assert_equal combined, Developer.all.merge!(order: [Arel.sql("developers.name"), Arel.sql("developers.salary")]).to_a + assert_equal combined, Developer.all.merge!(order: ["developers.name", "developers.salary"]).to_a end def test_find_keeps_multiple_group_values diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index ff345d5f0e..be8aeed5ac 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -9,7 +9,7 @@ class EachTest < ActiveRecord::TestCase fixtures :posts, :subscribers def setup - @posts = Post.order(Arel.sql("id asc")) + @posts = Post.order("id asc") @total = Post.count Post.count("id") # preheat arel's table cache end @@ -101,7 +101,7 @@ class EachTest < ActiveRecord::TestCase previous_logger = ActiveRecord::Base.logger ActiveRecord::Base.logger = nil assert_nothing_raised do - Post.order(Arel.sql("comments_count DESC")).find_each { |post| post } + Post.order("comments_count DESC").find_each { |post| post } end ensure ActiveRecord::Base.logger = previous_logger @@ -233,7 +233,7 @@ class EachTest < ActiveRecord::TestCase end def test_find_in_batches_should_use_any_column_as_primary_key - nick_order_subscribers = Subscriber.order(Arel.sql("nick asc")) + nick_order_subscribers = Subscriber.order("nick asc") start_nick = nick_order_subscribers.second.nick subscribers = [] @@ -329,7 +329,7 @@ class EachTest < ActiveRecord::TestCase end def test_in_batches_each_record_should_be_ordered_by_id - ids = Post.order(Arel.sql("id ASC")).pluck(:id) + ids = Post.order("id ASC").pluck(:id) assert_queries(6) do Post.in_batches(of: 2).each_record.with_index do |post, i| assert_equal ids[i], post.id @@ -384,7 +384,7 @@ class EachTest < ActiveRecord::TestCase end def test_in_batches_should_start_from_the_start_option - post = Post.order(Arel.sql("id ASC")).where("id >= ?", 2).first + post = Post.order("id ASC").where("id >= ?", 2).first assert_queries(2) do relation = Post.in_batches(of: 1, start: 2).first assert_equal post, relation.first @@ -392,7 +392,7 @@ class EachTest < ActiveRecord::TestCase end def test_in_batches_should_end_at_the_finish_option - post = Post.order(Arel.sql("id DESC")).where("id <= ?", 5).first + post = Post.order("id DESC").where("id <= ?", 5).first assert_queries(7) do relation = Post.in_batches(of: 1, finish: 5, load: true).reverse_each.first assert_equal post, relation.last @@ -451,7 +451,7 @@ class EachTest < ActiveRecord::TestCase end def test_in_batches_should_use_any_column_as_primary_key - nick_order_subscribers = Subscriber.order(Arel.sql("nick asc")) + nick_order_subscribers = Subscriber.order("nick asc") start_nick = nick_order_subscribers.second.nick subscribers = [] diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 65ebfcd989..344d1a6639 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -813,9 +813,9 @@ class FinderTest < ActiveRecord::TestCase end def test_hash_condition_find_with_array - p1, p2 = Post.limit(2).order(Arel.sql("id asc")).to_a - assert_equal [p1, p2], Post.where(id: [p1, p2]).order(Arel.sql("id asc")).to_a - assert_equal [p1, p2], Post.where(id: [p1, p2.id]).order(Arel.sql("id asc")).to_a + p1, p2 = Post.limit(2).order("id asc").to_a + assert_equal [p1, p2], Post.where(id: [p1, p2]).order("id asc").to_a + assert_equal [p1, p2], Post.where(id: [p1, p2.id]).order("id asc").to_a end def test_hash_condition_find_with_nil @@ -1013,7 +1013,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_one_attribute_with_several_options - assert_equal accounts(:unknown), Account.order(Arel.sql("id DESC")).where("id != ?", 3).find_by_credit_limit(50) + assert_equal accounts(:unknown), Account.order("id DESC").where("id != ?", 3).find_by_credit_limit(50) end def test_find_by_one_missing_attribute @@ -1041,7 +1041,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal devs[2], Developer.offset(2).first assert_equal devs[-3], Developer.offset(2).last - assert_equal devs[-3], Developer.offset(2).order(Arel.sql("id DESC")).first + assert_equal devs[-3], Developer.offset(2).order("id DESC").first end def test_find_by_nil_attribute @@ -1094,9 +1094,9 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_records - p1, p2 = Post.limit(2).order(Arel.sql("id asc")).to_a - assert_equal [p1, p2], Post.where(["id in (?)", [p1, p2]]).order(Arel.sql("id asc")) - assert_equal [p1, p2], Post.where(["id in (?)", [p1, p2.id]]).order(Arel.sql("id asc")) + p1, p2 = Post.limit(2).order("id asc").to_a + assert_equal [p1, p2], Post.where(["id in (?)", [p1, p2]]).order("id asc") + assert_equal [p1, p2], Post.where(["id in (?)", [p1, p2.id]]).order("id asc") end def test_select_value @@ -1136,7 +1136,7 @@ class FinderTest < ActiveRecord::TestCase client_of = Company. where(client_of: [2, 1, nil], name: ["37signals", "Summit", "Microsoft"]). - order(Arel.sql("client_of DESC")). + order("client_of DESC"). map(&:client_of) assert_includes client_of, nil @@ -1146,7 +1146,7 @@ class FinderTest < ActiveRecord::TestCase def test_find_with_nil_inside_set_passed_for_attribute client_of = Company. where(client_of: [nil]). - order(Arel.sql("client_of DESC")). + order("client_of DESC"). map(&:client_of) assert_equal [], client_of.compact @@ -1155,7 +1155,7 @@ class FinderTest < ActiveRecord::TestCase def test_with_limiting_with_custom_select posts = Post.references(:authors).merge( includes: :author, select: 'posts.*, authors.id as "author_id"', - limit: 3, order: Arel.sql("posts.id") + limit: 3, order: "posts.id" ).to_a assert_equal 3, posts.size assert_equal [0, 1, 1], posts.map(&:author_id).sort diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index 3b35df526f..8a656d7720 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -13,10 +13,10 @@ class RelationMergingTest < ActiveRecord::TestCase fixtures :developers, :comments, :authors, :author_addresses, :posts def test_relation_merging - devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order(Arel.sql("id ASC")).where("id < 3")) + devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order("id ASC").where("id < 3")) assert_equal [developers(:david), developers(:jamis)], devs.to_a - dev_with_count = Developer.limit(1).merge(Developer.order(Arel.sql("id DESC"))).merge(Developer.select("developers.*")) + dev_with_count = Developer.limit(1).merge(Developer.order("id DESC")).merge(Developer.select("developers.*")) assert_equal [developers(:poor_jamis)], dev_with_count.to_a end @@ -57,7 +57,7 @@ class RelationMergingTest < ActiveRecord::TestCase end def test_relation_merging_with_locks - devs = Developer.lock.where("salary >= 80000").order(Arel.sql("id DESC")).merge(Developer.limit(2)) + devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2)) assert devs.locked? end @@ -118,7 +118,7 @@ class MergingDifferentRelationsTest < ActiveRecord::TestCase test "merging where relations" do hello_by_bob = Post.where(body: "hello").joins(:author). - merge(Author.where(name: "Bob")).order(Arel.sql("posts.id")).pluck(Arel.sql("posts.id")) + merge(Author.where(name: "Bob")).order("posts.id").pluck("posts.id") assert_equal [posts(:misc_by_bob).id, posts(:other_by_bob).id], hello_by_bob diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb index 2abc3b7fe8..7e418f9c7d 100644 --- a/activerecord/test/cases/relation/or_test.rb +++ b/activerecord/test/cases/relation/or_test.rb @@ -50,15 +50,15 @@ module ActiveRecord end def test_or_preserves_other_querying_methods - expected = Post.where("id = 1 or id = 2 or id = 3").order(Arel.sql("body asc")).to_a - partial = Post.order(Arel.sql("body asc")) + expected = Post.where("id = 1 or id = 2 or id = 3").order("body asc").to_a + partial = Post.order("body asc") assert_equal expected, partial.where("id = 1").or(partial.where(id: [2, 3])).to_a - assert_equal expected, Post.order(Arel.sql("body asc")).where("id = 1").or(Post.order(Arel.sql("body asc")).where(id: [2, 3])).to_a + assert_equal expected, Post.order("body asc").where("id = 1").or(Post.order("body asc").where(id: [2, 3])).to_a end def test_or_with_incompatible_relations error = assert_raises ArgumentError do - Post.order(Arel.sql("body asc")).where("id = 1").or(Post.order(Arel.sql("id desc")).where(id: [2, 3])).to_a + Post.order("body asc").where("id = 1").or(Post.order("id desc").where(id: [2, 3])).to_a end assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message @@ -78,12 +78,12 @@ module ActiveRecord def test_or_with_unscope_order expected = Post.where("id = 1 or id = 2") - assert_equal expected, Post.order(Arel.sql("body asc")).where("id = 1").unscope(:order).or(Post.where("id = 2")).to_a + assert_equal expected, Post.order("body asc").where("id = 1").unscope(:order).or(Post.where("id = 2")).to_a end def test_or_with_incompatible_unscope error = assert_raises ArgumentError do - Post.order(Arel.sql("body asc")).where("id = 1").or(Post.order(Arel.sql("body asc")).where("id = 2").unscope(:order)).to_a + Post.order("body asc").where("id = 1").or(Post.order("body asc").where("id = 2").unscope(:order)).to_a end assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message @@ -101,7 +101,7 @@ module ActiveRecord end def test_or_inside_named_scope - expected = Post.where("body LIKE '\%a\%' OR title LIKE ?", "%'%").order(Arel.sql("id DESC")).to_a + expected = Post.where("body LIKE '\%a\%' OR title LIKE ?", "%'%").order("id DESC").to_a assert_equal expected, Post.order(id: :desc).typographically_interesting end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 906f3499dd..ab2e432e78 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -102,7 +102,7 @@ class RelationTest < ActiveRecord::TestCase end def test_scoped_first - topics = Topic.all.order(Arel.sql("id ASC")) + topics = Topic.all.order("id ASC") assert_queries(1) do 2.times { assert_equal "The First Topic", topics.first.title } @@ -112,7 +112,7 @@ class RelationTest < ActiveRecord::TestCase end def test_loaded_first - topics = Topic.all.order(Arel.sql("id ASC")) + topics = Topic.all.order("id ASC") topics.load # force load assert_no_queries do @@ -123,7 +123,7 @@ class RelationTest < ActiveRecord::TestCase end def test_loaded_first_with_limit - topics = Topic.all.order(Arel.sql("id ASC")) + topics = Topic.all.order("id ASC") topics.load # force load assert_no_queries do @@ -135,7 +135,7 @@ class RelationTest < ActiveRecord::TestCase end def test_first_get_more_than_available - topics = Topic.all.order(Arel.sql("id ASC")) + topics = Topic.all.order("id ASC") unloaded_first = topics.first(10) topics.load # force load @@ -220,7 +220,7 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_arel_assoc_order - topics = Topic.order(Arel.sql("id") => :desc) + topics = Topic.order("id" => :desc) assert_equal 5, topics.to_a.size assert_equal topics(:fifth).title, topics.first.title end @@ -232,7 +232,7 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_reversed_arel_assoc_order - topics = Topic.order(Arel.sql("id") => :asc).reverse_order + topics = Topic.order("id" => :asc).reverse_order assert_equal 5, topics.to_a.size assert_equal topics(:fifth).title, topics.first.title end @@ -319,7 +319,7 @@ class RelationTest < ActiveRecord::TestCase end def test_raising_exception_on_invalid_hash_params - e = assert_raise(ArgumentError) { Topic.order(Arel.sql("name"), Arel.sql("id DESC"), id: :asfsdf) } + e = assert_raise(ArgumentError) { Topic.order(Arel.sql("name"), "id DESC", id: :asfsdf) } assert_equal 'Direction "asfsdf" is invalid. Valid directions are: [:asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]', e.message end @@ -365,7 +365,7 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_order_and_take - entrants = Entrant.order(Arel.sql("id ASC")).limit(2).to_a + entrants = Entrant.order("id ASC").limit(2).to_a assert_equal 2, entrants.size assert_equal entrants(:first).name, entrants.first.name @@ -374,7 +374,7 @@ class RelationTest < ActiveRecord::TestCase def test_finding_with_cross_table_order_and_limit tags = Tag.includes(:taggings). order( - Arel.sql("tags.name asc"), + "tags.name asc", Arel.sql("taggings.taggable_id asc"), Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)") ).limit(1).to_a @@ -403,12 +403,12 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_order_limit_and_offset - entrants = Entrant.order(Arel.sql("id ASC")).limit(2).offset(1) + entrants = Entrant.order("id ASC").limit(2).offset(1) assert_equal 2, entrants.to_a.size assert_equal entrants(:second).name, entrants.first.name - entrants = Entrant.order(Arel.sql("id ASC")).limit(2).offset(2) + entrants = Entrant.order("id ASC").limit(2).offset(2) assert_equal 1, entrants.to_a.size assert_equal entrants(:third).name, entrants.first.name end @@ -518,27 +518,27 @@ class RelationTest < ActiveRecord::TestCase def test_find_with_preloaded_associations assert_queries(2) do - posts = Post.preload(:comments).order(Arel.sql("posts.id")) + posts = Post.preload(:comments).order("posts.id") assert posts.first.comments.first end assert_queries(2) do - posts = Post.preload(:comments).order(Arel.sql("posts.id")) + posts = Post.preload(:comments).order("posts.id") assert posts.first.comments.first end assert_queries(2) do - posts = Post.preload(:author).order(Arel.sql("posts.id")) + posts = Post.preload(:author).order("posts.id") assert posts.first.author end assert_queries(2) do - posts = Post.preload(:author).order(Arel.sql("posts.id")) + posts = Post.preload(:author).order("posts.id") assert posts.first.author end assert_queries(3) do - posts = Post.preload(:author, :comments).order(Arel.sql("posts.id")) + posts = Post.preload(:author, :comments).order("posts.id") assert posts.first.author assert posts.first.comments.first end @@ -553,22 +553,22 @@ class RelationTest < ActiveRecord::TestCase def test_find_with_included_associations assert_queries(2) do - posts = Post.includes(:comments).order(Arel.sql("posts.id")) + posts = Post.includes(:comments).order("posts.id") assert posts.first.comments.first end assert_queries(2) do - posts = Post.all.includes(:comments).order(Arel.sql("posts.id")) + posts = Post.all.includes(:comments).order("posts.id") assert posts.first.comments.first end assert_queries(2) do - posts = Post.includes(:author).order(Arel.sql("posts.id")) + posts = Post.includes(:author).order("posts.id") assert posts.first.author end assert_queries(3) do - posts = Post.includes(:author, :comments).order(Arel.sql("posts.id")) + posts = Post.includes(:author, :comments).order("posts.id") assert posts.first.author assert posts.first.comments.first end @@ -698,7 +698,7 @@ class RelationTest < ActiveRecord::TestCase end def test_find_ids - authors = Author.order(Arel.sql("id ASC")) + authors = Author.order("id ASC") results = authors.find(authors(:david).id, authors(:mary).id) assert_kind_of Array, results @@ -982,7 +982,7 @@ class RelationTest < ActiveRecord::TestCase end def test_multiple_selects - post = Post.all.select("comments_count").select("title").order(Arel.sql("id ASC")).first + post = Post.all.select("comments_count").select("title").order("id ASC").first assert_equal "Welcome to the weblog", post.title assert_equal 2, post.comments_count end @@ -1344,7 +1344,7 @@ class RelationTest < ActiveRecord::TestCase end def test_except - relation = Post.where(author_id: 1).order(Arel.sql("id ASC")).limit(1) + relation = Post.where(author_id: 1).order("id ASC").limit(1) assert_equal [posts(:welcome)], relation.to_a author_posts = relation.except(:order, :limit) @@ -1355,7 +1355,7 @@ class RelationTest < ActiveRecord::TestCase end def test_only - relation = Post.where(author_id: 1).order(Arel.sql("id ASC")).limit(1) + relation = Post.where(author_id: 1).order("id ASC").limit(1) assert_equal [posts(:welcome)], relation.to_a author_posts = relation.only(:where) @@ -1366,7 +1366,7 @@ class RelationTest < ActiveRecord::TestCase end def test_anonymous_extension - relation = Post.where(author_id: 1).order(Arel.sql("id ASC")).extending do + relation = Post.where(author_id: 1).order("id ASC").extending do def author "lifo" end @@ -1377,7 +1377,7 @@ class RelationTest < ActiveRecord::TestCase end def test_named_extension - relation = Post.where(author_id: 1).order(Arel.sql("id ASC")).extending(Post::NamedExtension) + relation = Post.where(author_id: 1).order("id ASC").extending(Post::NamedExtension) assert_equal "lifo", relation.author assert_equal "lifo", relation.limit(1).author end @@ -1392,13 +1392,13 @@ class RelationTest < ActiveRecord::TestCase end def test_order_using_scoping - car1 = CoolCar.order(Arel.sql("id DESC")).scoping do - CoolCar.all.merge!(order: Arel.sql("id asc")).first + car1 = CoolCar.order("id DESC").scoping do + CoolCar.all.merge!(order: "id asc").first end assert_equal "zyke", car1.name - car2 = FastCar.order(Arel.sql("id DESC")).scoping do - FastCar.all.merge!(order: Arel.sql("id asc")).first + car2 = FastCar.order("id DESC").scoping do + FastCar.all.merge!(order: "id asc").first end assert_equal "zyke", car2.name end @@ -1442,7 +1442,7 @@ class RelationTest < ActiveRecord::TestCase end def test_update_all_with_joins_and_limit_and_order - comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order(Arel.sql("comments.id")).limit(1) + comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("comments.id").limit(1) assert_equal 1, comments.update_all(post_id: posts(:thinking).id) assert_equal posts(:thinking), comments(:greetings).post assert_equal posts(:welcome), comments(:more_greetings).post @@ -1457,7 +1457,7 @@ class RelationTest < ActiveRecord::TestCase end def test_update_all_with_joins_and_offset_and_order - all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order(Arel.sql("posts.id"), Arel.sql("comments.id")) + all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order(Arel.sql("posts.id"), "comments.id") count = all_comments.count comments = all_comments.offset(1) @@ -1814,7 +1814,7 @@ class RelationTest < ActiveRecord::TestCase end test "joins with select" do - posts = Post.joins(:author).select("id", "authors.author_address_id").order(Arel.sql("posts.id")).limit(3) + posts = Post.joins(:author).select("id", "authors.author_address_id").order("posts.id").limit(3) assert_equal [1, 2, 4], posts.map(&:id) assert_equal [1, 1, 1], posts.map(&:author_address_id) end diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 26f0b86703..0d64db140c 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -13,7 +13,7 @@ class DefaultScopingTest < ActiveRecord::TestCase fixtures :developers, :posts, :comments def test_default_scope - expected = Developer.all.merge!(order: Arel.sql("salary DESC")).to_a.collect(&:salary) + expected = Developer.all.merge!(order: "salary DESC").to_a.collect(&:salary) received = DeveloperOrderedBySalary.all.collect(&:salary) assert_equal expected, received end @@ -86,14 +86,14 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_reorder_overrides_default_scope_order - expected = Developer.order(Arel.sql("name DESC")).collect(&:name) - received = DeveloperOrderedBySalary.reorder(Arel.sql("name DESC")).collect(&:name) + expected = Developer.order("name DESC").collect(&:name) + received = DeveloperOrderedBySalary.reorder("name DESC").collect(&:name) assert_equal expected, received end def test_order_after_reorder_combines_orders expected = Developer.order(Arel.sql("name DESC, id DESC")).collect { |dev| [dev.name, dev.id] } - received = Developer.order(Arel.sql("name ASC")).reorder(Arel.sql("name DESC")).order(Arel.sql("id DESC")).collect { |dev| [dev.name, dev.id] } + received = Developer.order("name ASC").reorder("name DESC").order("id DESC").collect { |dev| [dev.name, dev.id] } assert_equal expected, received end @@ -105,7 +105,7 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_unscope_after_reordering_and_combining expected = Developer.order(Arel.sql("id DESC, name DESC")).collect { |dev| [dev.name, dev.id] } - received = DeveloperOrderedBySalary.reorder(Arel.sql("name DESC")).unscope(:order).order(Arel.sql("id DESC, name DESC")).collect { |dev| [dev.name, dev.id] } + received = DeveloperOrderedBySalary.reorder("name DESC").unscope(:order).order(Arel.sql("id DESC, name DESC")).collect { |dev| [dev.name, dev.id] } assert_equal expected, received expected_2 = Developer.all.collect { |dev| [dev.name, dev.id] } @@ -113,60 +113,60 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected_2, received_2 expected_3 = Developer.all.collect { |dev| [dev.name, dev.id] } - received_3 = Developer.reorder(Arel.sql("name DESC")).unscope(:order).collect { |dev| [dev.name, dev.id] } + received_3 = Developer.reorder("name DESC").unscope(:order).collect { |dev| [dev.name, dev.id] } assert_equal expected_3, received_3 end def test_unscope_with_where_attributes - expected = Developer.order(Arel.sql("salary DESC")).collect(&:name) + expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.where(name: "David").unscope(where: :name).collect(&:name) assert_equal expected, received - expected_2 = Developer.order(Arel.sql("salary DESC")).collect(&:name) + expected_2 = Developer.order("salary DESC").collect(&:name) received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({ where: :name }, :select).collect(&:name) assert_equal expected_2, received_2 - expected_3 = Developer.order(Arel.sql("salary DESC")).collect(&:name) + expected_3 = Developer.order("salary DESC").collect(&:name) received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect(&:name) assert_equal expected_3, received_3 - expected_4 = Developer.order(Arel.sql("salary DESC")).collect(&:name) + expected_4 = Developer.order("salary DESC").collect(&:name) received_4 = DeveloperOrderedBySalary.where.not("name" => "Jamis").unscope(where: :name).collect(&:name) assert_equal expected_4, received_4 - expected_5 = Developer.order(Arel.sql("salary DESC")).collect(&:name) + expected_5 = Developer.order("salary DESC").collect(&:name) received_5 = DeveloperOrderedBySalary.where.not("name" => ["Jamis", "David"]).unscope(where: :name).collect(&:name) assert_equal expected_5, received_5 - expected_6 = Developer.order(Arel.sql("salary DESC")).collect(&:name) + expected_6 = Developer.order("salary DESC").collect(&:name) received_6 = DeveloperOrderedBySalary.where(Developer.arel_table["name"].eq("David")).unscope(where: :name).collect(&:name) assert_equal expected_6, received_6 - expected_7 = Developer.order(Arel.sql("salary DESC")).collect(&:name) + expected_7 = Developer.order("salary DESC").collect(&:name) received_7 = DeveloperOrderedBySalary.where(Developer.arel_table[:name].eq("David")).unscope(where: :name).collect(&:name) assert_equal expected_7, received_7 end def test_unscope_comparison_where_clauses # unscoped for WHERE (`developers`.`id` <= 2) - expected = Developer.order(Arel.sql("salary DESC")).collect(&:name) + expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.where(id: -Float::INFINITY..2).unscope(where: :id).collect { |dev| dev.name } assert_equal expected, received # unscoped for WHERE (`developers`.`id` < 2) - expected = Developer.order(Arel.sql("salary DESC")).collect(&:name) + expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.where(id: -Float::INFINITY...2).unscope(where: :id).collect { |dev| dev.name } assert_equal expected, received end def test_unscope_multiple_where_clauses - expected = Developer.order(Arel.sql("salary DESC")).collect(&:name) + expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.where(name: "Jamis").where(id: 1).unscope(where: [:name, :id]).collect(&:name) assert_equal expected, received end def test_unscope_string_where_clauses_involved - dev_relation = Developer.order(Arel.sql("salary DESC")).where("created_at > ?", 1.year.ago) + dev_relation = Developer.order("salary DESC").where("created_at > ?", 1.year.ago) expected = dev_relation.collect(&:name) dev_ordered_relation = DeveloperOrderedBySalary.where(name: "Jamis").where("created_at > ?", 1.year.ago) @@ -176,17 +176,17 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_unscope_with_grouping_attributes - expected = Developer.order(Arel.sql("salary DESC")).collect(&:name) + expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect(&:name) assert_equal expected, received - expected_2 = Developer.order(Arel.sql("salary DESC")).collect(&:name) + expected_2 = Developer.order("salary DESC").collect(&:name) received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect(&:name) assert_equal expected_2, received_2 end def test_unscope_with_limit_in_query - expected = Developer.order(Arel.sql("salary DESC")).collect(&:name) + expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect(&:name) assert_equal expected, received end @@ -198,13 +198,13 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_unscope_reverse_order expected = Developer.all.collect(&:name) - received = Developer.order(Arel.sql("salary DESC")).reverse_order.unscope(:order).collect(&:name) + received = Developer.order("salary DESC").reverse_order.unscope(:order).collect(&:name) assert_equal expected, received end def test_unscope_select - expected = Developer.order(Arel.sql("salary ASC")).collect(&:name) - received = Developer.order(Arel.sql("salary DESC")).reverse_order.select(:name).unscope(:select).collect(&:name) + expected = Developer.order("salary ASC").collect(&:name) + received = Developer.order("salary DESC").reverse_order.select(:name).unscope(:select).collect(&:name) assert_equal expected, received expected_2 = Developer.all.collect(&:id) @@ -256,11 +256,11 @@ class DefaultScopingTest < ActiveRecord::TestCase end assert_raises(ArgumentError) do - Developer.order(Arel.sql("name DESC")).reverse_order.unscope(:reverse_order) + Developer.order("name DESC").reverse_order.unscope(:reverse_order) end assert_raises(ArgumentError) do - Developer.order(Arel.sql("name DESC")).where(name: "Jamis").unscope() + Developer.order("name DESC").where(name: "Jamis").unscope() end end @@ -295,7 +295,7 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_order_in_default_scope_should_not_prevail - expected = Developer.all.merge!(order: Arel.sql("salary desc")).to_a.collect(&:salary) + expected = Developer.all.merge!(order: "salary desc").to_a.collect(&:salary) received = DeveloperOrderedBySalary.all.merge!(order: "salary").to_a.collect(&:salary) assert_equal expected, received end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 4079bb477e..b0431a4e34 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -472,7 +472,7 @@ class NamedScopingTest < ActiveRecord::TestCase def test_scopes_on_relations # Topic.replied - approved_topics = Topic.all.approved.order(Arel.sql("id DESC")) + approved_topics = Topic.all.approved.order("id DESC") assert_equal topics(:fifth), approved_topics.first replied_approved_topics = approved_topics.replied @@ -480,7 +480,7 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_index_on_scope - approved = Topic.approved.order(Arel.sql("id ASC")) + approved = Topic.approved.order("id ASC") assert_equal topics(:second), approved[0] assert approved.loaded? end diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index 2a95204d0d..116f8e83aa 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -39,23 +39,23 @@ class RelationScopingTest < ActiveRecord::TestCase end def test_reverse_order - assert_equal Developer.order(Arel.sql("id DESC")).to_a.reverse, Developer.order(Arel.sql("id DESC")).reverse_order + assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order end def test_reverse_order_with_arel_node - assert_equal Developer.order(Arel.sql("id DESC")).to_a.reverse, Developer.order(Developer.arel_table[:id].desc).reverse_order + assert_equal Developer.order("id DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).reverse_order end def test_reverse_order_with_multiple_arel_nodes - assert_equal Developer.order(Arel.sql("id DESC")).order(Arel.sql("name DESC")).to_a.reverse, Developer.order(Developer.arel_table[:id].desc).order(Developer.arel_table[:name].desc).reverse_order + assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).order(Developer.arel_table[:name].desc).reverse_order end def test_reverse_order_with_arel_nodes_and_strings - assert_equal Developer.order(Arel.sql("id DESC")).order(Arel.sql("name DESC")).to_a.reverse, Developer.order(Arel.sql("id DESC")).order(Developer.arel_table[:name].desc).reverse_order + assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order("id DESC").order(Developer.arel_table[:name].desc).reverse_order end def test_double_reverse_order_produces_original_order - assert_equal Developer.order(Arel.sql("name DESC")), Developer.order(Arel.sql("name DESC")).reverse_order.reverse_order + assert_equal Developer.order("name DESC"), Developer.order("name DESC").reverse_order.reverse_order end def test_scoped_find @@ -72,7 +72,7 @@ class RelationScopingTest < ActiveRecord::TestCase end def test_scoped_find_last - highest_salary = Developer.order(Arel.sql("salary DESC")).first + highest_salary = Developer.order("salary DESC").first Developer.order("salary").scoping do assert_equal highest_salary, Developer.last @@ -80,8 +80,8 @@ class RelationScopingTest < ActiveRecord::TestCase end def test_scoped_find_last_preserves_scope - lowest_salary = Developer.order(Arel.sql("salary ASC")).first - highest_salary = Developer.order(Arel.sql("salary DESC")).first + lowest_salary = Developer.order("salary ASC").first + highest_salary = Developer.order("salary DESC").first Developer.order("salary").scoping do assert_equal highest_salary, Developer.last diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb index 74471b0ace..b32bb24ce8 100644 --- a/activerecord/test/cases/unsafe_raw_sql_test.rb +++ b/activerecord/test/cases/unsafe_raw_sql_test.rb @@ -77,10 +77,40 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase assert_equal ids_expected, ids_disabled end + test "order: allows table and column name" do + ids_expected = Post.order(Arel.sql("title")).pluck(:id) + + ids_depr = with_unsafe_raw_sql_deprecated { Post.order("posts.title").pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order("posts.title").pluck(:id) } + + assert_equal ids_expected, ids_depr + assert_equal ids_expected, ids_disabled + end + + test "order: allows column name and direction in string" do + ids_expected = Post.order(Arel.sql("title desc")).pluck(:id) + + ids_depr = with_unsafe_raw_sql_deprecated { Post.order("title desc").pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order("title desc").pluck(:id) } + + assert_equal ids_expected, ids_depr + assert_equal ids_expected, ids_disabled + end + + test "order: allows table name, column name and direction in string" do + ids_expected = Post.order(Arel.sql("title desc")).pluck(:id) + + ids_depr = with_unsafe_raw_sql_deprecated { Post.order("posts.title desc").pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order("posts.title desc").pluck(:id) } + + assert_equal ids_expected, ids_depr + assert_equal ids_expected, ids_disabled + end + test "order: disallows invalid column name" do with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do - Post.order("title asc").pluck(:id) + Post.order("foo asc").pluck(:id) end end end @@ -168,6 +198,16 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase assert_equal values_expected, values_disabled end + test "pluck: allows table and column names" do + titles_expected = Post.pluck(Arel.sql("title")) + + titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("posts.title") } + titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("posts.title") } + + assert_equal titles_expected, titles_depr + assert_equal titles_expected, titles_disabled + end + test "pluck: disallows invalid column name" do with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 11fe073ab0..f462fdb69a 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -8,9 +8,9 @@ class Author < ActiveRecord::Base has_many :posts_with_comments, -> { includes(:comments) }, class_name: "Post" has_many :popular_grouped_posts, -> { includes(:comments).group("type").having("SUM(comments_count) > 1").select("type") }, class_name: "Post" has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order(Arel.sql("comments.id")) }, class_name: "Post" - has_many :posts_sorted_by_id_limited, -> { order(Arel.sql("posts.id")).limit(1) }, class_name: "Post" + has_many :posts_sorted_by_id_limited, -> { order("posts.id").limit(1) }, class_name: "Post" has_many :posts_with_categories, -> { includes(:categories) }, class_name: "Post" - has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order(Arel.sql("posts.id")) }, class_name: "Post" + has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, class_name: "Post" has_many :posts_with_special_categorizations, class_name: "PostWithSpecialCategorization" has_one :post_about_thinking, -> { where("posts.title like '%thinking%'") }, class_name: "Post" has_one :post_about_thinking_with_last_comment, -> { where("posts.title like '%thinking%'").includes(:last_comment) }, class_name: "Post" diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index 0be943a321..3d6a7a96c2 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -19,13 +19,13 @@ class Car < ActiveRecord::Base scope :incl_tyres, -> { includes(:tyres) } scope :incl_engines, -> { includes(:engines) } - scope :order_using_new_style, -> { order(Arel.sql("name asc")) } + scope :order_using_new_style, -> { order("name asc") } end class CoolCar < Car - default_scope { order(Arel.sql("name desc")) } + default_scope { order("name desc") } end class FastCar < Car - default_scope { order(Arel.sql("name desc")) } + default_scope { order("name desc") } end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 1a82a9e646..bbc5fc2b2d 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -50,7 +50,7 @@ class Firm < Company has_many :clients, -> { order "id" }, dependent: :destroy, before_remove: :log_before_remove, after_remove: :log_after_remove has_many :unsorted_clients, class_name: "Client" has_many :unsorted_clients_with_symbol, class_name: :Client - has_many :clients_sorted_desc, -> { order Arel.sql("id DESC") }, class_name: "Client" + has_many :clients_sorted_desc, -> { order "id DESC" }, class_name: "Client" has_many :clients_of_firm, -> { order "id" }, foreign_key: "client_of", class_name: "Client", inverse_of: :firm has_many :clients_ordered_by_name, -> { order "name" }, class_name: "Client" has_many :unvalidated_clients_of_firm, foreign_key: "client_of", class_name: "Client", validate: false diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb index 9108eb5249..52b7e06a63 100644 --- a/activerecord/test/models/company_in_module.rb +++ b/activerecord/test/models/company_in_module.rb @@ -9,7 +9,7 @@ module MyApplication class Firm < Company has_many :clients, -> { order("id") }, dependent: :destroy - has_many :clients_sorted_desc, -> { order(Arel.sql("id DESC")) }, class_name: "Client" + has_many :clients_sorted_desc, -> { order("id DESC") }, class_name: "Client" has_many :clients_of_firm, -> { order "id" }, foreign_key: "client_of", class_name: "Client" has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, class_name: "Client" has_one :account, class_name: "MyApplication::Billing::Account", dependent: :destroy diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index c2a21eac04..8881c69368 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -4,7 +4,7 @@ require "ostruct" module DeveloperProjectsAssociationExtension2 def find_least_recent - order(Arel.sql("id ASC")).first + order("id ASC").first end end @@ -13,7 +13,7 @@ class Developer < ActiveRecord::Base has_and_belongs_to_many :projects do def find_most_recent - order(Arel.sql("id DESC")).first + order("id DESC").first end end @@ -41,7 +41,7 @@ class Developer < ActiveRecord::Base join_table: "developers_projects", association_foreign_key: "project_id" do def find_least_recent - order(Arel.sql("id ASC")).first + order("id ASC").first end end @@ -126,7 +126,7 @@ end class DeveloperFilteredOnJoins < ActiveRecord::Base self.table_name = "developers" - has_and_belongs_to_many :projects, -> { order(Arel.sql("projects.id")) }, foreign_key: "developer_id", join_table: "developers_projects" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" def self.default_scope joins(:projects).where(projects: { name: "Active Controller" }) @@ -135,9 +135,9 @@ end class DeveloperOrderedBySalary < ActiveRecord::Base self.table_name = "developers" - default_scope { order(Arel.sql("salary DESC")) } + default_scope { order("salary DESC") } - scope :by_name, -> { order(Arel.sql("name DESC")) } + scope :by_name, -> { order("name DESC") } end class DeveloperCalledDavid < ActiveRecord::Base @@ -225,14 +225,14 @@ end class EagerDeveloperWithDefaultScope < ActiveRecord::Base self.table_name = "developers" - has_and_belongs_to_many :projects, -> { order(Arel.sql("projects.id")) }, foreign_key: "developer_id", join_table: "developers_projects" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" default_scope { includes(:projects) } end class EagerDeveloperWithClassMethodDefaultScope < ActiveRecord::Base self.table_name = "developers" - has_and_belongs_to_many :projects, -> { order(Arel.sql("projects.id")) }, foreign_key: "developer_id", join_table: "developers_projects" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" def self.default_scope includes(:projects) @@ -241,21 +241,21 @@ end class EagerDeveloperWithLambdaDefaultScope < ActiveRecord::Base self.table_name = "developers" - has_and_belongs_to_many :projects, -> { order(Arel.sql("projects.id")) }, foreign_key: "developer_id", join_table: "developers_projects" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" default_scope lambda { includes(:projects) } end class EagerDeveloperWithBlockDefaultScope < ActiveRecord::Base self.table_name = "developers" - has_and_belongs_to_many :projects, -> { order(Arel.sql("projects.id")) }, foreign_key: "developer_id", join_table: "developers_projects" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" default_scope { includes(:projects) } end class EagerDeveloperWithCallableDefaultScope < ActiveRecord::Base self.table_name = "developers" - has_and_belongs_to_many :projects, -> { order(Arel.sql("projects.id")) }, foreign_key: "developer_id", join_table: "developers_projects" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" default_scope OpenStruct.new(call: includes(:projects)) end diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb index 47cb4d2146..09ee7544b3 100644 --- a/activerecord/test/models/membership.rb +++ b/activerecord/test/models/membership.rb @@ -12,7 +12,7 @@ class CurrentMembership < Membership end class SuperMembership < Membership - belongs_to :member, -> { order(Arel.sql("members.id DESC")) } + belongs_to :member, -> { order("members.id DESC") } belongs_to :club end diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index 99de37a52f..c8617d1cfe 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -3,7 +3,7 @@ class Pirate < ActiveRecord::Base belongs_to :parrot, validate: true belongs_to :non_validated_parrot, class_name: "Parrot" - has_and_belongs_to_many :parrots, -> { order(Arel.sql("parrots.id ASC")) }, validate: true + has_and_belongs_to_many :parrots, -> { order("parrots.id ASC") }, validate: true has_and_belongs_to_many :non_validated_parrots, class_name: "Parrot" has_and_belongs_to_many :parrots_with_method_callbacks, class_name: "Parrot", before_add: :log_before_add, @@ -23,7 +23,7 @@ class Pirate < ActiveRecord::Base has_one :ship has_one :update_only_ship, class_name: "Ship" has_one :non_validated_ship, class_name: "Ship" - has_many :birds, -> { order(Arel.sql("birds.id ASC")) } + has_many :birds, -> { order("birds.id ASC") } has_many :birds_with_method_callbacks, class_name: "Bird", before_add: :log_before_add, after_add: :log_after_add, diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 49bbbaaab7..4508f727d0 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -35,8 +35,8 @@ class Post < ActiveRecord::Base def first_comment super.body end - has_one :first_comment, -> { order(Arel.sql("id ASC")) }, class_name: "Comment" - has_one :last_comment, -> { order(Arel.sql("id desc")) }, class_name: "Comment" + has_one :first_comment, -> { order("id ASC") }, class_name: "Comment" + has_one :last_comment, -> { order("id desc") }, class_name: "Comment" scope :with_special_comments, -> { joins(:comments).where(comments: { type: "SpecialComment" }) } scope :with_very_special_comments, -> { joins(:comments).where(comments: { type: "VerySpecialComment" }) } @@ -52,7 +52,7 @@ class Post < ActiveRecord::Base has_many :comments do def find_most_recent - order(Arel.sql("id DESC")).first + order("id DESC").first end def newest @@ -323,5 +323,9 @@ class FakeKlass def enforce_raw_sql_whitelist(*args) # noop end + + def attribute_names_and_aliases + [] + end end end diff --git a/activerecord/test/models/tag.rb b/activerecord/test/models/tag.rb index e0d42f4f66..bc13c3a42d 100644 --- a/activerecord/test/models/tag.rb +++ b/activerecord/test/models/tag.rb @@ -11,6 +11,6 @@ end class OrderedTag < Tag self.table_name = "tags" - has_many :taggings, -> { order(Arel.sql("taggings.id DESC")) }, foreign_key: "tag_id" + has_many :taggings, -> { order("taggings.id DESC") }, foreign_key: "tag_id" has_many :tagged_posts, through: :taggings, source: "taggable", source_type: "Post" end -- 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/attribute_methods.rb | 48 +++++++-------------- .../lib/active_record/relation/calculations.rb | 12 ++---- .../lib/active_record/relation/query_methods.rb | 21 +--------- .../associations/cascaded_eager_loading_test.rb | 8 ++-- .../associations/eager_load_nested_include_test.rb | 2 +- activerecord/test/cases/associations/eager_test.rb | 20 ++++----- .../has_and_belongs_to_many_associations_test.rb | 2 +- .../associations/has_many_associations_test.rb | 2 +- .../has_one_through_associations_test.rb | 6 +-- .../associations/inverse_associations_test.rb | 8 ++-- .../test/cases/associations/join_model_test.rb | 2 +- .../nested_through_associations_test.rb | 6 +-- activerecord/test/cases/base_test.rb | 6 +-- activerecord/test/cases/calculations_test.rb | 14 +++---- activerecord/test/cases/finder_test.rb | 14 +++---- activerecord/test/cases/relation/merging_test.rb | 10 ++--- activerecord/test/cases/relation/mutation_test.rb | 2 +- activerecord/test/cases/relations_test.rb | 49 ++++++++++------------ .../test/cases/scoping/default_scoping_test.rb | 12 +++--- activerecord/test/cases/unsafe_raw_sql_test.rb | 4 +- activerecord/test/models/author.rb | 20 ++++----- activerecord/test/models/category.rb | 2 +- activerecord/test/models/comment.rb | 2 +- activerecord/test/models/owner.rb | 2 +- activerecord/test/models/person.rb | 2 +- activerecord/test/models/post.rb | 6 +-- activerecord/test/models/project.rb | 4 +- 27 files changed, 120 insertions(+), 166 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index ff381b4e0b..64f81ca582 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -167,12 +167,24 @@ module ActiveRecord end end - def enforce_raw_sql_whitelist(args, whitelist: attribute_names_and_aliases) # :nodoc: + # Regexp whitelist. Matches the following: + # "#{table_name}.#{column_name}" + # "#{column_name}" + COLUMN_NAME_WHITELIST = /\A(?:\w+\.)?\w+\z/i + + # Regexp whitelist. Matches the following: + # "#{table_name}.#{column_name}" + # "#{table_name}.#{column_name} #{direction}" + # "#{column_name}" + # "#{column_name} #{direction}" + COLUMN_NAME_ORDER_WHITELIST = /\A(?:\w+\.)?\w+(?:\s+asc|\s+desc)?\z/i + + def enforce_raw_sql_whitelist(args, whitelist: COLUMN_NAME_WHITELIST) # :nodoc: unexpected = args.reject do |arg| - whitelist.include?(arg.to_s) || - arg.kind_of?(Arel::Node) || + arg.kind_of?(Arel::Node) || arg.is_a?(Arel::Nodes::SqlLiteral) || - arg.is_a?(Arel::Attributes::Attribute) + arg.is_a?(Arel::Attributes::Attribute) || + arg.to_s.split(/\s*,\s*/).all? { |part| whitelist.match?(part) } end return if unexpected.none? @@ -195,22 +207,6 @@ module ActiveRecord end end - # Can the given name be treated as a column name? Returns true if name - # is attribute or attribute alias. - # - # class Person < ActiveRecord::Base - # end - # - # Person.respond_to_attribute?(:name) - # # => true - # - # Person.respond_to_attribute?("foo") - # # => false - def respond_to_attribute?(name) # :nodoc: - name = name.to_s - attribute_names.include?(name) || attribute_aliases.include?(name) - end - # Returns true if the given attribute exists, otherwise false. # # class Person < ActiveRecord::Base @@ -242,18 +238,6 @@ module ActiveRecord ConnectionAdapters::NullColumn.new(name) end end - - # An Array of String attribute names and aliases for accessing those - # attributes. - # - # class Person < ActiveRecord::Base - # end - # - # Person.attribute_names_and_aliases - # # => ["id", "created_at", "updated_at", "name", "age"] - def attribute_names_and_aliases # :nodoc: - attribute_names + attribute_aliases.keys - end end # A Person object with a name attribute can ask person.respond_to?(:name), 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 diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index f08fd73da4..e69cfe5e52 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -121,7 +121,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase silly.parent_id = 1 assert silly.save - topics = Topic.all.merge!(includes: :replies, order: ["topics.id", Arel.sql("replies_topics.id")]).to_a + topics = Topic.all.merge!(includes: :replies, order: ["topics.id", "replies_topics.id"]).to_a assert_no_queries do assert_equal 2, topics[0].replies.size assert_equal 0, topics[1].replies.size @@ -129,14 +129,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_belongs_to_sti - replies = Reply.all.merge!(includes: :topic, order: Arel.sql("topics.id")).to_a + replies = Reply.all.merge!(includes: :topic, order: "topics.id").to_a assert_includes replies, topics(:second) assert_not_includes replies, topics(:first) assert_equal topics(:first), assert_no_queries { replies.first.topic } end def test_eager_association_loading_with_multiple_stis_and_order - author = Author.all.merge!(includes: { posts: [ :special_comments , :very_special_comment ] }, order: ["authors.name", Arel.sql("comments.body"), Arel.sql("very_special_comments_posts.body")], where: "posts.id = 4").first + author = Author.all.merge!(includes: { posts: [ :special_comments , :very_special_comment ] }, order: ["authors.name", "comments.body", "very_special_comments_posts.body"], where: "posts.id = 4").first assert_equal authors(:david), author assert_no_queries do author.posts.first.special_comments @@ -145,7 +145,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_of_stis_with_multiple_references - authors = Author.all.merge!(includes: { posts: { special_comments: { post: [ :special_comments, :very_special_comment ] } } }, order: Arel.sql("comments.body, very_special_comments_posts.body"), where: "posts.id = 4").to_a + authors = Author.all.merge!(includes: { posts: { special_comments: { post: [ :special_comments, :very_special_comment ] } } }, order: "comments.body, very_special_comments_posts.body", where: "posts.id = 4").to_a assert_equal [authors(:david)], authors assert_no_queries do authors.first.posts.first.special_comments.first.post.special_comments diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb index b1809401fb..c5b2b77bd4 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -120,7 +120,7 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase assert_nothing_raised do # @davey_mcdave doesn't have any author_favorites includes = { posts: :comments, categorizations: :category, author_favorites: :favorite_author } - Author.all.merge!(includes: includes, where: { authors: { name: @davey_mcdave.name } }, order: Arel.sql("categories.name")).to_a + Author.all.merge!(includes: includes, where: { authors: { name: @davey_mcdave.name } }, order: "categories.name").to_a end end end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 75f851fec7..9a042c74db 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -62,7 +62,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_loading_with_one_association_with_non_preload - posts = Post.all.merge!(includes: :last_comment, order: Arel.sql("comments.id DESC")).to_a + posts = Post.all.merge!(includes: :last_comment, order: "comments.id DESC").to_a post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end @@ -317,12 +317,12 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_nested_loading_through_has_one_association_with_order_on_association - aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: Arel.sql("authors.id")).find(author_addresses(:david_address).id) + aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: "authors.id").find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_order_on_nested_association - aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: Arel.sql("posts.id")).find(author_addresses(:david_address).id) + aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: "posts.id").find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end @@ -420,7 +420,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_order_string_with_unquoted_table_name assert_nothing_raised do - Comment.all.merge!(includes: :post, order: Arel.sql("posts.id")).to_a + Comment.all.merge!(includes: :post, order: "posts.id").to_a end end @@ -1089,12 +1089,12 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_order_on_join_table_with_include_and_limit - assert_equal 5, Developer.all.merge!(includes: "projects", order: Arel.sql("developers_projects.joined_on DESC"), limit: 5).to_a.size + assert_equal 5, Developer.all.merge!(includes: "projects", order: "developers_projects.joined_on DESC", limit: 5).to_a.size end def test_eager_loading_with_order_on_joined_table_preloads posts = assert_queries(2) do - Post.all.merge!(joins: :comments, includes: :author, order: Arel.sql("comments.id DESC")).to_a + Post.all.merge!(joins: :comments, includes: :author, order: "comments.id DESC").to_a end assert_equal posts(:eager_other), posts[1] assert_equal authors(:mary), assert_no_queries { posts[1].author } @@ -1190,7 +1190,7 @@ class EagerAssociationTest < ActiveRecord::TestCase if current_adapter?(:OracleAdapter) firm = Firm.all.merge!(includes: :clients_using_primary_key, order: "clients_using_primary_keys_companies"[0, 30] + ".name").find(1) else - firm = Firm.all.merge!(includes: :clients_using_primary_key, order: Arel.sql("clients_using_primary_keys_companies.name")).find(1) + firm = Firm.all.merge!(includes: :clients_using_primary_key, order: "clients_using_primary_keys_companies.name").find(1) end assert_no_queries do assert_equal expected, firm.clients_using_primary_key @@ -1207,7 +1207,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_include_has_one_using_primary_key expected = accounts(:signals37) - firm = Firm.all.merge!(includes: :account_using_primary_key, order: Arel.sql("accounts.id")).to_a.detect { |f| f.id == 1 } + firm = Firm.all.merge!(includes: :account_using_primary_key, order: "accounts.id").to_a.detect { |f| f.id == 1 } assert_no_queries do assert_equal expected, firm.account_using_primary_key end @@ -1278,7 +1278,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_join_eager_with_empty_order_should_generate_valid_sql assert_nothing_raised do - Post.includes(:comments).order(Arel.sql("")).where(comments: { body: "Thank you for the welcome" }).first + Post.includes(:comments).order("").where(comments: { body: "Thank you for the welcome" }).first end end @@ -1382,7 +1382,7 @@ class EagerAssociationTest < ActiveRecord::TestCase test "preloading associations with string joins and order references" do author = assert_queries(2) { - Author.includes(:posts).joins("LEFT JOIN posts ON posts.author_id = authors.id").order(Arel.sql("posts.title DESC")).first + Author.includes(:posts).joins("LEFT JOIN posts ON posts.author_id = authors.id").order("posts.title DESC").first } assert_no_queries { assert_equal 5, author.posts.size diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 7e5655bb08..c817d7267b 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -587,7 +587,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_find_should_append_to_association_order - ordered_developers = projects(:active_record).developers.order(Arel.sql("projects.id")) + ordered_developers = projects(:active_record).developers.order("projects.id") assert_equal ["developers.name desc, developers.id desc", "projects.id"], ordered_developers.order_values end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 3597da7ff3..6bd11a5d81 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -550,7 +550,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_should_append_to_association_order - ordered_clients = companies(:first_firm).clients_sorted_desc.order(Arel.sql("companies.id")) + ordered_clients = companies(:first_firm).clients_sorted_desc.order("companies.id") assert_equal ["id DESC", "companies.id"], ordered_clients.order_values end diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index bef20b2ebe..fe24c465b2 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -139,7 +139,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eagerloading members = assert_queries(1) do - Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: Arel.sql("clubs.name")).to_a #force fallback + Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].club } @@ -147,7 +147,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eager_loading_through_polymorphic members = assert_queries(1) do - Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: Arel.sql("clubs.name")).to_a #force fallback + Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].sponsor_club } @@ -156,7 +156,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record Sponsor.new(sponsor_club: clubs(:crazy_club), sponsorable: members(:groucho)).save! members = assert_queries(1) do - Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: Arel.sql("clubs.name DESC")).to_a #force fallback + Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name DESC").to_a #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].sponsor_club } diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index c39e967569..e13cf93dcf 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -223,7 +223,7 @@ class InverseHasOneTests < ActiveRecord::TestCase f.man.name = "Mungo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance" - m = Man.all.merge!(where: { name: "Gordon" }, includes: :face, order: Arel.sql("faces.id")).first + m = Man.all.merge!(where: { name: "Gordon" }, includes: :face, order: "faces.id").first f = m.face assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = "Bongo" @@ -329,7 +329,7 @@ class InverseHasManyTests < ActiveRecord::TestCase assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance" end - m = Man.all.merge!(where: { name: "Gordon" }, includes: :interests, order: Arel.sql("interests.id")).first + m = Man.all.merge!(where: { name: "Gordon" }, includes: :interests, order: "interests.id").first is = m.interests is.each do |i| assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" @@ -554,7 +554,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase m.face.description = "pleasing" assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance" - f = Face.all.merge!(includes: :man, order: Arel.sql("men.id"), where: { description: "trusting" }).first + f = Face.all.merge!(includes: :man, order: "men.id", where: { description: "trusting" }).first m = f.man assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" f.description = "gormless" @@ -637,7 +637,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase m.polymorphic_face.description = "pleasing" assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance" - f = Face.all.merge!(where: { description: "confused" }, includes: :man, order: Arel.sql("men.id")).first + f = Face.all.merge!(where: { description: "confused" }, includes: :man, order: "men.id").first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" f.description = "gormless" diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index a3acb1a152..87694b0788 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -420,7 +420,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_eager_load_has_many_through_has_many - author = Author.all.merge!(where: ["name = ?", "David"], includes: :comments, order: Arel.sql("comments.id")).first + author = Author.all.merge!(where: ["name = ?", "David"], includes: :comments, order: "comments.id").first SpecialComment.new; VerySpecialComment.new assert_no_queries do assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], author.comments.collect(&:id) diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index 0254da9a99..65d30d011b 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -171,7 +171,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_one_with_has_many_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Member.where("member_details.id" => member_details(:groucho).id).order(Arel.sql("member_details.id")), + Member.where("member_details.id" => member_details(:groucho).id).order("member_details.id"), [members(:groucho), members(:some_other_guy)], :organization_member_details ) @@ -203,7 +203,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_one_through_with_has_many_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Member.where("member_details.id" => member_details(:groucho).id).order(Arel.sql("member_details.id")), + Member.where("member_details.id" => member_details(:groucho).id).order("member_details.id"), [members(:groucho), members(:some_other_guy)], :organization_member_details_2 ) @@ -341,7 +341,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_belongs_to_with_has_many_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Categorization.where("taggings.id" => taggings(:welcome_general).id).order(Arel.sql("taggings.id")), + Categorization.where("taggings.id" => taggings(:welcome_general).id).order("taggings.id"), [categorizations(:david_welcome_general)], :post_taggings ) end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index a45b3a1644..f0ef522515 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1109,12 +1109,12 @@ class BasicsTest < ActiveRecord::TestCase end def test_find_multiple_ordered_last - last = Developer.all.merge!(order: Arel.sql("developers.name, developers.salary DESC")).last - assert_equal last, Developer.all.merge!(order: Arel.sql("developers.name, developers.salary DESC")).to_a.last + last = Developer.all.merge!(order: "developers.name, developers.salary DESC").last + assert_equal last, Developer.all.merge!(order: "developers.name, developers.salary DESC").to_a.last end def test_find_keeps_multiple_order_values - combined = Developer.all.merge!(order: Arel.sql("developers.name, developers.salary")).to_a + combined = Developer.all.merge!(order: "developers.name, developers.salary").to_a assert_equal combined, Developer.all.merge!(order: ["developers.name", "developers.salary"]).to_a end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 20dcb0080b..55b50e4f84 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -144,7 +144,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_order_by_calculation - c = Account.group(:firm_id).order(Arel.sql("sum_credit_limit desc, firm_id")).sum(:credit_limit) + c = Account.group(:firm_id).order("sum_credit_limit desc, firm_id").sum(:credit_limit) assert_equal [105, 60, 53, 50, 50], c.keys.collect { |k| c[k] } assert_equal [6, 2, 9, 1], c.keys.compact end @@ -644,7 +644,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_pluck_with_qualified_column_name - assert_equal [1, 2, 3, 4, 5], Topic.order(:id).pluck(Arel.sql("topics.id")) + assert_equal [1, 2, 3, 4, 5], Topic.order(:id).pluck("topics.id") end def test_pluck_auto_table_name_prefix @@ -659,7 +659,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_not_auto_table_name_prefix_if_column_joined Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) - assert_equal [7], Company.joins(:contracts).pluck(Arel.sql("developer_id")) + assert_equal [7], Company.joins(:contracts).pluck(:developer_id) end def test_pluck_with_selection_clause @@ -684,7 +684,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_not_auto_table_name_prefix_if_column_included Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) - ids = Company.includes(:contracts).pluck(Arel.sql("developer_id")) + ids = Company.includes(:contracts).pluck(:developer_id) assert_equal Company.count, ids.length assert_equal [7], ids.compact end @@ -704,12 +704,12 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_with_multiple_columns_and_selection_clause assert_equal [[1, 50], [2, 50], [3, 50], [4, 60], [5, 55], [6, 53]], - Account.pluck(Arel.sql("id, credit_limit")) + Account.pluck("id, credit_limit") end def test_pluck_with_multiple_columns_and_includes Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) - companies_and_developers = Company.order(Arel.sql("companies.id")).includes(:contracts).pluck(:name, Arel.sql("developer_id")) + companies_and_developers = Company.order("companies.id").includes(:contracts).pluck(:name, :developer_id) assert_equal Company.count, companies_and_developers.length assert_equal ["37signals", nil], companies_and_developers.first @@ -731,7 +731,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_columns_with_same_name expected = [["The First Topic", "The Second Topic of the day"], ["The Third Topic of the day", "The Fourth Topic of the day"]] actual = Topic.joins(:replies) - .pluck(Arel.sql("topics.title"), Arel.sql("replies_topics.title")) + .pluck("topics.title", "replies_topics.title") assert_equal expected, actual end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 344d1a6639..1268949ba9 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -243,15 +243,15 @@ class FinderTest < ActiveRecord::TestCase end def test_exists_with_joins - assert_equal true, Topic.joins(:replies).where(replies_topics: { approved: true }).order(Arel.sql("replies_topics.created_at DESC")).exists? + assert_equal true, Topic.joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists? end def test_exists_with_left_joins - assert_equal true, Topic.left_joins(:replies).where(replies_topics: { approved: true }).order(Arel.sql("replies_topics.created_at DESC")).exists? + assert_equal true, Topic.left_joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists? end def test_exists_with_eager_load - assert_equal true, Topic.eager_load(:replies).where(replies_topics: { approved: true }).order(Arel.sql("replies_topics.created_at DESC")).exists? + assert_equal true, Topic.eager_load(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists? end def test_exists_with_includes_limit_and_empty_result @@ -267,8 +267,8 @@ class FinderTest < ActiveRecord::TestCase def test_exists_with_distinct_association_includes_limit_and_order author = Author.first - assert_equal false, author.unique_categorized_posts.includes(:special_comments).order(Arel.sql("comments.tags_count DESC")).limit(0).exists? - assert_equal true, author.unique_categorized_posts.includes(:special_comments).order(Arel.sql("comments.tags_count DESC")).limit(1).exists? + assert_equal false, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(0).exists? + assert_equal true, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(1).exists? end def test_exists_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association @@ -1125,11 +1125,11 @@ class FinderTest < ActiveRecord::TestCase def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct assert_equal 2, Post.includes(authors: :author_address). where.not(author_addresses: { id: nil }). - order(Arel.sql("author_addresses.id DESC")).limit(2).to_a.size + order("author_addresses.id DESC").limit(2).to_a.size assert_equal 3, Post.includes(author: :author_address, authors: :author_address). where.not(author_addresses_authors: { id: nil }). - order(Arel.sql("author_addresses_authors.id DESC")).limit(3).to_a.size + order("author_addresses_authors.id DESC").limit(3).to_a.size end def test_find_with_nil_inside_set_passed_for_one_attribute diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index 8a656d7720..b68b3723f6 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -47,8 +47,8 @@ class RelationMergingTest < ActiveRecord::TestCase def test_relation_merging_with_eager_load relations = [] - relations << Post.order(Arel.sql("comments.id DESC")).merge(Post.eager_load(:last_comment)).merge(Post.all) - relations << Post.eager_load(:last_comment).merge(Post.order(Arel.sql("comments.id DESC"))).merge(Post.all) + relations << Post.order("comments.id DESC").merge(Post.eager_load(:last_comment)).merge(Post.all) + relations << Post.eager_load(:last_comment).merge(Post.order("comments.id DESC")).merge(Post.all) relations.each do |posts| post = posts.find { |p| p.id == 1 } @@ -126,19 +126,19 @@ class MergingDifferentRelationsTest < ActiveRecord::TestCase test "merging order relations" do posts_by_author_name = Post.limit(3).joins(:author). - merge(Author.order(:name)).pluck(Arel.sql("authors.name")) + merge(Author.order(:name)).pluck("authors.name") assert_equal ["Bob", "Bob", "David"], posts_by_author_name posts_by_author_name = Post.limit(3).joins(:author). - merge(Author.order(Arel.sql("name"))).pluck(Arel.sql("authors.name")) + merge(Author.order("name")).pluck("authors.name") assert_equal ["Bob", "Bob", "David"], posts_by_author_name end test "merging order relations (using a hash argument)" do posts_by_author_name = Post.limit(4).joins(:author). - merge(Author.order(name: :desc)).pluck(Arel.sql("authors.name")) + merge(Author.order(name: :desc)).pluck("authors.name") assert_equal ["Mary", "Mary", "Mary", "David"], posts_by_author_name end diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index d845c07a50..ad3700b73a 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -94,7 +94,7 @@ module ActiveRecord end test "reverse_order!" do - @relation = Post.order(Arel.sql("title ASC, comments_count DESC")) + @relation = Post.order("title ASC, comments_count DESC") relation.reverse_order! diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index ab2e432e78..6dfb78d913 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -220,7 +220,7 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_arel_assoc_order - topics = Topic.order("id" => :desc) + topics = Topic.order(Arel.sql("id") => :desc) assert_equal 5, topics.to_a.size assert_equal topics(:fifth).title, topics.first.title end @@ -232,13 +232,13 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_reversed_arel_assoc_order - topics = Topic.order("id" => :asc).reverse_order + topics = Topic.order(Arel.sql("id") => :asc).reverse_order assert_equal 5, topics.to_a.size assert_equal topics(:fifth).title, topics.first.title end def test_reverse_order_with_function - topics = Topic.order(Arel.sql("length(title)")).reverse_order + topics = Topic.order("length(title)").reverse_order assert_equal topics(:second).title, topics.first.title end @@ -248,9 +248,9 @@ class RelationTest < ActiveRecord::TestCase end def test_reverse_order_with_function_other_predicates - topics = Topic.order(Arel.sql("author_name, length(title), id")).reverse_order + topics = Topic.order("author_name, length(title), id").reverse_order assert_equal topics(:second).title, topics.first.title - topics = Topic.order(Arel.sql("length(author_name), id, length(title)")).reverse_order + topics = Topic.order("length(author_name), id, length(title)").reverse_order assert_equal topics(:fifth).title, topics.first.title end @@ -319,7 +319,7 @@ class RelationTest < ActiveRecord::TestCase end def test_raising_exception_on_invalid_hash_params - e = assert_raise(ArgumentError) { Topic.order(Arel.sql("name"), "id DESC", id: :asfsdf) } + e = assert_raise(ArgumentError) { Topic.order(:name, "id DESC", id: :asfsdf) } assert_equal 'Direction "asfsdf" is invalid. Valid directions are: [:asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]', e.message end @@ -373,11 +373,8 @@ class RelationTest < ActiveRecord::TestCase def test_finding_with_cross_table_order_and_limit tags = Tag.includes(:taggings). - order( - "tags.name asc", - Arel.sql("taggings.taggable_id asc"), - Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)") - ).limit(1).to_a + order("tags.name asc", "taggings.taggable_id asc", Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)")). + limit(1).to_a assert_equal 1, tags.length end @@ -507,7 +504,7 @@ class RelationTest < ActiveRecord::TestCase def test_eager_association_loading_of_stis_with_multiple_references authors = Author.eager_load(posts: { special_comments: { post: [ :special_comments, :very_special_comment ] } }). - order(Arel.sql("comments.body, very_special_comments_posts.body")).where("posts.id = 4").to_a + order("comments.body, very_special_comments_posts.body").where("posts.id = 4").to_a assert_equal [authors(:david)], authors assert_no_queries do @@ -580,7 +577,7 @@ class RelationTest < ActiveRecord::TestCase end def test_includes_with_select - query = Post.select("comments_count AS ranking").order(Arel.sql("ranking")).includes(:comments) + query = Post.select("comments_count AS ranking").order("ranking").includes(:comments) .where(comments: { id: 1 }) assert_equal ["comments_count AS ranking"], query.select_values @@ -649,9 +646,9 @@ class RelationTest < ActiveRecord::TestCase def test_to_sql_on_eager_join expected = assert_sql { - Post.eager_load(:last_comment).order(Arel.sql("comments.id DESC")).to_a + Post.eager_load(:last_comment).order("comments.id DESC").to_a }.first - actual = Post.eager_load(:last_comment).order(Arel.sql("comments.id DESC")).to_sql + actual = Post.eager_load(:last_comment).order("comments.id DESC").to_sql assert_equal expected, actual end @@ -662,7 +659,7 @@ class RelationTest < ActiveRecord::TestCase end def test_loading_with_one_association_with_non_preload - posts = Post.eager_load(:last_comment).order(Arel.sql("comments.id DESC")) + posts = Post.eager_load(:last_comment).order("comments.id DESC") post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end @@ -1421,7 +1418,7 @@ class RelationTest < ActiveRecord::TestCase end def test_ordering_with_extra_spaces - assert_equal authors(:david), Author.order(Arel.sql("id DESC , name DESC")).last + assert_equal authors(:david), Author.order("id DESC , name DESC").last end def test_update_all_with_blank_argument @@ -1457,7 +1454,7 @@ class RelationTest < ActiveRecord::TestCase end def test_update_all_with_joins_and_offset_and_order - all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order(Arel.sql("posts.id"), "comments.id") + all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("posts.id", "comments.id") count = all_comments.count comments = all_comments.offset(1) @@ -1567,7 +1564,7 @@ class RelationTest < ActiveRecord::TestCase end def test_automatically_added_order_references - scope = Post.order(Arel.sql("comments.body")) + scope = Post.order("comments.body") assert_equal ["comments"], scope.references_values scope = Post.order(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")) @@ -1577,14 +1574,14 @@ class RelationTest < ActiveRecord::TestCase assert_equal ["comments"], scope.references_values end - scope = Post.order(Arel.sql("comments.body"), Arel.sql("yaks.body")) + scope = Post.order("comments.body", "yaks.body") assert_equal ["comments", "yaks"], scope.references_values # Don't infer yaks, let's not go down that road again... - scope = Post.order(Arel.sql("comments.body, yaks.body")) + scope = Post.order("comments.body, yaks.body") assert_equal ["comments"], scope.references_values - scope = Post.order(Arel.sql("comments.body asc")) + scope = Post.order("comments.body asc") assert_equal ["comments"], scope.references_values scope = Post.order(Arel.sql("foo(comments.body)")) @@ -1592,7 +1589,7 @@ class RelationTest < ActiveRecord::TestCase end def test_automatically_added_reorder_references - scope = Post.reorder(Arel.sql("comments.body")) + scope = Post.reorder("comments.body") assert_equal %w(comments), scope.references_values scope = Post.reorder(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")) @@ -1602,14 +1599,14 @@ class RelationTest < ActiveRecord::TestCase assert_equal ["comments"], scope.references_values end - scope = Post.reorder(Arel.sql("comments.body"), Arel.sql("yaks.body")) + scope = Post.reorder("comments.body", "yaks.body") assert_equal %w(comments yaks), scope.references_values # Don't infer yaks, let's not go down that road again... - scope = Post.reorder(Arel.sql("comments.body, yaks.body")) + scope = Post.reorder("comments.body, yaks.body") assert_equal %w(comments), scope.references_values - scope = Post.reorder(Arel.sql("comments.body asc")) + scope = Post.reorder("comments.body asc") assert_equal %w(comments), scope.references_values scope = Post.reorder(Arel.sql("foo(comments.body)")) diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 0d64db140c..716ca29eda 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -80,7 +80,7 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_scope_overwrites_default - expected = Developer.all.merge!(order: Arel.sql("salary DESC, name DESC")).to_a.collect(&:name) + expected = Developer.all.merge!(order: "salary DESC, name DESC").to_a.collect(&:name) received = DeveloperOrderedBySalary.by_name.to_a.collect(&:name) assert_equal expected, received end @@ -92,7 +92,7 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_order_after_reorder_combines_orders - expected = Developer.order(Arel.sql("name DESC, id DESC")).collect { |dev| [dev.name, dev.id] } + expected = Developer.order("name DESC, id DESC").collect { |dev| [dev.name, dev.id] } received = Developer.order("name ASC").reorder("name DESC").order("id DESC").collect { |dev| [dev.name, dev.id] } assert_equal expected, received end @@ -104,12 +104,12 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_unscope_after_reordering_and_combining - expected = Developer.order(Arel.sql("id DESC, name DESC")).collect { |dev| [dev.name, dev.id] } - received = DeveloperOrderedBySalary.reorder("name DESC").unscope(:order).order(Arel.sql("id DESC, name DESC")).collect { |dev| [dev.name, dev.id] } + expected = Developer.order("id DESC, name DESC").collect { |dev| [dev.name, dev.id] } + received = DeveloperOrderedBySalary.reorder("name DESC").unscope(:order).order("id DESC, name DESC").collect { |dev| [dev.name, dev.id] } assert_equal expected, received expected_2 = Developer.all.collect { |dev| [dev.name, dev.id] } - received_2 = Developer.order(Arel.sql("id DESC, name DESC")).unscope(:order).collect { |dev| [dev.name, dev.id] } + received_2 = Developer.order("id DESC, name DESC").unscope(:order).collect { |dev| [dev.name, dev.id] } assert_equal expected_2, received_2 expected_3 = Developer.all.collect { |dev| [dev.name, dev.id] } @@ -192,7 +192,7 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_order_to_unscope_reordering - scope = DeveloperOrderedBySalary.order(Arel.sql("salary DESC, name ASC")).reverse_order.unscope(:order) + scope = DeveloperOrderedBySalary.order("salary DESC, name ASC").reverse_order.unscope(:order) assert !/order/i.match?(scope.to_sql) end diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb index b32bb24ce8..18c6f4bae3 100644 --- a/activerecord/test/cases/unsafe_raw_sql_test.rb +++ b/activerecord/test/cases/unsafe_raw_sql_test.rb @@ -110,7 +110,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase test "order: disallows invalid column name" do with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do - Post.order("foo asc").pluck(:id) + Post.order("len(title) asc").pluck(:id) end end end @@ -126,7 +126,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase test "order: disallows invalid column with direction" do with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do - Post.order(foo: :asc).pluck(:id) + Post.order("len(title)" => :asc).pluck(:id) end end end diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index f462fdb69a..cb8686f315 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -7,7 +7,7 @@ class Author < ActiveRecord::Base has_many :very_special_comments, through: :posts has_many :posts_with_comments, -> { includes(:comments) }, class_name: "Post" has_many :popular_grouped_posts, -> { includes(:comments).group("type").having("SUM(comments_count) > 1").select("type") }, class_name: "Post" - has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order(Arel.sql("comments.id")) }, class_name: "Post" + has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order("comments.id") }, class_name: "Post" has_many :posts_sorted_by_id_limited, -> { order("posts.id").limit(1) }, class_name: "Post" has_many :posts_with_categories, -> { includes(:categories) }, class_name: "Post" has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, class_name: "Post" @@ -20,15 +20,15 @@ class Author < ActiveRecord::Base end end has_many :comments_containing_the_letter_e, through: :posts, source: :comments - has_many :comments_with_order_and_conditions, -> { order(Arel.sql("comments.body")).where("comments.body like 'Thank%'") }, through: :posts, source: :comments + has_many :comments_with_order_and_conditions, -> { order("comments.body").where("comments.body like 'Thank%'") }, through: :posts, source: :comments has_many :comments_with_include, -> { includes(:post).where(posts: { type: "Post" }) }, through: :posts, source: :comments has_many :comments_for_first_author, -> { for_first_author }, through: :posts, source: :comments has_many :first_posts - has_many :comments_on_first_posts, -> { order(Arel.sql("posts.id desc, comments.id asc")) }, through: :first_posts, source: :comments + has_many :comments_on_first_posts, -> { order("posts.id desc, comments.id asc") }, through: :first_posts, source: :comments has_one :first_post - has_one :comment_on_first_post, -> { order(Arel.sql("posts.id desc, comments.id asc")) }, through: :first_post, source: :comments + has_one :comment_on_first_post, -> { order("posts.id desc, comments.id asc") }, through: :first_post, source: :comments has_many :thinking_posts, -> { where(title: "So I was thinking") }, dependent: :delete_all, class_name: "Post" has_many :welcome_posts, -> { where(title: "Welcome to the weblog") }, class_name: "Post" @@ -40,11 +40,11 @@ class Author < ActiveRecord::Base -> { where(title: "Welcome to the weblog").where(Post.arel_table[:comments_count].gt(0)) }, class_name: "Post" - has_many :comments_desc, -> { order(Arel.sql("comments.id DESC")) }, through: :posts, source: :comments + has_many :comments_desc, -> { order("comments.id DESC") }, through: :posts, source: :comments has_many :unordered_comments, -> { unscope(:order).distinct }, through: :posts_sorted_by_id_limited, source: :comments has_many :funky_comments, through: :posts, source: :comments - has_many :ordered_uniq_comments, -> { distinct.order(Arel.sql("comments.id")) }, through: :posts, source: :comments - has_many :ordered_uniq_comments_desc, -> { distinct.order(Arel.sql("comments.id DESC")) }, through: :posts, source: :comments + has_many :ordered_uniq_comments, -> { distinct.order("comments.id") }, through: :posts, source: :comments + has_many :ordered_uniq_comments_desc, -> { distinct.order("comments.id DESC") }, through: :posts, source: :comments has_many :readonly_comments, -> { readonly }, through: :posts, source: :comments has_many :special_posts @@ -107,15 +107,15 @@ class Author < ActiveRecord::Base has_many :similar_posts, -> { distinct }, through: :tags, source: :tagged_posts has_many :ordered_posts, -> { distinct }, through: :ordered_tags, source: :tagged_posts - has_many :distinct_tags, -> { select("DISTINCT tags.*").order(Arel.sql("tags.name")) }, through: :posts, source: :tags + has_many :distinct_tags, -> { select("DISTINCT tags.*").order("tags.name") }, through: :posts, source: :tags has_many :tags_with_primary_key, through: :posts has_many :books has_many :unpublished_books, -> { where(status: [:proposed, :written]) }, class_name: "Book" has_many :subscriptions, through: :books - has_many :subscribers, -> { order(Arel.sql("subscribers.nick")) }, through: :subscriptions - has_many :distinct_subscribers, -> { select("DISTINCT subscribers.*").order(Arel.sql("subscribers.nick")) }, through: :subscriptions, source: :subscriber + has_many :subscribers, -> { order("subscribers.nick") }, through: :subscriptions + has_many :distinct_subscribers, -> { select("DISTINCT subscribers.*").order("subscribers.nick") }, through: :subscriptions, source: :subscriber has_one :essay, primary_key: :name, as: :writer has_one :essay_category, through: :essay, source: :category diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb index 3038264694..2ccc00bed9 100644 --- a/activerecord/test/models/category.rb +++ b/activerecord/test/models/category.rb @@ -4,7 +4,7 @@ class Category < ActiveRecord::Base has_and_belongs_to_many :posts has_and_belongs_to_many :special_posts, class_name: "Post" has_and_belongs_to_many :other_posts, class_name: "Post" - has_and_belongs_to_many :posts_with_authors_sorted_by_author_id, -> { includes(:authors).order(Arel.sql("authors.id")) }, class_name: "Post" + has_and_belongs_to_many :posts_with_authors_sorted_by_author_id, -> { includes(:authors).order("authors.id") }, class_name: "Post" has_and_belongs_to_many :select_testing_posts, -> { select "posts.*, 1 as correctness_marker" }, diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index d5acfc0749..5ab433f2d9 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -81,7 +81,7 @@ class CommentThatAutomaticallyAltersPostBody < Comment end class CommentWithDefaultScopeReferencesAssociation < Comment - default_scope -> { includes(:developer).order(Arel.sql("developers.name")).references(:developer) } + default_scope -> { includes(:developer).order("developers.name").references(:developer) } belongs_to :developer end diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb index ebaafdec5e..5fa50d9918 100644 --- a/activerecord/test/models/owner.rb +++ b/activerecord/test/models/owner.rb @@ -2,7 +2,7 @@ class Owner < ActiveRecord::Base self.primary_key = :owner_id - has_many :pets, -> { order Arel.sql("pets.name desc") } + has_many :pets, -> { order "pets.name desc" } has_many :toys, through: :pets has_many :persons, through: :pets diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 7067f1b6b0..5cba1e440e 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -19,7 +19,7 @@ class Person < ActiveRecord::Base has_many :bad_references has_many :fixed_bad_references, -> { where favourite: true }, class_name: "BadReference" has_one :favourite_reference, -> { where "favourite=?", true }, class_name: "Reference" - has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order(Arel.sql("comments.id")) }, through: :readers, source: :post + has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order("comments.id") }, through: :readers, source: :post has_many :first_posts, -> { where(id: [1, 2]) }, through: :readers has_many :jobs, through: :references diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 4508f727d0..780a2c17f5 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -85,7 +85,7 @@ class Post < ActiveRecord::Base has_one :very_special_comment has_one :very_special_comment_with_post, -> { includes(:post) }, class_name: "VerySpecialComment" - has_one :very_special_comment_with_post_with_joins, -> { joins(:post).order(Arel.sql("posts.id")) }, class_name: "VerySpecialComment" + has_one :very_special_comment_with_post_with_joins, -> { joins(:post).order("posts.id") }, class_name: "VerySpecialComment" has_many :special_comments has_many :nonexistent_comments, -> { where "comments.id < 0" }, class_name: "Comment" @@ -323,9 +323,5 @@ class FakeKlass def enforce_raw_sql_whitelist(*args) # noop end - - def attribute_names_and_aliases - [] - end end end diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index 9b282a6729..846cef625b 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -2,9 +2,9 @@ class Project < ActiveRecord::Base belongs_to :mentor - has_and_belongs_to_many :developers, -> { distinct.order Arel.sql("developers.name desc, developers.id desc") } + has_and_belongs_to_many :developers, -> { distinct.order "developers.name desc, developers.id desc" } has_and_belongs_to_many :readonly_developers, -> { readonly }, class_name: "Developer" - has_and_belongs_to_many :non_unique_developers, -> { order Arel.sql("developers.name desc, developers.id desc") }, class_name: "Developer" + has_and_belongs_to_many :non_unique_developers, -> { order "developers.name desc, developers.id desc" }, class_name: "Developer" has_and_belongs_to_many :limited_developers, -> { limit 1 }, class_name: "Developer" has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, class_name: "Developer" has_and_belongs_to_many :developers_named_david_with_hash_conditions, -> { where(name: "David").distinct }, class_name: "Developer" -- 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 +- activerecord/test/cases/relations_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') 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 diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 6dfb78d913..f3f82e7591 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -238,7 +238,7 @@ class RelationTest < ActiveRecord::TestCase end def test_reverse_order_with_function - topics = Topic.order("length(title)").reverse_order + topics = Topic.order(Arel.sql("length(title)")).reverse_order assert_equal topics(:second).title, topics.first.title end -- 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 ++++ activerecord/test/cases/relations_test.rb | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'activerecord') 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. diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index f3f82e7591..4c865ef965 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -248,9 +248,9 @@ class RelationTest < ActiveRecord::TestCase end def test_reverse_order_with_function_other_predicates - topics = Topic.order("author_name, length(title), id").reverse_order + topics = Topic.order(Arel.sql("author_name, length(title), id")).reverse_order assert_equal topics(:second).title, topics.first.title - topics = Topic.order("length(author_name), id, length(title)").reverse_order + topics = Topic.order(Arel.sql("length(author_name), id, length(title)")).reverse_order assert_equal topics(:fifth).title, topics.first.title end -- 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 +++++++++++++- activerecord/lib/active_record/sanitization.rb | 6 ++++++ activerecord/test/cases/relations_test.rb | 6 +++--- 3 files changed, 22 insertions(+), 4 deletions(-) (limited to 'activerecord') 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! diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 1c3099f55c..4caf1145f0 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -63,6 +63,12 @@ module ActiveRecord # # => "id ASC" def sanitize_sql_for_order(condition) # :doc: if condition.is_a?(Array) && condition.first.to_s.include?("?") + # Ensure we aren't dealing with a subclass of String that might + # override methods we use (eg. Arel::Nodes::SqlLiteral). + if condition.first.kind_of?(String) && !condition.first.instance_of?(String) + condition = [String.new(condition.first), *condition[1..-1]] + end + sanitize_sql_array(condition) else condition diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 4c865ef965..a755a3ceeb 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -389,13 +389,13 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_sanitized_order - query = Tag.order(["field(id, ?)", [1, 3, 2]]).to_sql + query = Tag.order([Arel.sql("field(id, ?)"), [1, 3, 2]]).to_sql assert_match(/field\(id, 1,3,2\)/, query) - query = Tag.order(["field(id, ?)", []]).to_sql + query = Tag.order([Arel.sql("field(id, ?)"), []]).to_sql assert_match(/field\(id, NULL\)/, query) - query = Tag.order(["field(id, ?)", nil]).to_sql + query = Tag.order([Arel.sql("field(id, ?)"), nil]).to_sql assert_match(/field\(id, NULL\)/, query) end -- 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 ++++---------------- activerecord/lib/active_record/sanitization.rb | 6 +++- activerecord/test/cases/unsafe_raw_sql_test.rb | 36 ++++++++++++++++++++++ 3 files changed, 47 insertions(+), 28 deletions(-) (limited to 'activerecord') 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) diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 4caf1145f0..232743d3b6 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -63,13 +63,17 @@ module ActiveRecord # # => "id ASC" def sanitize_sql_for_order(condition) # :doc: if condition.is_a?(Array) && condition.first.to_s.include?("?") + enforce_raw_sql_whitelist([condition.first], + whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST + ) + # Ensure we aren't dealing with a subclass of String that might # override methods we use (eg. Arel::Nodes::SqlLiteral). if condition.first.kind_of?(String) && !condition.first.instance_of?(String) condition = [String.new(condition.first), *condition[1..-1]] end - sanitize_sql_array(condition) + Arel.sql(sanitize_sql_array(condition)) else condition end diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb index 18c6f4bae3..861df8f1da 100644 --- a/activerecord/test/cases/unsafe_raw_sql_test.rb +++ b/activerecord/test/cases/unsafe_raw_sql_test.rb @@ -138,6 +138,42 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase assert_equal ids_depr, ids_disabled end + test "order: allows Arel.sql with binds" do + ids_expected = Post.order(Arel.sql('INSTR(title, "comments"), id')).pluck(:id) + + ids_depr = with_unsafe_raw_sql_deprecated { Post.order([Arel.sql("INSTR(title, ?), id"), "comments"]).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order([Arel.sql("INSTR(title, ?), id"), "comments"]).pluck(:id) } + + assert_equal ids_expected, ids_depr + assert_equal ids_expected, ids_disabled + end + + test "order: disallows invalid bind statement" do + with_unsafe_raw_sql_disabled do + assert_raises(ActiveRecord::UnknownAttributeReference) do + Post.order(["INSTR(title, ?), id", "comments"]).pluck(:id) + end + end + end + + test "order: disallows invalid Array arguments" do + with_unsafe_raw_sql_disabled do + assert_raises(ActiveRecord::UnknownAttributeReference) do + Post.order(["author_id", "length(title)"]).pluck(:id) + end + end + end + + test "order: allows valid Array arguments" do + ids_expected = Post.order(Arel.sql("author_id, length(title)")).pluck(:id) + + ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", Arel.sql("length(title)")]).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", Arel.sql("length(title)")]).pluck(:id) } + + assert_equal ids_expected, ids_depr + assert_equal ids_expected, ids_disabled + end + test "order: logs deprecation warning for unrecognized column" do with_unsafe_raw_sql_deprecated do ActiveSupport::Deprecation.expects(:warn).with do |msg| -- cgit v1.2.3 From 4a5b3ca972e867d9b9276dcd98b0a6b9b6fb7583 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Thu, 26 Oct 2017 12:00:22 -0600 Subject: use database agnostic function/quoting in test --- activerecord/test/cases/unsafe_raw_sql_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb index 861df8f1da..5809d05327 100644 --- a/activerecord/test/cases/unsafe_raw_sql_test.rb +++ b/activerecord/test/cases/unsafe_raw_sql_test.rb @@ -139,10 +139,10 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase end test "order: allows Arel.sql with binds" do - ids_expected = Post.order(Arel.sql('INSTR(title, "comments"), id')).pluck(:id) + ids_expected = Post.order(Arel.sql("REPLACE(title, 'misc', 'zzzz'), id")).pluck(:id) - ids_depr = with_unsafe_raw_sql_deprecated { Post.order([Arel.sql("INSTR(title, ?), id"), "comments"]).pluck(:id) } - ids_disabled = with_unsafe_raw_sql_disabled { Post.order([Arel.sql("INSTR(title, ?), id"), "comments"]).pluck(:id) } + ids_depr = with_unsafe_raw_sql_deprecated { Post.order([Arel.sql("REPLACE(title, ?, ?), id"), "misc", "zzzz"]).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order([Arel.sql("REPLACE(title, ?, ?), id"), "misc", "zzzz"]).pluck(:id) } assert_equal ids_expected, ids_depr assert_equal ids_expected, ids_disabled @@ -151,7 +151,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase test "order: disallows invalid bind statement" do with_unsafe_raw_sql_disabled do assert_raises(ActiveRecord::UnknownAttributeReference) do - Post.order(["INSTR(title, ?), id", "comments"]).pluck(:id) + Post.order(["REPLACE(title, ?, ?), id", "misc", "zzzz"]).pluck(:id) end end end -- cgit v1.2.3 From 1f9f6f6cfc57020ccb35f77872c56f069f337075 Mon Sep 17 00:00:00 2001 From: Brent Wheeldon Date: Thu, 2 Nov 2017 14:53:43 -0400 Subject: Prevent deadlocks with load interlock and DB lock. This fixes an issue where competing threads deadlock each other. - Thread A holds the load interlock but is blocked on getting the DB lock - Thread B holds the DB lock but is blocked on getting the load interlock (for example when there is a `Model.transaction` block that needs to autoload) This solution allows for dependency loading in other threads while a thread is waiting to acquire the DB lock. Fixes #31019 --- activerecord/lib/active_record/connection_adapters/abstract_adapter.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index e3aab8dad8..e91ef3b779 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -6,6 +6,7 @@ require "active_record/connection_adapters/schema_cache" require "active_record/connection_adapters/sql_type_metadata" require "active_record/connection_adapters/abstract/schema_dumper" require "active_record/connection_adapters/abstract/schema_creation" +require "active_support/concurrency/load_interlock_aware_monitor" require "arel/collectors/bind" require "arel/collectors/composite" require "arel/collectors/sql_string" @@ -108,7 +109,7 @@ module ActiveRecord @schema_cache = SchemaCache.new self @quoted_column_names, @quoted_table_names = {}, {} @visitor = arel_visitor - @lock = Monitor.new + @lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @prepared_statements = true -- 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.rb | 8 +- activerecord/lib/active_record/attribute.rb | 242 ------------------- .../attribute/user_provided_default.rb | 32 --- .../lib/active_record/attribute_methods/dirty.rb | 96 +------- .../active_record/attribute_mutation_tracker.rb | 111 --------- activerecord/lib/active_record/attribute_set.rb | 113 --------- .../lib/active_record/attribute_set/builder.rb | 126 ---------- .../active_record/attribute_set/yaml_encoder.rb | 43 ---- activerecord/lib/active_record/attributes.rb | 6 +- .../lib/active_record/legacy_yaml_adapter.rb | 2 +- activerecord/lib/active_record/model_schema.rb | 6 +- .../lib/active_record/relation/query_attribute.rb | 4 +- .../lib/active_record/relation/query_methods.rb | 4 +- activerecord/test/cases/attribute_set_test.rb | 255 --------------------- activerecord/test/cases/attribute_test.rb | 255 --------------------- 15 files changed, 19 insertions(+), 1284 deletions(-) delete mode 100644 activerecord/lib/active_record/attribute.rb delete mode 100644 activerecord/lib/active_record/attribute/user_provided_default.rb delete mode 100644 activerecord/lib/active_record/attribute_mutation_tracker.rb delete mode 100644 activerecord/lib/active_record/attribute_set.rb delete mode 100644 activerecord/lib/active_record/attribute_set/builder.rb delete mode 100644 activerecord/lib/active_record/attribute_set/yaml_encoder.rb delete mode 100644 activerecord/test/cases/attribute_set_test.rb delete mode 100644 activerecord/test/cases/attribute_test.rb (limited to 'activerecord') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index bf6dfd46e1..0e036f05f5 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -27,14 +27,14 @@ require "active_support" require "active_support/rails" require "active_model" require "arel" +require "yaml" require "active_record/version" -require "active_record/attribute_set" +require "active_model/attribute_set" module ActiveRecord extend ActiveSupport::Autoload - autoload :Attribute autoload :Base autoload :Callbacks autoload :Core @@ -181,3 +181,7 @@ end ActiveSupport.on_load(:i18n) do I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__) end + +YAML.load_tags["!ruby/object:ActiveRecord::AttributeSet"] = "ActiveModel::AttributeSet" +YAML.load_tags["!ruby/object:ActiveRecord::Attribute::FromDatabase"] = "ActiveModel::Attribute::FromDatabase" +YAML.load_tags["!ruby/object:ActiveRecord::LazyAttributeHash"] = "ActiveModel::LazyAttributeHash" diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb deleted file mode 100644 index fc474edc15..0000000000 --- a/activerecord/lib/active_record/attribute.rb +++ /dev/null @@ -1,242 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - class Attribute # :nodoc: - class << self - def from_database(name, value, type) - FromDatabase.new(name, value, type) - end - - def from_user(name, value, type, original_attribute = nil) - FromUser.new(name, value, type, original_attribute) - end - - def with_cast_value(name, value, type) - WithCastValue.new(name, value, type) - end - - def null(name) - Null.new(name) - end - - def uninitialized(name, type) - Uninitialized.new(name, type) - end - end - - attr_reader :name, :value_before_type_cast, :type - - # This method should not be called directly. - # Use #from_database or #from_user - def initialize(name, value_before_type_cast, type, original_attribute = nil) - @name = name - @value_before_type_cast = value_before_type_cast - @type = type - @original_attribute = original_attribute - end - - def value - # `defined?` is cheaper than `||=` when we get back falsy values - @value = type_cast(value_before_type_cast) unless defined?(@value) - @value - end - - def original_value - if assigned? - original_attribute.original_value - else - type_cast(value_before_type_cast) - end - end - - def value_for_database - type.serialize(value) - end - - def changed? - changed_from_assignment? || changed_in_place? - end - - def changed_in_place? - has_been_read? && type.changed_in_place?(original_value_for_database, value) - end - - def forgetting_assignment - with_value_from_database(value_for_database) - end - - def with_value_from_user(value) - type.assert_valid_value(value) - self.class.from_user(name, value, type, original_attribute || self) - end - - def with_value_from_database(value) - self.class.from_database(name, value, type) - end - - def with_cast_value(value) - self.class.with_cast_value(name, value, type) - end - - def with_type(type) - if changed_in_place? - with_value_from_user(value).with_type(type) - else - self.class.new(name, value_before_type_cast, type, original_attribute) - end - end - - def type_cast(*) - raise NotImplementedError - end - - def initialized? - true - end - - def came_from_user? - false - end - - def has_been_read? - defined?(@value) - end - - def ==(other) - self.class == other.class && - name == other.name && - value_before_type_cast == other.value_before_type_cast && - type == other.type - end - alias eql? == - - def hash - [self.class, name, value_before_type_cast, type].hash - end - - def init_with(coder) - @name = coder["name"] - @value_before_type_cast = coder["value_before_type_cast"] - @type = coder["type"] - @original_attribute = coder["original_attribute"] - @value = coder["value"] if coder.map.key?("value") - end - - def encode_with(coder) - coder["name"] = name - coder["value_before_type_cast"] = value_before_type_cast unless value_before_type_cast.nil? - coder["type"] = type if type - coder["original_attribute"] = original_attribute if original_attribute - coder["value"] = value if defined?(@value) - end - - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :original_attribute - alias_method :assigned?, :original_attribute - - def original_value_for_database - if assigned? - original_attribute.original_value_for_database - else - _original_value_for_database - end - end - - private - def initialize_dup(other) - if defined?(@value) && @value.duplicable? - @value = @value.dup - end - end - - def changed_from_assignment? - assigned? && type.changed?(original_value, value, value_before_type_cast) - end - - def _original_value_for_database - type.serialize(original_value) - end - - class FromDatabase < Attribute # :nodoc: - def type_cast(value) - type.deserialize(value) - end - - def _original_value_for_database - value_before_type_cast - end - end - - class FromUser < Attribute # :nodoc: - def type_cast(value) - type.cast(value) - end - - def came_from_user? - !type.value_constructed_by_mass_assignment?(value_before_type_cast) - end - end - - class WithCastValue < Attribute # :nodoc: - def type_cast(value) - value - end - - def changed_in_place? - false - end - end - - class Null < Attribute # :nodoc: - def initialize(name) - super(name, nil, Type.default_value) - end - - def type_cast(*) - nil - end - - def with_type(type) - self.class.with_cast_value(name, nil, type) - end - - def with_value_from_database(value) - raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" - end - alias_method :with_value_from_user, :with_value_from_database - end - - class Uninitialized < Attribute # :nodoc: - UNINITIALIZED_ORIGINAL_VALUE = Object.new - - def initialize(name, type) - super(name, nil, type) - end - - def value - if block_given? - yield name - end - end - - def original_value - UNINITIALIZED_ORIGINAL_VALUE - end - - def value_for_database - end - - def initialized? - false - end - - def with_type(type) - self.class.new(name, type) - end - end - private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue - end -end diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb deleted file mode 100644 index f746960fac..0000000000 --- a/activerecord/lib/active_record/attribute/user_provided_default.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require "active_record/attribute" - -module ActiveRecord - class Attribute # :nodoc: - class UserProvidedDefault < FromUser # :nodoc: - def initialize(name, value, type, database_default) - @user_provided_value = value - super(name, value, type, database_default) - end - - def value_before_type_cast - if user_provided_value.is_a?(Proc) - @memoized_value_before_type_cast ||= user_provided_value.call - else - @user_provided_value - end - end - - def with_type(type) - self.class.new(name, user_provided_value, type, original_attribute) - end - - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :user_provided_value - end - end -end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 79110d04f4..3de6fe566d 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support/core_ext/module/attribute_accessors" -require "active_record/attribute_mutation_tracker" module ActiveRecord module AttributeMethods @@ -33,65 +32,13 @@ module ActiveRecord # reload the record and clears changed attributes. def reload(*) super.tap do + @previously_changed = ActiveSupport::HashWithIndifferentAccess.new @mutations_before_last_save = nil + @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new @mutations_from_database = nil - @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end end - def initialize_dup(other) # :nodoc: - super - @attributes = self.class._default_attributes.map do |attr| - attr.with_value_from_user(@attributes.fetch_value(attr.name)) - end - @mutations_from_database = nil - end - - def changes_applied # :nodoc: - @mutations_before_last_save = mutations_from_database - @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new - forget_attribute_assignments - @mutations_from_database = nil - end - - def clear_changes_information # :nodoc: - @mutations_before_last_save = nil - @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new - forget_attribute_assignments - @mutations_from_database = nil - end - - def clear_attribute_changes(attr_names) # :nodoc: - super - attr_names.each do |attr_name| - clear_attribute_change(attr_name) - end - end - - def changed_attributes # :nodoc: - # This should only be set by methods which will call changed_attributes - # multiple times when it is known that the computed value cannot change. - if defined?(@cached_changed_attributes) - @cached_changed_attributes - else - super.reverse_merge(mutations_from_database.changed_values).freeze - end - end - - def changes # :nodoc: - cache_changed_attributes do - super - end - end - - def previous_changes # :nodoc: - mutations_before_last_save.changes - end - - def attribute_changed_in_place?(attr_name) # :nodoc: - mutations_from_database.changed_in_place?(attr_name) - end - # Did this attribute change when we last saved? This method can be invoked # as +saved_change_to_name?+ instead of saved_change_to_attribute?("name"). # Behaves similarly to +attribute_changed?+. This method is useful in @@ -182,26 +129,6 @@ module ActiveRecord result end - def mutations_from_database - unless defined?(@mutations_from_database) - @mutations_from_database = nil - end - @mutations_from_database ||= AttributeMutationTracker.new(@attributes) - end - - def changes_include?(attr_name) - super || mutations_from_database.changed?(attr_name) - end - - def clear_attribute_change(attr_name) - mutations_from_database.forget_change(attr_name) - end - - def attribute_will_change!(attr_name) - super - mutations_from_database.force_change(attr_name) - end - def _update_record(*) partial_writes? ? super(keys_for_partial_write) : super end @@ -213,25 +140,6 @@ module ActiveRecord def keys_for_partial_write changed_attribute_names_to_save & self.class.column_names end - - def forget_attribute_assignments - @attributes = @attributes.map(&:forgetting_assignment) - end - - def mutations_before_last_save - @mutations_before_last_save ||= NullMutationTracker.instance - end - - def cache_changed_attributes - @cached_changed_attributes = changed_attributes - yield - ensure - clear_changed_attributes_cache - end - - def clear_changed_attributes_cache - remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes) - end end end end diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb deleted file mode 100644 index 94bf641a5d..0000000000 --- a/activerecord/lib/active_record/attribute_mutation_tracker.rb +++ /dev/null @@ -1,111 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - class AttributeMutationTracker # :nodoc: - OPTION_NOT_GIVEN = Object.new - - def initialize(attributes) - @attributes = attributes - @forced_changes = Set.new - end - - def changed_values - attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result| - if changed?(attr_name) - result[attr_name] = attributes[attr_name].original_value - end - end - end - - def changes - attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result| - change = change_to_attribute(attr_name) - if change - result[attr_name] = change - end - end - end - - def change_to_attribute(attr_name) - attr_name = attr_name.to_s - if changed?(attr_name) - [attributes[attr_name].original_value, attributes.fetch_value(attr_name)] - end - end - - def any_changes? - attr_names.any? { |attr| changed?(attr) } - end - - def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) - attr_name = attr_name.to_s - forced_changes.include?(attr_name) || - attributes[attr_name].changed? && - (OPTION_NOT_GIVEN == from || attributes[attr_name].original_value == from) && - (OPTION_NOT_GIVEN == to || attributes[attr_name].value == to) - end - - def changed_in_place?(attr_name) - attributes[attr_name.to_s].changed_in_place? - end - - def forget_change(attr_name) - attr_name = attr_name.to_s - attributes[attr_name] = attributes[attr_name].forgetting_assignment - forced_changes.delete(attr_name) - end - - def original_value(attr_name) - attributes[attr_name.to_s].original_value - end - - def force_change(attr_name) - forced_changes << attr_name.to_s - end - - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :attributes, :forced_changes - - private - - def attr_names - attributes.keys - end - end - - class NullMutationTracker # :nodoc: - include Singleton - - def changed_values(*) - {} - end - - def changes(*) - {} - end - - def change_to_attribute(attr_name) - end - - def any_changes?(*) - false - end - - def changed?(*) - false - end - - def changed_in_place?(*) - false - end - - def forget_change(*) - end - - def original_value(*) - end - end -end diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb deleted file mode 100644 index 9785666e77..0000000000 --- a/activerecord/lib/active_record/attribute_set.rb +++ /dev/null @@ -1,113 +0,0 @@ -# frozen_string_literal: true - -require "active_record/attribute_set/builder" -require "active_record/attribute_set/yaml_encoder" - -module ActiveRecord - class AttributeSet # :nodoc: - delegate :each_value, :fetch, to: :attributes - - def initialize(attributes) - @attributes = attributes - end - - def [](name) - attributes[name] || Attribute.null(name) - end - - def []=(name, value) - attributes[name] = value - end - - def values_before_type_cast - attributes.transform_values(&:value_before_type_cast) - end - - def to_hash - initialized_attributes.transform_values(&:value) - end - alias_method :to_h, :to_hash - - def key?(name) - attributes.key?(name) && self[name].initialized? - end - - def keys - attributes.each_key.select { |name| self[name].initialized? } - end - - if defined?(JRUBY_VERSION) - # This form is significantly faster on JRuby, and this is one of our biggest hotspots. - # https://github.com/jruby/jruby/pull/2562 - def fetch_value(name, &block) - self[name].value(&block) - end - else - def fetch_value(name) - self[name].value { |n| yield n if block_given? } - end - end - - def write_from_database(name, value) - attributes[name] = self[name].with_value_from_database(value) - end - - def write_from_user(name, value) - attributes[name] = self[name].with_value_from_user(value) - end - - def write_cast_value(name, value) - attributes[name] = self[name].with_cast_value(value) - end - - def freeze - @attributes.freeze - super - end - - def deep_dup - self.class.allocate.tap do |copy| - copy.instance_variable_set(:@attributes, attributes.deep_dup) - end - end - - def initialize_dup(_) - @attributes = attributes.dup - super - end - - def initialize_clone(_) - @attributes = attributes.clone - super - end - - def reset(key) - if key?(key) - write_from_database(key, nil) - end - end - - def accessed - attributes.select { |_, attr| attr.has_been_read? }.keys - end - - def map(&block) - new_attributes = attributes.transform_values(&block) - AttributeSet.new(new_attributes) - end - - def ==(other) - attributes == other.attributes - end - - protected - - attr_reader :attributes - - private - - def initialized_attributes - attributes.select { |_, attr| attr.initialized? } - end - end -end diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb deleted file mode 100644 index 349cc7e403..0000000000 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ /dev/null @@ -1,126 +0,0 @@ -# frozen_string_literal: true - -require "active_record/attribute" - -module ActiveRecord - class AttributeSet # :nodoc: - class Builder # :nodoc: - attr_reader :types, :always_initialized, :default - - def initialize(types, always_initialized = nil, &default) - @types = types - @always_initialized = always_initialized - @default = default - end - - def build_from_database(values = {}, additional_types = {}) - if always_initialized && !values.key?(always_initialized) - values[always_initialized] = nil - end - - attributes = LazyAttributeHash.new(types, values, additional_types, &default) - AttributeSet.new(attributes) - end - end - end - - class LazyAttributeHash # :nodoc: - delegate :transform_values, :each_key, :each_value, :fetch, to: :materialize - - def initialize(types, values, additional_types, &default) - @types = types - @values = values - @additional_types = additional_types - @materialized = false - @delegate_hash = {} - @default = default || proc {} - end - - def key?(key) - delegate_hash.key?(key) || values.key?(key) || types.key?(key) - end - - def [](key) - delegate_hash[key] || assign_default_value(key) - end - - def []=(key, value) - if frozen? - raise RuntimeError, "Can't modify frozen hash" - end - delegate_hash[key] = value - end - - def deep_dup - dup.tap do |copy| - copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup)) - end - end - - def initialize_dup(_) - @delegate_hash = Hash[delegate_hash] - super - end - - def select - keys = types.keys | values.keys | delegate_hash.keys - keys.each_with_object({}) do |key, hash| - attribute = self[key] - if yield(key, attribute) - hash[key] = attribute - end - end - end - - def ==(other) - if other.is_a?(LazyAttributeHash) - materialize == other.materialize - else - materialize == other - end - end - - def marshal_dump - materialize - end - - def marshal_load(delegate_hash) - @delegate_hash = delegate_hash - @types = {} - @values = {} - @additional_types = {} - @materialized = true - end - - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :types, :values, :additional_types, :delegate_hash, :default - - def materialize - unless @materialized - values.each_key { |key| self[key] } - types.each_key { |key| self[key] } - unless frozen? - @materialized = true - end - end - delegate_hash - end - - private - - def assign_default_value(name) - type = additional_types.fetch(name, types[name]) - value_present = true - value = values.fetch(name) { value_present = false } - - if value_present - delegate_hash[name] = Attribute.from_database(name, value, type) - elsif types.key?(name) - delegate_hash[name] = default.call(name) || Attribute.uninitialized(name, type) - end - end - end -end diff --git a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb deleted file mode 100644 index 9254ce16ab..0000000000 --- a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - class AttributeSet - # Attempts to do more intelligent YAML dumping of an - # ActiveRecord::AttributeSet to reduce the size of the resulting string - class YAMLEncoder # :nodoc: - def initialize(default_types) - @default_types = default_types - end - - def encode(attribute_set, coder) - coder["concise_attributes"] = attribute_set.each_value.map do |attr| - if attr.type.equal?(default_types[attr.name]) - attr.with_type(nil) - else - attr - end - end - end - - def decode(coder) - if coder["attributes"] - coder["attributes"] - else - attributes_hash = Hash[coder["concise_attributes"].map do |attr| - if attr.type.nil? - attr = attr.with_type(default_types[attr.name]) - end - [attr.name, attr] - end] - AttributeSet.new(attributes_hash) - end - end - - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :default_types - end - end -end diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 9224d58928..0b7c9398a8 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "active_record/attribute/user_provided_default" +require "active_model/attribute/user_provided_default" module ActiveRecord # See ActiveRecord::Attributes::ClassMethods for documentation @@ -250,14 +250,14 @@ module ActiveRecord if value == NO_DEFAULT_PROVIDED default_attribute = _default_attributes[name].with_type(type) elsif from_user - default_attribute = Attribute::UserProvidedDefault.new( + default_attribute = ActiveModel::Attribute::UserProvidedDefault.new( name, value, type, _default_attributes.fetch(name.to_s) { nil }, ) else - default_attribute = Attribute.from_database(name, value, type) + default_attribute = ActiveModel::Attribute.from_database(name, value, type) end _default_attributes[name] = default_attribute end diff --git a/activerecord/lib/active_record/legacy_yaml_adapter.rb b/activerecord/lib/active_record/legacy_yaml_adapter.rb index 23644aab8f..ffa095dd94 100644 --- a/activerecord/lib/active_record/legacy_yaml_adapter.rb +++ b/activerecord/lib/active_record/legacy_yaml_adapter.rb @@ -8,7 +8,7 @@ module ActiveRecord case coder["active_record_yaml_version"] when 1, 2 then coder else - if coder["attributes"].is_a?(AttributeSet) + if coder["attributes"].is_a?(ActiveModel::AttributeSet) Rails420.convert(klass, coder) else Rails41.convert(klass, coder) diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index bed9400f51..12ee4a4137 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -323,7 +323,7 @@ module ActiveRecord end def attributes_builder # :nodoc: - @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) do |name| + @attributes_builder ||= ActiveModel::AttributeSet::Builder.new(attribute_types, primary_key) do |name| unless columns_hash.key?(name) _default_attributes[name].dup end @@ -346,7 +346,7 @@ module ActiveRecord end def yaml_encoder # :nodoc: - @yaml_encoder ||= AttributeSet::YAMLEncoder.new(attribute_types) + @yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types) end # Returns the type of the attribute with the given name, after applying @@ -376,7 +376,7 @@ module ActiveRecord end def _default_attributes # :nodoc: - @default_attributes ||= AttributeSet.new({}) + @default_attributes ||= ActiveModel::AttributeSet.new({}) end # Returns an array of column names as strings. 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, diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb deleted file mode 100644 index 8be77ed88f..0000000000 --- a/activerecord/test/cases/attribute_set_test.rb +++ /dev/null @@ -1,255 +0,0 @@ -# frozen_string_literal: true - -require "cases/helper" - -module ActiveRecord - class AttributeSetTest < ActiveRecord::TestCase - test "building a new set from raw attributes" do - builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) - attributes = builder.build_from_database(foo: "1.1", bar: "2.2") - - assert_equal 1, attributes[:foo].value - assert_equal 2.2, attributes[:bar].value - assert_equal :foo, attributes[:foo].name - assert_equal :bar, attributes[:bar].name - end - - test "building with custom types" do - builder = AttributeSet::Builder.new(foo: Type::Float.new) - attributes = builder.build_from_database({ foo: "3.3", bar: "4.4" }, { bar: Type::Integer.new }) - - assert_equal 3.3, attributes[:foo].value - assert_equal 4, attributes[:bar].value - end - - test "[] returns a null object" do - builder = AttributeSet::Builder.new(foo: Type::Float.new) - attributes = builder.build_from_database(foo: "3.3") - - assert_equal "3.3", attributes[:foo].value_before_type_cast - assert_nil attributes[:bar].value_before_type_cast - assert_equal :bar, attributes[:bar].name - end - - test "duping creates a new hash, but does not dup the attributes" do - builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new) - attributes = builder.build_from_database(foo: 1, bar: "foo") - - # Ensure the type cast value is cached - attributes[:foo].value - attributes[:bar].value - - duped = attributes.dup - duped.write_from_database(:foo, 2) - duped[:bar].value << "bar" - - assert_equal 1, attributes[:foo].value - assert_equal 2, duped[:foo].value - assert_equal "foobar", attributes[:bar].value - assert_equal "foobar", duped[:bar].value - end - - test "deep_duping creates a new hash and dups each attribute" do - builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new) - attributes = builder.build_from_database(foo: 1, bar: "foo") - - # Ensure the type cast value is cached - attributes[:foo].value - attributes[:bar].value - - duped = attributes.deep_dup - duped.write_from_database(:foo, 2) - duped[:bar].value << "bar" - - assert_equal 1, attributes[:foo].value - assert_equal 2, duped[:foo].value - assert_equal "foo", attributes[:bar].value - assert_equal "foobar", duped[:bar].value - end - - test "freezing cloned set does not freeze original" do - attributes = AttributeSet.new({}) - clone = attributes.clone - - clone.freeze - - assert clone.frozen? - assert_not attributes.frozen? - end - - test "to_hash returns a hash of the type cast values" do - builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) - attributes = builder.build_from_database(foo: "1.1", bar: "2.2") - - assert_equal({ foo: 1, bar: 2.2 }, attributes.to_hash) - assert_equal({ foo: 1, bar: 2.2 }, attributes.to_h) - end - - test "to_hash maintains order" do - builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) - attributes = builder.build_from_database(foo: "2.2", bar: "3.3") - - attributes[:bar] - hash = attributes.to_h - - assert_equal [[:foo, 2], [:bar, 3.3]], hash.to_a - end - - test "values_before_type_cast" do - builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new) - attributes = builder.build_from_database(foo: "1.1", bar: "2.2") - - assert_equal({ foo: "1.1", bar: "2.2" }, attributes.values_before_type_cast) - end - - test "known columns are built with uninitialized attributes" do - attributes = attributes_with_uninitialized_key - assert attributes[:foo].initialized? - assert_not attributes[:bar].initialized? - end - - test "uninitialized attributes are not included in the attributes hash" do - attributes = attributes_with_uninitialized_key - assert_equal({ foo: 1 }, attributes.to_hash) - end - - test "uninitialized attributes are not included in keys" do - attributes = attributes_with_uninitialized_key - assert_equal [:foo], attributes.keys - end - - test "uninitialized attributes return false for key?" do - attributes = attributes_with_uninitialized_key - assert attributes.key?(:foo) - assert_not attributes.key?(:bar) - end - - test "unknown attributes return false for key?" do - attributes = attributes_with_uninitialized_key - assert_not attributes.key?(:wibble) - end - - test "fetch_value returns the value for the given initialized attribute" do - builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) - attributes = builder.build_from_database(foo: "1.1", bar: "2.2") - - assert_equal 1, attributes.fetch_value(:foo) - assert_equal 2.2, attributes.fetch_value(:bar) - end - - test "fetch_value returns nil for unknown attributes" do - attributes = attributes_with_uninitialized_key - assert_nil attributes.fetch_value(:wibble) { "hello" } - end - - test "fetch_value returns nil for unknown attributes when types has a default" do - types = Hash.new(Type::Value.new) - builder = AttributeSet::Builder.new(types) - attributes = builder.build_from_database - - assert_nil attributes.fetch_value(:wibble) { "hello" } - end - - test "fetch_value uses the given block for uninitialized attributes" do - attributes = attributes_with_uninitialized_key - value = attributes.fetch_value(:bar) { |n| n.to_s + "!" } - assert_equal "bar!", value - end - - test "fetch_value returns nil for uninitialized attributes if no block is given" do - attributes = attributes_with_uninitialized_key - assert_nil attributes.fetch_value(:bar) - end - - test "the primary_key is always initialized" do - builder = AttributeSet::Builder.new({ foo: Type::Integer.new }, :foo) - attributes = builder.build_from_database - - assert attributes.key?(:foo) - assert_equal [:foo], attributes.keys - assert attributes[:foo].initialized? - end - - class MyType - def cast(value) - return if value.nil? - value + " from user" - end - - def deserialize(value) - return if value.nil? - value + " from database" - end - - def assert_valid_value(*) - end - end - - test "write_from_database sets the attribute with database typecasting" do - builder = AttributeSet::Builder.new(foo: MyType.new) - attributes = builder.build_from_database - - assert_nil attributes.fetch_value(:foo) - - attributes.write_from_database(:foo, "value") - - assert_equal "value from database", attributes.fetch_value(:foo) - end - - test "write_from_user sets the attribute with user typecasting" do - builder = AttributeSet::Builder.new(foo: MyType.new) - attributes = builder.build_from_database - - assert_nil attributes.fetch_value(:foo) - - attributes.write_from_user(:foo, "value") - - assert_equal "value from user", attributes.fetch_value(:foo) - end - - def attributes_with_uninitialized_key - builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) - builder.build_from_database(foo: "1.1") - end - - test "freezing doesn't prevent the set from materializing" do - builder = AttributeSet::Builder.new(foo: Type::String.new) - attributes = builder.build_from_database(foo: "1") - - attributes.freeze - assert_equal({ foo: "1" }, attributes.to_hash) - end - - test "#accessed_attributes returns only attributes which have been read" do - builder = AttributeSet::Builder.new(foo: Type::Value.new, bar: Type::Value.new) - attributes = builder.build_from_database(foo: "1", bar: "2") - - assert_equal [], attributes.accessed - - attributes.fetch_value(:foo) - - assert_equal [:foo], attributes.accessed - end - - test "#map returns a new attribute set with the changes applied" do - builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new) - attributes = builder.build_from_database(foo: "1", bar: "2") - new_attributes = attributes.map do |attr| - attr.with_cast_value(attr.value + 1) - end - - assert_equal 2, new_attributes.fetch_value(:foo) - assert_equal 3, new_attributes.fetch_value(:bar) - end - - test "comparison for equality is correctly implemented" do - builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new) - attributes = builder.build_from_database(foo: "1", bar: "2") - attributes2 = builder.build_from_database(foo: "1", bar: "2") - attributes3 = builder.build_from_database(foo: "2", bar: "2") - - assert_equal attributes, attributes2 - assert_not_equal attributes2, attributes3 - end - end -end diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb deleted file mode 100644 index 1731e7926e..0000000000 --- a/activerecord/test/cases/attribute_test.rb +++ /dev/null @@ -1,255 +0,0 @@ -# frozen_string_literal: true - -require "cases/helper" - -module ActiveRecord - class AttributeTest < ActiveRecord::TestCase - setup do - @type = Minitest::Mock.new - end - - teardown do - assert @type.verify - end - - test "from_database + read type casts from database" do - @type.expect(:deserialize, "type cast from database", ["a value"]) - attribute = Attribute.from_database(nil, "a value", @type) - - type_cast_value = attribute.value - - assert_equal "type cast from database", type_cast_value - end - - test "from_user + read type casts from user" do - @type.expect(:cast, "type cast from user", ["a value"]) - attribute = Attribute.from_user(nil, "a value", @type) - - type_cast_value = attribute.value - - assert_equal "type cast from user", type_cast_value - end - - test "reading memoizes the value" do - @type.expect(:deserialize, "from the database", ["whatever"]) - attribute = Attribute.from_database(nil, "whatever", @type) - - type_cast_value = attribute.value - second_read = attribute.value - - assert_equal "from the database", type_cast_value - assert_same type_cast_value, second_read - end - - test "reading memoizes falsy values" do - @type.expect(:deserialize, false, ["whatever"]) - attribute = Attribute.from_database(nil, "whatever", @type) - - attribute.value - attribute.value - end - - test "read_before_typecast returns the given value" do - attribute = Attribute.from_database(nil, "raw value", @type) - - raw_value = attribute.value_before_type_cast - - assert_equal "raw value", raw_value - end - - test "from_database + read_for_database type casts to and from database" do - @type.expect(:deserialize, "read from database", ["whatever"]) - @type.expect(:serialize, "ready for database", ["read from database"]) - attribute = Attribute.from_database(nil, "whatever", @type) - - serialize = attribute.value_for_database - - assert_equal "ready for database", serialize - end - - test "from_user + read_for_database type casts from the user to the database" do - @type.expect(:cast, "read from user", ["whatever"]) - @type.expect(:serialize, "ready for database", ["read from user"]) - attribute = Attribute.from_user(nil, "whatever", @type) - - serialize = attribute.value_for_database - - assert_equal "ready for database", serialize - end - - test "duping dups the value" do - @type.expect(:deserialize, "type cast".dup, ["a value"]) - attribute = Attribute.from_database(nil, "a value", @type) - - value_from_orig = attribute.value - value_from_clone = attribute.dup.value - value_from_orig << " foo" - - assert_equal "type cast foo", value_from_orig - assert_equal "type cast", value_from_clone - end - - test "duping does not dup the value if it is not dupable" do - @type.expect(:deserialize, false, ["a value"]) - attribute = Attribute.from_database(nil, "a value", @type) - - assert_same attribute.value, attribute.dup.value - end - - test "duping does not eagerly type cast if we have not yet type cast" do - attribute = Attribute.from_database(nil, "a value", @type) - attribute.dup - end - - class MyType - def cast(value) - value + " from user" - end - - def deserialize(value) - value + " from database" - end - - def assert_valid_value(*) - end - end - - test "with_value_from_user returns a new attribute with the value from the user" do - old = Attribute.from_database(nil, "old", MyType.new) - new = old.with_value_from_user("new") - - assert_equal "old from database", old.value - assert_equal "new from user", new.value - end - - test "with_value_from_database returns a new attribute with the value from the database" do - old = Attribute.from_user(nil, "old", MyType.new) - new = old.with_value_from_database("new") - - assert_equal "old from user", old.value - assert_equal "new from database", new.value - end - - test "uninitialized attributes yield their name if a block is given to value" do - block = proc { |name| name.to_s + "!" } - foo = Attribute.uninitialized(:foo, nil) - bar = Attribute.uninitialized(:bar, nil) - - assert_equal "foo!", foo.value(&block) - assert_equal "bar!", bar.value(&block) - end - - test "uninitialized attributes have no value" do - assert_nil Attribute.uninitialized(:foo, nil).value - end - - test "attributes equal other attributes with the same constructor arguments" do - first = Attribute.from_database(:foo, 1, Type::Integer.new) - second = Attribute.from_database(:foo, 1, Type::Integer.new) - assert_equal first, second - end - - test "attributes do not equal attributes with different names" do - first = Attribute.from_database(:foo, 1, Type::Integer.new) - second = Attribute.from_database(:bar, 1, Type::Integer.new) - assert_not_equal first, second - end - - test "attributes do not equal attributes with different types" do - first = Attribute.from_database(:foo, 1, Type::Integer.new) - second = Attribute.from_database(:foo, 1, Type::Float.new) - assert_not_equal first, second - end - - test "attributes do not equal attributes with different values" do - first = Attribute.from_database(:foo, 1, Type::Integer.new) - second = Attribute.from_database(:foo, 2, Type::Integer.new) - assert_not_equal first, second - end - - test "attributes do not equal attributes of other classes" do - first = Attribute.from_database(:foo, 1, Type::Integer.new) - second = Attribute.from_user(:foo, 1, Type::Integer.new) - assert_not_equal first, second - end - - test "an attribute has not been read by default" do - attribute = Attribute.from_database(:foo, 1, Type::Value.new) - assert_not attribute.has_been_read? - end - - test "an attribute has been read when its value is calculated" do - attribute = Attribute.from_database(:foo, 1, Type::Value.new) - attribute.value - assert attribute.has_been_read? - end - - test "an attribute is not changed if it hasn't been assigned or mutated" do - attribute = Attribute.from_database(:foo, 1, Type::Value.new) - - refute attribute.changed? - end - - test "an attribute is changed if it's been assigned a new value" do - attribute = Attribute.from_database(:foo, 1, Type::Value.new) - changed = attribute.with_value_from_user(2) - - assert changed.changed? - end - - test "an attribute is not changed if it's assigned the same value" do - attribute = Attribute.from_database(:foo, 1, Type::Value.new) - unchanged = attribute.with_value_from_user(1) - - refute unchanged.changed? - end - - test "an attribute can not be mutated if it has not been read, - and skips expensive calculations" do - type_which_raises_from_all_methods = Object.new - attribute = Attribute.from_database(:foo, "bar", type_which_raises_from_all_methods) - - assert_not attribute.changed_in_place? - end - - test "an attribute is changed if it has been mutated" do - attribute = Attribute.from_database(:foo, "bar", Type::String.new) - attribute.value << "!" - - assert attribute.changed_in_place? - assert attribute.changed? - end - - test "an attribute can forget its changes" do - attribute = Attribute.from_database(:foo, "bar", Type::String.new) - changed = attribute.with_value_from_user("foo") - forgotten = changed.forgetting_assignment - - assert changed.changed? # sanity check - refute forgotten.changed? - end - - test "with_value_from_user validates the value" do - type = Type::Value.new - type.define_singleton_method(:assert_valid_value) do |value| - if value == 1 - raise ArgumentError - end - end - - attribute = Attribute.from_database(:foo, 1, type) - assert_equal 1, attribute.value - assert_equal 2, attribute.with_value_from_user(2).value - assert_raises ArgumentError do - attribute.with_value_from_user(1) - end - end - - test "with_type preserves mutations" do - attribute = Attribute.from_database(:foo, "".dup, Type::Value.new) - attribute.value << "1" - - assert_equal 1, attribute.with_type(Type::Integer.new).value - end - end -end -- cgit v1.2.3 From de40c45f2b7d4a2ba47d28e9a4967134e45df91f Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 8 Nov 2017 22:21:12 +0900 Subject: Remove useless preloader classes They are only different by one line of code which doesn't deserve a hierarchy of 7 classes. Closes #31079. [Ryuta Kamizono & Bogdan Gusiev] --- .../lib/active_record/associations/preloader.rb | 26 ++++++---------------- .../associations/preloader/association.rb | 8 ++++++- .../associations/preloader/belongs_to.rb | 10 --------- .../preloader/collection_association.rb | 16 ------------- .../associations/preloader/has_many.rb | 10 --------- .../associations/preloader/has_many_through.rb | 11 --------- .../associations/preloader/has_one.rb | 10 --------- .../associations/preloader/has_one_through.rb | 11 --------- .../associations/preloader/singular_association.rb | 15 ------------- .../associations/preloader/through_association.rb | 2 +- 10 files changed, 15 insertions(+), 104 deletions(-) delete mode 100644 activerecord/lib/active_record/associations/preloader/belongs_to.rb delete mode 100644 activerecord/lib/active_record/associations/preloader/collection_association.rb delete mode 100644 activerecord/lib/active_record/associations/preloader/has_many.rb delete mode 100644 activerecord/lib/active_record/associations/preloader/has_many_through.rb delete mode 100644 activerecord/lib/active_record/associations/preloader/has_one.rb delete mode 100644 activerecord/lib/active_record/associations/preloader/has_one_through.rb delete mode 100644 activerecord/lib/active_record/associations/preloader/singular_association.rb (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 5a93a89d0a..e1087be9b3 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -44,16 +44,8 @@ module ActiveRecord extend ActiveSupport::Autoload eager_autoload do - autoload :Association, "active_record/associations/preloader/association" - autoload :SingularAssociation, "active_record/associations/preloader/singular_association" - autoload :CollectionAssociation, "active_record/associations/preloader/collection_association" - autoload :ThroughAssociation, "active_record/associations/preloader/through_association" - - autoload :HasMany, "active_record/associations/preloader/has_many" - autoload :HasManyThrough, "active_record/associations/preloader/has_many_through" - autoload :HasOne, "active_record/associations/preloader/has_one" - autoload :HasOneThrough, "active_record/associations/preloader/has_one_through" - autoload :BelongsTo, "active_record/associations/preloader/belongs_to" + autoload :Association, "active_record/associations/preloader/association" + autoload :ThroughAssociation, "active_record/associations/preloader/through_association" end # Eager loads the named associations for the given Active Record record(s). @@ -182,8 +174,7 @@ module ActiveRecord end # Returns a class containing the logic needed to load preload the data - # and attach it to a relation. For example +Preloader::Association+ or - # +Preloader::HasManyThrough+. The class returned implements a `run` method + # and attach it to a relation. The class returned implements a `run` method # that accepts a preloader. def preloader_for(reflection, owners) if owners.first.association(reflection.name).loaded? @@ -191,13 +182,10 @@ module ActiveRecord end reflection.check_preloadable! - case reflection.macro - when :has_many - reflection.options[:through] ? HasManyThrough : HasMany - when :has_one - reflection.options[:through] ? HasOneThrough : HasOne - when :belongs_to - BelongsTo + if reflection.options[:through] + ThroughAssociation + else + Association end end end diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 19c337dc39..735da152b7 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -42,7 +42,13 @@ module ActiveRecord end def associate_records_to_owner(owner, records) - raise NotImplementedError + association = owner.association(reflection.name) + if reflection.collection? + association.loaded! + association.target.concat(records) + else + association.target = records.first + end end def owner_keys diff --git a/activerecord/lib/active_record/associations/preloader/belongs_to.rb b/activerecord/lib/active_record/associations/preloader/belongs_to.rb deleted file mode 100644 index a8e3340b23..0000000000 --- a/activerecord/lib/active_record/associations/preloader/belongs_to.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class BelongsTo < SingularAssociation #:nodoc: - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb deleted file mode 100644 index fc2029f54a..0000000000 --- a/activerecord/lib/active_record/associations/preloader/collection_association.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class CollectionAssociation < Association #:nodoc: - private - def associate_records_to_owner(owner, records) - association = owner.association(reflection.name) - association.loaded! - association.target.concat(records) - end - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/has_many.rb b/activerecord/lib/active_record/associations/preloader/has_many.rb deleted file mode 100644 index 72f55bc43f..0000000000 --- a/activerecord/lib/active_record/associations/preloader/has_many.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class HasMany < CollectionAssociation #:nodoc: - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb deleted file mode 100644 index 3e17d07a33..0000000000 --- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class HasManyThrough < CollectionAssociation #:nodoc: - include ThroughAssociation - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/has_one.rb b/activerecord/lib/active_record/associations/preloader/has_one.rb deleted file mode 100644 index e339b65fb5..0000000000 --- a/activerecord/lib/active_record/associations/preloader/has_one.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class HasOne < SingularAssociation #:nodoc: - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/has_one_through.rb b/activerecord/lib/active_record/associations/preloader/has_one_through.rb deleted file mode 100644 index 17734d0257..0000000000 --- a/activerecord/lib/active_record/associations/preloader/has_one_through.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class HasOneThrough < SingularAssociation #:nodoc: - include ThroughAssociation - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/singular_association.rb b/activerecord/lib/active_record/associations/preloader/singular_association.rb deleted file mode 100644 index 30a92411e3..0000000000 --- a/activerecord/lib/active_record/associations/preloader/singular_association.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class SingularAssociation < Association #:nodoc: - private - def associate_records_to_owner(owner, records) - association = owner.association(reflection.name) - association.target = records.first - end - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 762275fbad..a6b7ab80a2 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -3,7 +3,7 @@ module ActiveRecord module Associations class Preloader - module ThroughAssociation #:nodoc: + class ThroughAssociation < Association # :nodoc: def run(preloader) already_loaded = owners.first.association(through_reflection.name).loaded? through_scope = through_scope() -- 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. --- .../relation/predicate_builder/relation_handler.rb | 4 ++++ activerecord/lib/active_record/relation/query_methods.rb | 3 +++ activerecord/test/cases/relations_test.rb | 12 ++++++++++++ 3 files changed, 19 insertions(+) (limited to 'activerecord') 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 diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 844be0d0bf..e44533cf60 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -195,6 +195,18 @@ class RelationTest < ActiveRecord::TestCase assert_equal(relation.map(&:post_count).sort, subquery.values.sort) end + def test_finding_with_subquery_with_eager_loading_in_from + relation = Comment.includes(:post).where("posts.type": "Post") + assert_equal relation.to_a, Comment.select("*").from(relation).to_a + assert_equal relation.to_a, Comment.select("subquery.*").from(relation).to_a + assert_equal relation.to_a, Comment.select("a.*").from(relation, :a).to_a + end + + def test_finding_with_subquery_with_eager_loading_in_where + relation = Comment.includes(:post).where("posts.type": "Post") + assert_equal relation.to_a, Comment.where(id: relation).to_a + end + def test_finding_with_conditions assert_equal ["David"], Author.where(name: "David").map(&:name) assert_equal ["Mary"], Author.where(["name = ?", "Mary"]).map(&:name) -- 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` --- .../lib/active_record/associations/collection_proxy.rb | 10 ++++++---- activerecord/lib/active_record/relation.rb | 3 ++- activerecord/lib/active_record/relation/delegation.rb | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 07f7303f8d..8b4a48a38c 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -988,6 +988,12 @@ module ActiveRecord load_target == other end + ## + # :method: to_ary + # + # :call-seq: + # to_ary() + # # Returns a new array of objects from the collection. If the collection # hasn't been loaded, it fetches the records from the database. # @@ -1021,10 +1027,6 @@ module ActiveRecord # # #, # # # # # ] - def to_ary - load_target.dup - end - alias_method :to_a, :to_ary def records # :nodoc: load_target diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 7615fb6ee9..e2d2f45503 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -243,9 +243,10 @@ module ActiveRecord end # Converts relation objects to Array. - def to_a + def to_ary records.dup end + alias to_a to_ary def records # :nodoc: load 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 d6e86f17732e67ecc04870af1fb9a4eaaf0f3e1b Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 10 Nov 2017 23:43:11 +0900 Subject: Fix random CI failure due to non-deterministic sorting order https://travis-ci.org/rails/rails/jobs/300163487#L1974 --- activerecord/test/cases/relations_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index e44533cf60..eec43ef79e 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -204,7 +204,7 @@ class RelationTest < ActiveRecord::TestCase def test_finding_with_subquery_with_eager_loading_in_where relation = Comment.includes(:post).where("posts.type": "Post") - assert_equal relation.to_a, Comment.where(id: relation).to_a + assert_equal relation.sort_by(&:id), Comment.where(id: relation).sort_by(&:id) end def test_finding_with_conditions -- 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 ++++++++---------- activerecord/test/cases/relation_test.rb | 9 +++++++++ 2 files changed, 17 insertions(+), 10 deletions(-) (limited to 'activerecord') 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 diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index fd5985ffe7..8362722e12 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -274,6 +274,15 @@ module ActiveRecord assert_equal({ 2 => 1, 4 => 3, 5 => 1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count) end + def test_relation_merging_keeps_joining_order + authors = Author.where(id: 1) + posts = Post.joins(:author).merge(authors) + comments = Comment.joins(:post).merge(posts) + ratings = Rating.joins(:comment).merge(comments) + + assert_equal 3, ratings.count + end + class EnsureRoundTripTypeCasting < ActiveRecord::Type::Value def type :string -- cgit v1.2.3 From 24b59434e6aca9679b9f86a41cfbb1a33e3d5619 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 11 Nov 2017 06:43:54 +0900 Subject: Add missing autoload `Type` (#31123) Attribute modules (`Attribute`, `Attributes`, `AttributeSet`) uses `Type`, but referencing `Type` before the modules still fail. ``` % ./bin/test -w test/cases/attribute_test.rb -n test_with_value_from_user_validates_the_value Run options: -n test_with_value_from_user_validates_the_value --seed 31876 E Error: ActiveModel::AttributeTest#test_with_value_from_user_validates_the_value: NameError: uninitialized constant ActiveModel::AttributeTest::Type /Users/kamipo/src/github.com/rails/rails/activemodel/test/cases/attribute_test.rb:233:in `block in ' bin/test test/cases/attribute_test.rb:232 Finished in 0.002985s, 335.0479 runs/s, 335.0479 assertions/s. 1 runs, 1 assertions, 0 failures, 1 errors, 0 skips ``` Probably we need more autoloading at least `Type`. --- activerecord/lib/active_record.rb | 1 + activerecord/lib/active_record/connection_adapters/abstract_adapter.rb | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 0e036f05f5..5de6503144 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -104,6 +104,7 @@ module ActiveRecord autoload :Result autoload :TableMetadata + autoload :Type end module Coders diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index e3aab8dad8..345983a655 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "active_record/type" require "active_record/connection_adapters/determine_if_preparable_visitor" require "active_record/connection_adapters/schema_cache" require "active_record/connection_adapters/sql_type_metadata" -- cgit v1.2.3 From ee5cf14ab06c366b37a6339f2af7c41457b3557b Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 11 Nov 2017 19:18:17 +0900 Subject: Should test actual error which is raised from the database --- .../test/cases/adapters/mysql2/transaction_test.rb | 24 +++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb index 25d9f69a89..7b032fed6d 100644 --- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb +++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb @@ -62,7 +62,29 @@ module ActiveRecord test "raises TransactionTimeout when mysql raises ER_LOCK_WAIT_TIMEOUT" do assert_raises(ActiveRecord::TransactionTimeout) do - ActiveRecord::Base.connection.execute("SIGNAL SQLSTATE 'HY000' SET MESSAGE_TEXT = 'Testing error', MYSQL_ERRNO = 1205;") + s = Sample.create!(value: 1) + latch1 = Concurrent::CountDownLatch.new + latch2 = Concurrent::CountDownLatch.new + + thread = Thread.new do + Sample.transaction do + Sample.lock.find(s.id) + latch1.count_down + latch2.wait + end + end + + begin + Sample.transaction do + latch1.wait + Sample.connection.execute("SET innodb_lock_wait_timeout = 1") + Sample.lock.find(s.id) + end + ensure + Sample.connection.execute("SET innodb_lock_wait_timeout = DEFAULT") + latch2.count_down + thread.join + end end end end -- cgit v1.2.3 From 4a65dfcb9adae8fb12a86521c1a34b392e6084c2 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 11 Nov 2017 19:53:40 +0900 Subject: Raise `TransactionTimeout` when lock wait timeout exceeded for PG adapter Follow up of #30360. --- activerecord/CHANGELOG.md | 4 +-- .../connection_adapters/postgresql_adapter.rb | 3 +++ .../test/cases/adapters/mysql2/transaction_test.rb | 2 +- .../cases/adapters/postgresql/transaction_test.rb | 29 ++++++++++++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index e6a41ec281..34ef5c79e1 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -144,8 +144,8 @@ *Jeremy Green* -* Add new error class `TransactionTimeout` for MySQL adapter which will be raised - when lock wait time expires. +* Add new error class `TransactionTimeout` which will be raised + when lock wait timeout exceeded. *Gabriel Courtemanche* diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 2c3c1df2a9..46863c41ab 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -391,6 +391,7 @@ module ActiveRecord UNIQUE_VIOLATION = "23505" SERIALIZATION_FAILURE = "40001" DEADLOCK_DETECTED = "40P01" + LOCK_NOT_AVAILABLE = "55P03" def translate_exception(exception, message) return exception unless exception.respond_to?(:result) @@ -410,6 +411,8 @@ module ActiveRecord SerializationFailure.new(message) when DEADLOCK_DETECTED Deadlocked.new(message) + when LOCK_NOT_AVAILABLE + TransactionTimeout.new(message) else super end diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb index 7b032fed6d..ac9a8d9dfb 100644 --- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb +++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb @@ -60,7 +60,7 @@ module ActiveRecord end end - test "raises TransactionTimeout when mysql raises ER_LOCK_WAIT_TIMEOUT" do + test "raises TransactionTimeout when lock wait timeout exceeded" do assert_raises(ActiveRecord::TransactionTimeout) do s = Sample.create!(value: 1) latch1 = Concurrent::CountDownLatch.new diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb index f56adf4a5e..b6aec8e993 100644 --- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb +++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb @@ -91,6 +91,35 @@ module ActiveRecord end end + test "raises TransactionTimeout when lock wait timeout exceeded" do + skip unless ActiveRecord::Base.connection.postgresql_version >= 90300 + assert_raises(ActiveRecord::TransactionTimeout) do + s = Sample.create!(value: 1) + latch1 = Concurrent::CountDownLatch.new + latch2 = Concurrent::CountDownLatch.new + + thread = Thread.new do + Sample.transaction do + Sample.lock.find(s.id) + latch1.count_down + latch2.wait + end + end + + begin + Sample.transaction do + latch1.wait + Sample.connection.execute("SET lock_timeout = 1") + Sample.lock.find(s.id) + end + ensure + Sample.connection.execute("SET lock_timeout = DEFAULT") + latch2.count_down + thread.join + end + end + end + private def with_warning_suppression -- cgit v1.2.3 From a968a7609db56f56298c462aa26809588f9375de Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 13 Nov 2017 20:15:16 +0900 Subject: Add new error class `StatementTimeout` which will be raised when statement timeout exceeded (#31129) We are sometimes using The MAX_EXECUTION_TIME hint for MySQL depending on the situation. It will prevent catastrophic performance down by wrong performing queries. The new error class `StatementTimeout` will make to be easier to handle that case. https://dev.mysql.com/doc/refman/5.7/en/optimizer-hints.html#optimizer-hints-execution-time --- activerecord/CHANGELOG.md | 5 ++++ .../connection_adapters/abstract_mysql_adapter.rb | 3 +++ .../connection_adapters/postgresql_adapter.rb | 3 +++ activerecord/lib/active_record/errors.rb | 7 ++++-- .../test/cases/adapters/mysql2/transaction_test.rb | 29 ++++++++++++++++++++++ .../cases/adapters/postgresql/transaction_test.rb | 28 +++++++++++++++++++++ 6 files changed, 73 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 34ef5c79e1..c95e80755d 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,8 @@ +* Add new error class `StatementTimeout` which will be raised + when statement timeout exceeded. + + *Ryuta Kamizono* + * Fix `bin/rails db:migrate` with specified `VERSION`. `bin/rails db:migrate` with empty VERSION behaves as without `VERSION`. Check a format of `VERSION`: Allow a migration version number diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index bfec6fb784..ca651ef390 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -635,6 +635,7 @@ module ActiveRecord ER_CANNOT_ADD_FOREIGN = 1215 ER_CANNOT_CREATE_TABLE = 1005 ER_LOCK_WAIT_TIMEOUT = 1205 + ER_QUERY_TIMEOUT = 3024 def translate_exception(exception, message) case error_number(exception) @@ -660,6 +661,8 @@ module ActiveRecord Deadlocked.new(message) when ER_LOCK_WAIT_TIMEOUT TransactionTimeout.new(message) + when ER_QUERY_TIMEOUT + StatementTimeout.new(message) else super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 46863c41ab..5ce6765dd8 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -392,6 +392,7 @@ module ActiveRecord SERIALIZATION_FAILURE = "40001" DEADLOCK_DETECTED = "40P01" LOCK_NOT_AVAILABLE = "55P03" + QUERY_CANCELED = "57014" def translate_exception(exception, message) return exception unless exception.respond_to?(:result) @@ -413,6 +414,8 @@ module ActiveRecord Deadlocked.new(message) when LOCK_NOT_AVAILABLE TransactionTimeout.new(message) + when QUERY_CANCELED + StatementTimeout.new(message) else super end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 9ef3316393..f77cd23e22 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -335,8 +335,11 @@ module ActiveRecord class IrreversibleOrderError < ActiveRecordError end - # TransactionTimeout will be raised when lock wait timeout expires. - # Wait time value is set by innodb_lock_wait_timeout. + # TransactionTimeout will be raised when lock wait timeout exceeded. class TransactionTimeout < StatementInvalid end + + # StatementTimeout will be raised when statement timeout exceeded. + class StatementTimeout < StatementInvalid + end end diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb index ac9a8d9dfb..4a3a4503de 100644 --- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb +++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb @@ -87,5 +87,34 @@ module ActiveRecord end end end + + test "raises StatementTimeout when statement timeout exceeded" do + skip unless ActiveRecord::Base.connection.show_variable("max_execution_time") + assert_raises(ActiveRecord::StatementTimeout) do + s = Sample.create!(value: 1) + latch1 = Concurrent::CountDownLatch.new + latch2 = Concurrent::CountDownLatch.new + + thread = Thread.new do + Sample.transaction do + Sample.lock.find(s.id) + latch1.count_down + latch2.wait + end + end + + begin + Sample.transaction do + latch1.wait + Sample.connection.execute("SET max_execution_time = 1") + Sample.lock.find(s.id) + end + ensure + Sample.connection.execute("SET max_execution_time = DEFAULT") + latch2.count_down + thread.join + end + end + end end end diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb index b6aec8e993..4d63bbce59 100644 --- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb +++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb @@ -120,6 +120,34 @@ module ActiveRecord end end + test "raises StatementTimeout when statement timeout exceeded" do + assert_raises(ActiveRecord::StatementTimeout) do + s = Sample.create!(value: 1) + latch1 = Concurrent::CountDownLatch.new + latch2 = Concurrent::CountDownLatch.new + + thread = Thread.new do + Sample.transaction do + Sample.lock.find(s.id) + latch1.count_down + latch2.wait + end + end + + begin + Sample.transaction do + latch1.wait + Sample.connection.execute("SET statement_timeout = 1") + Sample.lock.find(s.id) + end + ensure + Sample.connection.execute("SET statement_timeout = DEFAULT") + latch2.count_down + thread.join + end + end + end + private def with_warning_suppression -- 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. --- .../lib/active_record/relation/query_methods.rb | 2 ++ activerecord/test/cases/base_test.rb | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) (limited to 'activerecord') 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 diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index f0ef522515..0ae88ee6a0 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1476,4 +1476,25 @@ class BasicsTest < ActiveRecord::TestCase assert_equal(%w(first_name last_name), Developer.ignored_columns) assert_equal(%w(first_name last_name), SymbolIgnoredDeveloper.ignored_columns) end + + test "when #reload called, ignored columns' attribute methods are not defined" do + developer = Developer.create!(name: "Developer") + refute developer.respond_to?(:first_name) + refute developer.respond_to?(:first_name=) + + developer.reload + + refute developer.respond_to?(:first_name) + refute developer.respond_to?(:first_name=) + end + + test "ignored columns not included in SELECT" do + query = Developer.all.to_sql + + # ignored column + refute query.include?("first_name") + + # regular column + assert query.include?("name") + end end -- cgit v1.2.3 From 54e101855b88b577fefd671c4bc489828ec4ab52 Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Mon, 20 Feb 2017 17:07:24 -0500 Subject: Change tests to use models which don't ignore any columns --- .../test/cases/adapters/mysql2/explain_test.rb | 22 +++++++++++----------- .../test/cases/adapters/postgresql/explain_test.rb | 16 ++++++++-------- .../test/cases/adapters/sqlite3/explain_test.rb | 22 +++++++++++----------- 3 files changed, 30 insertions(+), 30 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb index 2736f7cf0e..b8e778f0b0 100644 --- a/activerecord/test/cases/adapters/mysql2/explain_test.rb +++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb @@ -1,23 +1,23 @@ # frozen_string_literal: true require "cases/helper" -require "models/developer" -require "models/computer" +require "models/author" +require "models/post" class Mysql2ExplainTest < ActiveRecord::Mysql2TestCase - fixtures :developers + fixtures :authors def test_explain_for_one_query - explain = Developer.where(id: 1).explain - assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain - assert_match %r(developers |.* const), explain + explain = Author.where(id: 1).explain + assert_match %(EXPLAIN for: SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1), explain + assert_match %r(authors |.* const), explain end def test_explain_with_eager_loading - explain = Developer.where(id: 1).includes(:audit_logs).explain - assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain - assert_match %r(developers |.* const), explain - assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain - assert_match %r(audit_logs |.* ALL), explain + explain = Author.where(id: 1).includes(:posts).explain + assert_match %(EXPLAIN for: SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1), explain + assert_match %r(authors |.* const), explain + assert_match %(EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`author_id` = 1), explain + assert_match %r(posts |.* ALL), explain end end diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb index 16fec94ede..be525383e9 100644 --- a/activerecord/test/cases/adapters/postgresql/explain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb @@ -1,22 +1,22 @@ # frozen_string_literal: true require "cases/helper" -require "models/developer" -require "models/computer" +require "models/author" +require "models/post" class PostgreSQLExplainTest < ActiveRecord::PostgreSQLTestCase - fixtures :developers + fixtures :authors def test_explain_for_one_query - explain = Developer.where(id: 1).explain - assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain + explain = Author.where(id: 1).explain + assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain assert_match %(QUERY PLAN), explain end def test_explain_with_eager_loading - explain = Developer.where(id: 1).includes(:audit_logs).explain + explain = Author.where(id: 1).includes(:posts).explain assert_match %(QUERY PLAN), explain - assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain - assert_match %r(EXPLAIN for: SELECT "audit_logs"\.\* FROM "audit_logs" WHERE "audit_logs"\."developer_id" = (?:\$1 \[\["developer_id", 1\]\]|1)), explain + assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain + assert_match %r(EXPLAIN for: SELECT "posts"\.\* FROM "posts" WHERE "posts"\."author_id" = (?:\$1 \[\["author_id", 1\]\]|1)), explain end end diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb index 3b081d34e1..b6d2ccdb53 100644 --- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb @@ -1,23 +1,23 @@ # frozen_string_literal: true require "cases/helper" -require "models/developer" -require "models/computer" +require "models/author" +require "models/post" class SQLite3ExplainTest < ActiveRecord::SQLite3TestCase - fixtures :developers + fixtures :authors def test_explain_for_one_query - explain = Developer.where(id: 1).explain - assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\? \[\["id", 1\]\]|1)), explain - assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) + explain = Author.where(id: 1).explain + assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\? \[\["id", 1\]\]|1)), explain + assert_match(/(SEARCH )?TABLE authors USING (INTEGER )?PRIMARY KEY/, explain) end def test_explain_with_eager_loading - explain = Developer.where(id: 1).includes(:audit_logs).explain - assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\? \[\["id", 1\]\]|1)), explain - assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) - assert_match %r(EXPLAIN for: SELECT "audit_logs"\.\* FROM "audit_logs" WHERE "audit_logs"\."developer_id" = (?:\? \[\["developer_id", 1\]\]|1)), explain - assert_match(/(SCAN )?TABLE audit_logs/, explain) + explain = Author.where(id: 1).includes(:posts).explain + assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\? \[\["id", 1\]\]|1)), explain + assert_match(/(SEARCH )?TABLE authors USING (INTEGER )?PRIMARY KEY/, explain) + assert_match %r(EXPLAIN for: SELECT "posts"\.\* FROM "posts" WHERE "posts"\."author_id" = (?:\? \[\["author_id", 1\]\]|1)), explain + assert_match(/(SCAN )?TABLE posts/, explain) end end -- cgit v1.2.3 From f8627dfee9c15b9875a8ef1b358ed341ec5ce367 Mon Sep 17 00:00:00 2001 From: Gabriel Sobrinho Date: Tue, 24 Oct 2017 23:17:10 -0200 Subject: Fix postgres ordering issue on default scoping test --- .../test/cases/scoping/default_scoping_test.rb | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 716ca29eda..fdfeabaa3b 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -120,49 +120,49 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_unscope_with_where_attributes expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.where(name: "David").unscope(where: :name).collect(&:name) - assert_equal expected, received + assert_equal expected.sort, received.sort expected_2 = Developer.order("salary DESC").collect(&:name) received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({ where: :name }, :select).collect(&:name) - assert_equal expected_2, received_2 + assert_equal expected_2.sort, received_2.sort expected_3 = Developer.order("salary DESC").collect(&:name) received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect(&:name) - assert_equal expected_3, received_3 + assert_equal expected_3.sort, received_3.sort expected_4 = Developer.order("salary DESC").collect(&:name) received_4 = DeveloperOrderedBySalary.where.not("name" => "Jamis").unscope(where: :name).collect(&:name) - assert_equal expected_4, received_4 + assert_equal expected_4.sort, received_4.sort expected_5 = Developer.order("salary DESC").collect(&:name) received_5 = DeveloperOrderedBySalary.where.not("name" => ["Jamis", "David"]).unscope(where: :name).collect(&:name) - assert_equal expected_5, received_5 + assert_equal expected_5.sort, received_5.sort expected_6 = Developer.order("salary DESC").collect(&:name) received_6 = DeveloperOrderedBySalary.where(Developer.arel_table["name"].eq("David")).unscope(where: :name).collect(&:name) - assert_equal expected_6, received_6 + assert_equal expected_6.sort, received_6.sort expected_7 = Developer.order("salary DESC").collect(&:name) received_7 = DeveloperOrderedBySalary.where(Developer.arel_table[:name].eq("David")).unscope(where: :name).collect(&:name) - assert_equal expected_7, received_7 + assert_equal expected_7.sort, received_7.sort end def test_unscope_comparison_where_clauses # unscoped for WHERE (`developers`.`id` <= 2) expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.where(id: -Float::INFINITY..2).unscope(where: :id).collect { |dev| dev.name } - assert_equal expected, received + assert_equal expected.sort, received.sort # unscoped for WHERE (`developers`.`id` < 2) expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.where(id: -Float::INFINITY...2).unscope(where: :id).collect { |dev| dev.name } - assert_equal expected, received + assert_equal expected.sort, received.sort end def test_unscope_multiple_where_clauses expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.where(name: "Jamis").where(id: 1).unscope(where: [:name, :id]).collect(&:name) - assert_equal expected, received + assert_equal expected.sort, received.sort end def test_unscope_string_where_clauses_involved @@ -172,23 +172,23 @@ class DefaultScopingTest < ActiveRecord::TestCase dev_ordered_relation = DeveloperOrderedBySalary.where(name: "Jamis").where("created_at > ?", 1.year.ago) received = dev_ordered_relation.unscope(where: [:name]).collect(&:name) - assert_equal expected, received + assert_equal expected.sort, received.sort end def test_unscope_with_grouping_attributes expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect(&:name) - assert_equal expected, received + assert_equal expected.sort, received.sort expected_2 = Developer.order("salary DESC").collect(&:name) received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect(&:name) - assert_equal expected_2, received_2 + assert_equal expected_2.sort, received_2.sort end def test_unscope_with_limit_in_query expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect(&:name) - assert_equal expected, received + assert_equal expected.sort, received.sort end def test_order_to_unscope_reordering @@ -472,7 +472,7 @@ class DefaultScopingTest < ActiveRecord::TestCase test "a scope can remove the condition from the default scope" do scope = DeveloperCalledJamis.david2 assert_equal 1, scope.where_clause.ast.children.length - assert_equal Developer.where(name: "David"), scope + assert_equal Developer.where(name: "David").map(&:id), scope.map(&:id) end def test_with_abstract_class_where_clause_should_not_be_duplicated -- cgit v1.2.3 From 8dd76a7a6ff1bb7105beabb8f834ca54ab1e5fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 13 Nov 2017 15:23:28 -0500 Subject: Use .tt extension to all the template files Make clear that the files are not to be run for interpreters. Fixes #23847. Fixes #30690. Closes #23878. --- .../templates/application_record.rb | 5 --- .../templates/application_record.rb.tt | 5 +++ .../migration/templates/create_table_migration.rb | 24 ----------- .../templates/create_table_migration.rb.tt | 24 +++++++++++ .../active_record/migration/templates/migration.rb | 46 ---------------------- .../migration/templates/migration.rb.tt | 46 ++++++++++++++++++++++ .../active_record/model/templates/model.rb | 13 ------ .../active_record/model/templates/model.rb.tt | 13 ++++++ .../active_record/model/templates/module.rb | 7 ---- .../active_record/model/templates/module.rb.tt | 7 ++++ 10 files changed, 95 insertions(+), 95 deletions(-) delete mode 100644 activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb create mode 100644 activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt delete mode 100644 activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb create mode 100644 activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt delete mode 100644 activerecord/lib/rails/generators/active_record/migration/templates/migration.rb create mode 100644 activerecord/lib/rails/generators/active_record/migration/templates/migration.rb.tt delete mode 100644 activerecord/lib/rails/generators/active_record/model/templates/model.rb create mode 100644 activerecord/lib/rails/generators/active_record/model/templates/model.rb.tt delete mode 100644 activerecord/lib/rails/generators/active_record/model/templates/module.rb create mode 100644 activerecord/lib/rails/generators/active_record/model/templates/module.rb.tt (limited to 'activerecord') diff --git a/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb b/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb deleted file mode 100644 index 60050e0bf8..0000000000 --- a/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb +++ /dev/null @@ -1,5 +0,0 @@ -<% module_namespacing do -%> -class ApplicationRecord < ActiveRecord::Base - self.abstract_class = true -end -<% end -%> diff --git a/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt b/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt new file mode 100644 index 0000000000..60050e0bf8 --- /dev/null +++ b/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt @@ -0,0 +1,5 @@ +<% module_namespacing do -%> +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end +<% end -%> diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb deleted file mode 100644 index 5f7201cfe1..0000000000 --- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +++ /dev/null @@ -1,24 +0,0 @@ -class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>] - def change - create_table :<%= table_name %><%= primary_key_type %> do |t| -<% attributes.each do |attribute| -%> -<% if attribute.password_digest? -%> - t.string :password_digest<%= attribute.inject_options %> -<% elsif attribute.token? -%> - t.string :<%= attribute.name %><%= attribute.inject_options %> -<% else -%> - t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %> -<% end -%> -<% end -%> -<% if options[:timestamps] %> - t.timestamps -<% end -%> - end -<% attributes.select(&:token?).each do |attribute| -%> - add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true -<% end -%> -<% attributes_with_index.each do |attribute| -%> - add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> -<% end -%> - end -end diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt new file mode 100644 index 0000000000..5f7201cfe1 --- /dev/null +++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt @@ -0,0 +1,24 @@ +class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>] + def change + create_table :<%= table_name %><%= primary_key_type %> do |t| +<% attributes.each do |attribute| -%> +<% if attribute.password_digest? -%> + t.string :password_digest<%= attribute.inject_options %> +<% elsif attribute.token? -%> + t.string :<%= attribute.name %><%= attribute.inject_options %> +<% else -%> + t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %> +<% end -%> +<% end -%> +<% if options[:timestamps] %> + t.timestamps +<% end -%> + end +<% attributes.select(&:token?).each do |attribute| -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true +<% end -%> +<% attributes_with_index.each do |attribute| -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> +<% end -%> + end +end diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb deleted file mode 100644 index 481c70201b..0000000000 --- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb +++ /dev/null @@ -1,46 +0,0 @@ -class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>] -<%- if migration_action == 'add' -%> - def change -<% attributes.each do |attribute| -%> - <%- if attribute.reference? -%> - add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> - <%- elsif attribute.token? -%> - add_column :<%= table_name %>, :<%= attribute.name %>, :string<%= attribute.inject_options %> - add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true - <%- else -%> - add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> - <%- if attribute.has_index? -%> - add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> - <%- end -%> - <%- end -%> -<%- end -%> - end -<%- elsif migration_action == 'join' -%> - def change - create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t| - <%- attributes.each do |attribute| -%> - <%- if attribute.reference? -%> - t.references :<%= attribute.name %><%= attribute.inject_options %> - <%- else -%> - <%= '# ' unless attribute.has_index? -%>t.index <%= attribute.index_name %><%= attribute.inject_index_options %> - <%- end -%> - <%- end -%> - end - end -<%- else -%> - def change -<% attributes.each do |attribute| -%> -<%- if migration_action -%> - <%- if attribute.reference? -%> - remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> - <%- else -%> - <%- if attribute.has_index? -%> - remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> - <%- end -%> - remove_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> - <%- end -%> -<%- end -%> -<%- end -%> - end -<%- end -%> -end diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb.tt b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb.tt new file mode 100644 index 0000000000..481c70201b --- /dev/null +++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb.tt @@ -0,0 +1,46 @@ +class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>] +<%- if migration_action == 'add' -%> + def change +<% attributes.each do |attribute| -%> + <%- if attribute.reference? -%> + add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> + <%- elsif attribute.token? -%> + add_column :<%= table_name %>, :<%= attribute.name %>, :string<%= attribute.inject_options %> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true + <%- else -%> + add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> + <%- if attribute.has_index? -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> + <%- end -%> +<%- end -%> + end +<%- elsif migration_action == 'join' -%> + def change + create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t| + <%- attributes.each do |attribute| -%> + <%- if attribute.reference? -%> + t.references :<%= attribute.name %><%= attribute.inject_options %> + <%- else -%> + <%= '# ' unless attribute.has_index? -%>t.index <%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> + <%- end -%> + end + end +<%- else -%> + def change +<% attributes.each do |attribute| -%> +<%- if migration_action -%> + <%- if attribute.reference? -%> + remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> + <%- else -%> + <%- if attribute.has_index? -%> + remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> + remove_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> + <%- end -%> +<%- end -%> +<%- end -%> + end +<%- end -%> +end diff --git a/activerecord/lib/rails/generators/active_record/model/templates/model.rb b/activerecord/lib/rails/generators/active_record/model/templates/model.rb deleted file mode 100644 index 55dc65c8ad..0000000000 --- a/activerecord/lib/rails/generators/active_record/model/templates/model.rb +++ /dev/null @@ -1,13 +0,0 @@ -<% module_namespacing do -%> -class <%= class_name %> < <%= parent_class_name.classify %> -<% attributes.select(&:reference?).each do |attribute| -%> - belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %><%= ', required: true' if attribute.required? %> -<% end -%> -<% attributes.select(&:token?).each do |attribute| -%> - has_secure_token<% if attribute.name != "token" %> :<%= attribute.name %><% end %> -<% end -%> -<% if attributes.any?(&:password_digest?) -%> - has_secure_password -<% end -%> -end -<% end -%> diff --git a/activerecord/lib/rails/generators/active_record/model/templates/model.rb.tt b/activerecord/lib/rails/generators/active_record/model/templates/model.rb.tt new file mode 100644 index 0000000000..55dc65c8ad --- /dev/null +++ b/activerecord/lib/rails/generators/active_record/model/templates/model.rb.tt @@ -0,0 +1,13 @@ +<% module_namespacing do -%> +class <%= class_name %> < <%= parent_class_name.classify %> +<% attributes.select(&:reference?).each do |attribute| -%> + belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %><%= ', required: true' if attribute.required? %> +<% end -%> +<% attributes.select(&:token?).each do |attribute| -%> + has_secure_token<% if attribute.name != "token" %> :<%= attribute.name %><% end %> +<% end -%> +<% if attributes.any?(&:password_digest?) -%> + has_secure_password +<% end -%> +end +<% end -%> diff --git a/activerecord/lib/rails/generators/active_record/model/templates/module.rb b/activerecord/lib/rails/generators/active_record/model/templates/module.rb deleted file mode 100644 index a3bf1c37b6..0000000000 --- a/activerecord/lib/rails/generators/active_record/model/templates/module.rb +++ /dev/null @@ -1,7 +0,0 @@ -<% module_namespacing do -%> -module <%= class_path.map(&:camelize).join('::') %> - def self.table_name_prefix - '<%= namespaced? ? namespaced_class_path.join('_') : class_path.join('_') %>_' - end -end -<% end -%> diff --git a/activerecord/lib/rails/generators/active_record/model/templates/module.rb.tt b/activerecord/lib/rails/generators/active_record/model/templates/module.rb.tt new file mode 100644 index 0000000000..a3bf1c37b6 --- /dev/null +++ b/activerecord/lib/rails/generators/active_record/model/templates/module.rb.tt @@ -0,0 +1,7 @@ +<% module_namespacing do -%> +module <%= class_path.map(&:camelize).join('::') %> + def self.table_name_prefix + '<%= namespaced? ? namespaced_class_path.join('_') : class_path.join('_') %>_' + end +end +<% end -%> -- cgit v1.2.3 From 68fe6b08ee72cc47263e0d2c9ff07f75c4b42761 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 13 Nov 2017 13:24:28 -0700 Subject: Properly cast input in `update_all` The documentation claims that given values go through "normal AR type casting and serialization", which to me implies `serialize(cast(value))`, not just serialization. The docs were changed to use this wording in #22492. The tests I cited in that PR (which is the same test modified in this commit), is worded in a way that implies it should be using `cast` as well. It's possible that I originally meant "normal type casting" to imply just the call to `serialize`, but given that `update_all(archived: params['archived'])` seems to be pretty common, I'm inclined to make this change as long as no tests are broken from it. --- activerecord/CHANGELOG.md | 6 ++++++ activerecord/lib/active_record/sanitization.rb | 3 ++- activerecord/test/cases/relation_test.rb | 7 ++++++- 3 files changed, 14 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index c95e80755d..81ff2923ce 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,9 @@ +* `update_all` will now pass its values to `Type#cast` before passing them to + `Type#serialize`. This means that `update_all(foo: 'true')` will properly + persist a boolean. + + *Sean Griffin* + * Add new error class `StatementTimeout` which will be raised when statement timeout exceeded. diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 1c3099f55c..90cc3373fb 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -110,7 +110,8 @@ module ActiveRecord def sanitize_sql_hash_for_assignment(attrs, table) # :doc: c = connection attrs.map do |attr, value| - value = type_for_attribute(attr.to_s).serialize(value) + type = type_for_attribute(attr.to_s) + value = type.serialize(type.cast(value)) "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}" end.join(", ") end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 8362722e12..a71d8de521 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -288,13 +288,18 @@ module ActiveRecord :string end + def cast(value) + raise value unless value == "value from user" + "cast value" + end + def deserialize(value) raise value unless value == "type cast for database" "type cast from database" end def serialize(value) - raise value unless value == "value from user" + raise value unless value == "cast value" "type cast for database" end end -- cgit v1.2.3 From b6d5e46311d7ea59539c1f45c6ffb269eeb23912 Mon Sep 17 00:00:00 2001 From: Yuji Yaginuma Date: Tue, 14 Nov 2017 13:54:58 +0900 Subject: Add `environment` as dependency of `load_config` (#31135) Currently the environment is not loaded in some db tasks. Therefore, if use encrypted secrets values in `database.yml`, `read_encrypted_secrets` will not be true, so the value can not be used correctly. To fix this, added `environment` as dependency of `load_config`. It also removes explicit `environment` dependencies that are no longer needed. Fixes #30717 --- .../lib/active_record/railties/databases.rake | 48 +++++++++++----------- 1 file changed, 24 insertions(+), 24 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 3bca2982e0..fce3e1c5cf 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -4,16 +4,16 @@ require "active_record" db_namespace = namespace :db do desc "Set the environment value for the database" - task "environment:set" => [:environment, :load_config] do + task "environment:set" => :load_config do ActiveRecord::InternalMetadata.create_table ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment end - task check_protected_environments: [:environment, :load_config] do + task check_protected_environments: :load_config do ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! end - task :load_config do + task load_config: :environment do ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {} ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths end @@ -56,7 +56,7 @@ db_namespace = namespace :db do end desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)." - task migrate: [:environment, :load_config] do + task migrate: :load_config do ActiveRecord::Tasks::DatabaseTasks.migrate db_namespace["_dump"].invoke end @@ -78,7 +78,7 @@ db_namespace = namespace :db do namespace :migrate do # desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).' - task redo: [:environment, :load_config] do + task redo: :load_config do raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty? if ENV["VERSION"] @@ -94,7 +94,7 @@ db_namespace = namespace :db do task reset: ["db:drop", "db:create", "db:migrate"] # desc 'Runs the "up" for a given migration VERSION.' - task up: [:environment, :load_config] do + task up: :load_config do raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty? ActiveRecord::Tasks::DatabaseTasks.check_target_version @@ -108,7 +108,7 @@ db_namespace = namespace :db do end # desc 'Runs the "down" for a given migration VERSION.' - task down: [:environment, :load_config] do + task down: :load_config do raise "VERSION is required - To go down one migration, use db:rollback" if !ENV["VERSION"] || ENV["VERSION"].empty? ActiveRecord::Tasks::DatabaseTasks.check_target_version @@ -122,7 +122,7 @@ db_namespace = namespace :db do end desc "Display status of migrations" - task status: [:environment, :load_config] do + task status: :load_config do unless ActiveRecord::SchemaMigration.table_exists? abort "Schema migrations table does not exist yet." end @@ -140,14 +140,14 @@ db_namespace = namespace :db do end desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)." - task rollback: [:environment, :load_config] do + task rollback: :load_config do step = ENV["STEP"] ? ENV["STEP"].to_i : 1 ActiveRecord::Migrator.rollback(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step) db_namespace["_dump"].invoke end # desc 'Pushes the schema to the next version (specify steps w/ STEP=n).' - task forward: [:environment, :load_config] do + task forward: :load_config do step = ENV["STEP"] ? ENV["STEP"].to_i : 1 ActiveRecord::Migrator.forward(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step) db_namespace["_dump"].invoke @@ -157,12 +157,12 @@ db_namespace = namespace :db do task reset: [ "db:drop", "db:setup" ] # desc "Retrieves the charset for the current environment's database" - task charset: [:environment, :load_config] do + task charset: :load_config do puts ActiveRecord::Tasks::DatabaseTasks.charset_current end # desc "Retrieves the collation for the current environment's database" - task collation: [:environment, :load_config] do + task collation: :load_config do begin puts ActiveRecord::Tasks::DatabaseTasks.collation_current rescue NoMethodError @@ -171,12 +171,12 @@ db_namespace = namespace :db do end desc "Retrieves the current schema version number" - task version: [:environment, :load_config] do + task version: :load_config do puts "Current version: #{ActiveRecord::Migrator.current_version}" end # desc "Raises an error if there are pending migrations" - task abort_if_pending_migrations: [:environment, :load_config] do + task abort_if_pending_migrations: :load_config do pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Tasks::DatabaseTasks.migrations_paths).pending_migrations if pending_migrations.any? @@ -199,7 +199,7 @@ db_namespace = namespace :db do namespace :fixtures do desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." - task load: [:environment, :load_config] do + task load: :load_config do require "active_record/fixtures" base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path @@ -221,7 +221,7 @@ db_namespace = namespace :db do end # desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." - task identify: [:environment, :load_config] do + task identify: :load_config do require "active_record/fixtures" label, id = ENV["LABEL"], ENV["ID"] @@ -247,7 +247,7 @@ db_namespace = namespace :db do namespace :schema do desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record" - task dump: [:environment, :load_config] do + task dump: :load_config do require "active_record/schema_dumper" filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema.rb") File.open(filename, "w:utf-8") do |file| @@ -257,7 +257,7 @@ db_namespace = namespace :db do end desc "Loads a schema.rb file into the database" - task load: [:environment, :load_config, :check_protected_environments] do + task load: [:load_config, :check_protected_environments] do ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV["SCHEMA"]) end @@ -267,14 +267,14 @@ db_namespace = namespace :db do namespace :cache do desc "Creates a db/schema_cache.yml file." - task dump: [:environment, :load_config] do + task dump: :load_config do conn = ActiveRecord::Base.connection filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml") ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(conn, filename) end desc "Clears a db/schema_cache.yml file." - task clear: [:environment, :load_config] do + task clear: :load_config do filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml") rm_f filename, verbose: false end @@ -284,7 +284,7 @@ db_namespace = namespace :db do namespace :structure do desc "Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql" - task dump: [:environment, :load_config] do + task dump: :load_config do filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql") current_config = ActiveRecord::Tasks::DatabaseTasks.current_config ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename) @@ -299,7 +299,7 @@ db_namespace = namespace :db do end desc "Recreates the databases from the structure.sql file" - task load: [:environment, :load_config, :check_protected_environments] do + task load: [:load_config, :check_protected_environments] do ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV["SCHEMA"]) end @@ -338,12 +338,12 @@ db_namespace = namespace :db do end # desc "Empty the test database" - task purge: %w(environment load_config check_protected_environments) do + task purge: %w(load_config check_protected_environments) do ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations["test"] end # desc 'Load the test schema' - task prepare: %w(environment load_config) do + task prepare: :load_config do unless ActiveRecord::Base.configurations.blank? db_namespace["test:load"].invoke 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') 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 df82237a45e930a7ab53b95bee78a3f34c5b92fb Mon Sep 17 00:00:00 2001 From: Rich Date: Tue, 14 Nov 2017 19:24:00 +0000 Subject: Add a #populate method to migrations (#31082) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add a #populate method to migrations * Address rubocop issues * Rename to #up_only and use #execute in the examples intead of the model * Update CHANGELOG [Rich Daley & Rafael Mendonça França] --- activerecord/CHANGELOG.md | 5 +++++ activerecord/lib/active_record/migration.rb | 18 ++++++++++++++++ .../test/cases/invertible_migration_test.rb | 25 ++++++++++++++++++++++ 3 files changed, 48 insertions(+) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 57ec37c75b..2088b018d7 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,8 @@ +* Add `#only_up` to database migrations for code that is only relevant when + migrating up, e.g. populating a new column. + + *Rich Daley* + * Require raw SQL fragments to be explicitly marked when used in relation query methods. diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index c13efa9d70..67c8c9fc08 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -734,6 +734,24 @@ module ActiveRecord execute_block { yield helper } end + # Used to specify an operation that is only run when migrating up + # (for example, populating a new column with its initial values). + # + # In the following example, the new column `published` will be given + # the value `true` for all existing records. + # + # class AddPublishedToPosts < ActiveRecord::Migration[5.3] + # def change + # add_column :posts, :published, :boolean, default: false + # up_only do + # execute "update posts set published = 'true'" + # end + # end + # end + def up_only + execute_block { yield } unless reverting? + end + # Runs the given migration classes. # Last argument can specify options: # - :direction (default is :up) diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index 60c628511f..e0f40710eb 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -161,6 +161,13 @@ module ActiveRecord end end + class UpOnlyMigration < SilentMigration + def change + add_column :horses, :oldie, :boolean, default: false + up_only { execute "update horses set oldie = 'true'" } + end + end + setup do @verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false end @@ -378,5 +385,23 @@ module ActiveRecord "horses_index_named index should not exist" end end + + def test_up_only + InvertibleMigration.new.migrate(:up) + horse1 = Horse.create + # populates existing horses with oldie=true but new ones have default false + UpOnlyMigration.new.migrate(:up) + Horse.reset_column_information + horse1.reload + horse2 = Horse.create + + assert horse1.oldie? # created before migration + assert !horse2.oldie? # created after migration + + UpOnlyMigration.new.migrate(:down) # should be no error + connection = ActiveRecord::Base.connection + assert !connection.column_exists?(:horses, :oldie) + Horse.reset_column_information + end end end -- cgit v1.2.3 From 2e0fe5928f0d08f85b4796c85bd0b39f6be09079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 14 Nov 2017 14:11:29 -0500 Subject: Use released arel --- activerecord/activerecord.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 7ad06fe840..8e42a11df4 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -31,5 +31,5 @@ Gem::Specification.new do |s| s.add_dependency "activesupport", version s.add_dependency "activemodel", version - s.add_dependency "arel", "9.0.0.alpha" + s.add_dependency "arel", ">= 9.0" end -- cgit v1.2.3 From 46a2f93614ccf0d1628e6fc3c4666cee476d17c8 Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Tue, 14 Nov 2017 20:02:52 +0000 Subject: Fix migration version in doc of #up_only --- activerecord/lib/active_record/migration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 67c8c9fc08..360bf25a8c 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -740,7 +740,7 @@ module ActiveRecord # In the following example, the new column `published` will be given # the value `true` for all existing records. # - # class AddPublishedToPosts < ActiveRecord::Migration[5.3] + # class AddPublishedToPosts < ActiveRecord::Migration[5.2] # def change # add_column :posts, :published, :boolean, default: false # up_only do -- cgit v1.2.3 From ca68f1cf49a3689c276d502105aaa68fa106502d Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 15 Nov 2017 07:40:59 +0900 Subject: Fix typo s/only_up/up_only/ [ci skip] --- activerecord/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 2088b018d7..217eada1d7 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,4 +1,4 @@ -* Add `#only_up` to database migrations for code that is only relevant when +* Add `#up_only` to database migrations for code that is only relevant when migrating up, e.g. populating a new column. *Rich Daley* -- cgit v1.2.3 From 6a34ef52c9eda2bc9ef85325bfa0a43cb4afd724 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 15 Nov 2017 08:16:27 +0900 Subject: Fix CI failure due to invalid `up_only` for MySQL `oldie = 'true'` to `tinyint(1)` column is invalid value for MySQL: ``` Mysql2::Error: Incorrect integer value: 'true' for column 'oldie' at row 1: update horses set oldie = 'true' ``` --- activerecord/test/cases/invertible_migration_test.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index e0f40710eb..ebe0b0aa87 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -163,11 +163,13 @@ module ActiveRecord class UpOnlyMigration < SilentMigration def change - add_column :horses, :oldie, :boolean, default: false - up_only { execute "update horses set oldie = 'true'" } + add_column :horses, :oldie, :integer, default: 0 + up_only { execute "update horses set oldie = 1" } end end + self.use_transactional_tests = false + setup do @verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false end @@ -389,14 +391,14 @@ module ActiveRecord def test_up_only InvertibleMigration.new.migrate(:up) horse1 = Horse.create - # populates existing horses with oldie=true but new ones have default false + # populates existing horses with oldie = 1 but new ones have default 0 UpOnlyMigration.new.migrate(:up) Horse.reset_column_information horse1.reload horse2 = Horse.create - assert horse1.oldie? # created before migration - assert !horse2.oldie? # created after migration + assert 1, horse1.oldie # created before migration + assert 0, horse2.oldie # created after migration UpOnlyMigration.new.migrate(:down) # should be no error connection = ActiveRecord::Base.connection -- cgit v1.2.3 From ef0a3d0dfa1b0d7ce8234c5a03db6af64f8b40e0 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 15 Nov 2017 17:00:51 +0000 Subject: Allow test_ignored_columns_not_included_in_SELECT column names case insensitive i.e. Oracle database identifier is UPPERCASE unlike other databases. ```ruby (byebug) query = Developer.all.to_sql "SELECT \"DEVELOPERS\".\"ID\", \"DEVELOPERS\".\"NAME\", \"DEVELOPERS\".\"SALARY\", \"DEVELOPERS\".\"FIRM_ID\", \"DEVELOPERS\".\"MENTOR_ID\", \"DEVELOPERS\".\"CREATED_AT\", \"DEVELOPERS\".\"UPDATED_AT\", \"DEVELOPERS\".\"CREATED_ON\", \"DEVELOPERS\".\"UPDATED_ON\" FROM \"DEVELOPERS\"" ``` --- activerecord/test/cases/base_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 0ae88ee6a0..d79afa2ee9 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1489,7 +1489,7 @@ class BasicsTest < ActiveRecord::TestCase end test "ignored columns not included in SELECT" do - query = Developer.all.to_sql + query = Developer.all.to_sql.downcase # ignored column refute query.include?("first_name") -- cgit v1.2.3 From bbae710a405ce92074c1666dcf859196c29e2ed2 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 17 Nov 2017 03:12:57 +0900 Subject: Avoid creating extra `relation` and `build_arel` in `_create_record` and `_update_record` (#29999) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently `_create_record` and `_update_record` in `Persistence` are creating extra `unscoped` and calling `build_arel` in the relation. But `compile_insert` and `compile_update` can be done without those expensive operation for `SelectManager` creation. So I moved the implementation to `Persistence` to avoid creating extra relation and refactored to avoid calling `build_arel`. https://gist.github.com/kamipo/8ed73d760112cfa5f6263c9413633419 Before: ``` Warming up -------------------------------------- _update_record 150.000 i/100ms Calculating ------------------------------------- _update_record 1.548k (±12.3%) i/s - 7.650k in 5.042603s ``` After: ``` Warming up -------------------------------------- _update_record 201.000 i/100ms Calculating ------------------------------------- _update_record 2.002k (±12.8%) i/s - 9.849k in 5.027681s ``` 30% faster for STI classes. --- activerecord/lib/active_record/persistence.rb | 43 ++++++++++++++++++- activerecord/lib/active_record/relation.rb | 61 --------------------------- 2 files changed, 41 insertions(+), 63 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index a57c60ffac..4e1b05dbf6 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -165,6 +165,38 @@ module ActiveRecord where(primary_key => id_or_array).delete_all end + def _insert_record(values) # :nodoc: + primary_key_value = nil + + if primary_key && Hash === values + arel_primary_key = arel_attribute(primary_key) + primary_key_value = values[arel_primary_key] + + if !primary_key_value && prefetch_primary_key? + primary_key_value = next_sequence_value + values[arel_primary_key] = primary_key_value + end + end + + if values.empty? + im = arel_table.compile_insert(connection.empty_insert_statement_value) + im.into arel_table + else + im = arel_table.compile_insert(_substitute_values(values)) + end + + connection.insert(im, "#{self} Create", primary_key || false, primary_key_value) + end + + def _update_record(values, id, id_was) # :nodoc: + bind = predicate_builder.build_bind_attribute(primary_key, id_was || id) + um = arel_table.where( + arel_attribute(primary_key).eq(bind) + ).compile_update(_substitute_values(values), primary_key) + + connection.update(um, "#{self} Update") + end + private # Called by +instantiate+ to decide which class to use for a new # record instance. @@ -174,6 +206,13 @@ module ActiveRecord def discriminate_class_for_record(record) self end + + def _substitute_values(values) + values.map do |attr, value| + bind = predicate_builder.build_bind_attribute(attr.name, value) + [attr, bind] + end + end end # Returns true if this object hasn't been saved yet -- that is, a record @@ -671,7 +710,7 @@ module ActiveRecord rows_affected = 0 @_trigger_update_callback = true else - rows_affected = self.class.unscoped._update_record attributes_values, id, id_in_database + rows_affected = self.class._update_record(attributes_values, id, id_in_database) @_trigger_update_callback = rows_affected > 0 end @@ -685,7 +724,7 @@ module ActiveRecord def _create_record(attribute_names = self.attribute_names) attributes_values = arel_attributes_with_values_for_create(attribute_names) - new_id = self.class.unscoped.insert attributes_values + new_id = self.class._insert_record(attributes_values) self.id ||= new_id if self.class.primary_key @new_record = false diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index e2d2f45503..081ef5771f 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -36,67 +36,6 @@ module ActiveRecord reset end - def insert(values) # :nodoc: - primary_key_value = nil - - if primary_key && Hash === values - primary_key_value = values[values.keys.find { |k| - k.name == primary_key - }] - - if !primary_key_value && klass.prefetch_primary_key? - primary_key_value = klass.next_sequence_value - values[arel_attribute(klass.primary_key)] = primary_key_value - end - end - - im = arel.create_insert - im.into @table - - substitutes = substitute_values values - - if values.empty? # empty insert - im.values = Arel.sql(connection.empty_insert_statement_value) - else - im.insert substitutes - end - - @klass.connection.insert( - im, - "#{@klass} Create", - primary_key || false, - primary_key_value, - nil, - ) - end - - def _update_record(values, id, id_was) # :nodoc: - substitutes = substitute_values values - - scope = @klass.unscoped - - if @klass.finder_needs_type_condition? - scope.unscope!(where: @klass.inheritance_column) - end - - relation = scope.where(@klass.primary_key => (id_was || id)) - um = relation - .arel - .compile_update(substitutes, @klass.primary_key) - - @klass.connection.update( - um, - "#{@klass} Update", - ) - end - - def substitute_values(values) # :nodoc: - values.map do |arel_attr, value| - bind = predicate_builder.build_bind_attribute(arel_attr.name, value) - [arel_attr, bind] - end - end - def arel_attribute(name) # :nodoc: klass.arel_attribute(name, table) end -- cgit v1.2.3 From eeaf9cf61c3cd14929583878785c31dab79e2196 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 20 Nov 2017 06:24:50 +0900 Subject: Prevent extra `spawn` to make `klass.all` faster (#29009) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These extra `spawn` are called via `klass.all` and `klass.all` is called everywhere in the internal. Avoiding the extra `spawn` makes` klass.all` 30% faster for STI classes. https://gist.github.com/kamipo/684d03817a8115848cec8e8b079560b7 ``` Warming up -------------------------------------- fast relation 4.410k i/100ms slow relation 3.334k i/100ms Calculating ------------------------------------- fast relation 47.373k (± 5.2%) i/s - 238.140k in 5.041836s slow relation 35.757k (±15.9%) i/s - 176.702k in 5.104625s Comparison: fast relation: 47373.2 i/s slow relation: 35756.7 i/s - 1.32x slower ``` --- activerecord/lib/active_record/core.rb | 3 ++- activerecord/lib/active_record/scoping/default.rb | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index b97b14644e..481159e501 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -277,7 +277,8 @@ module ActiveRecord relation = Relation.create(self, arel_table, predicate_builder) if finder_needs_type_condition? && !ignore_default_scope? - relation.where(type_condition).create_with(inheritance_column.to_s => sti_name) + relation.where!(type_condition) + relation.create_with!(inheritance_column.to_s => sti_name) else relation end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 86ae374318..8c612df27a 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -111,7 +111,7 @@ module ActiveRecord # The user has defined their own default scope method, so call that evaluate_default_scope do if scope = default_scope - (base_rel ||= relation).merge(scope) + (base_rel ||= relation).merge!(scope) end end elsif default_scopes.any? @@ -119,7 +119,7 @@ module ActiveRecord evaluate_default_scope do default_scopes.inject(base_rel) do |default_scope, scope| scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call) - default_scope.merge(base_rel.instance_exec(&scope)) + default_scope.merge!(base_rel.instance_exec(&scope)) end end end -- cgit v1.2.3