diff options
Diffstat (limited to 'activerecord/test')
29 files changed, 284 insertions, 214 deletions
diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb index c32475c683..3756f74c95 100644 --- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb +++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb @@ -22,7 +22,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase CollationTest.validates_uniqueness_of(:string_ci_column, case_sensitive: false) CollationTest.create!(string_ci_column: "A") invalid = CollationTest.new(string_ci_column: "a") - queries = assert_sql { invalid.save } + queries = capture_sql { invalid.save } ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } assert_no_match(/lower/i, ci_uniqueness_query) end @@ -31,7 +31,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase CollationTest.validates_uniqueness_of(:string_cs_column, case_sensitive: false) CollationTest.create!(string_cs_column: "A") invalid = CollationTest.new(string_cs_column: "a") - queries = assert_sql { invalid.save } + queries = capture_sql { invalid.save } cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } assert_match(/lower/i, cs_uniqueness_query) end @@ -40,7 +40,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase CollationTest.validates_uniqueness_of(:string_ci_column, case_sensitive: true) CollationTest.create!(string_ci_column: "A") invalid = CollationTest.new(string_ci_column: "A") - queries = assert_sql { invalid.save } + queries = capture_sql { invalid.save } ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } assert_match(/binary/i, ci_uniqueness_query) end @@ -49,7 +49,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase CollationTest.validates_uniqueness_of(:string_cs_column, case_sensitive: true) CollationTest.create!(string_cs_column: "A") invalid = CollationTest.new(string_cs_column: "A") - queries = assert_sql { invalid.save } + queries = capture_sql { invalid.save } cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } assert_no_match(/binary/i, cs_uniqueness_query) end @@ -58,7 +58,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase CollationTest.validates_uniqueness_of(:binary_column, case_sensitive: true) CollationTest.create!(binary_column: "A") invalid = CollationTest.new(binary_column: "A") - queries = assert_sql { invalid.save } + queries = capture_sql { invalid.save } bin_uniqueness_query = queries.detect { |q| q.match(/binary_column/) } assert_no_match(/\bBINARY\b/, bin_uniqueness_query) end diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 210758f462..9494863a75 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -26,19 +26,19 @@ module ActiveRecord end def test_encoding - assert_queries(1) do + assert_queries(1, ignore_none: true) do assert_not_nil @connection.encoding end end def test_collation - assert_queries(1) do + assert_queries(1, ignore_none: true) do assert_not_nil @connection.collation end end def test_ctype - assert_queries(1) do + assert_queries(1, ignore_none: true) do assert_not_nil @connection.ctype end end diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb index ba477c63f4..96cfabf58f 100644 --- a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb +++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb @@ -13,7 +13,7 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase end module MissingSuperuserPrivileges - def execute(sql) + def execute(sql, name = nil) if IS_REFERENTIAL_INTEGRITY_SQL.call(sql) super "BROKEN;" rescue nil # put transaction in broken state raise ActiveRecord::StatementInvalid, "PG::InsufficientPrivilege" @@ -24,7 +24,7 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase end module ProgrammerMistake - def execute(sql) + def execute(sql, name = nil) if IS_REFERENTIAL_INTEGRITY_SQL.call(sql) raise ArgumentError, "something is not right." else diff --git a/activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb b/activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb deleted file mode 100644 index 93a7dafebd..0000000000 --- a/activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -require "cases/helper" -require "models/topic" - -module ActiveRecord - module ConnectionAdapters - class SQLite3Adapter - class BindParameterTest < ActiveRecord::SQLite3TestCase - def test_too_many_binds - topics = Topic.where(id: (1..999).to_a << 2**63) - assert_equal Topic.count, topics.count - - topics = Topic.where.not(id: (1..999).to_a << 2**63) - assert_equal 0, topics.count - end - end - end - end -end diff --git a/activerecord/test/cases/adapters/sqlite3/collation_test.rb b/activerecord/test/cases/adapters/sqlite3/collation_test.rb index 76c8f7d8dd..d938b5ff2f 100644 --- a/activerecord/test/cases/adapters/sqlite3/collation_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/collation_test.rb @@ -11,6 +11,10 @@ class SQLite3CollationTest < ActiveRecord::SQLite3TestCase @connection.create_table :collation_table_sqlite3, force: true do |t| t.string :string_nocase, collation: "NOCASE" t.text :text_rtrim, collation: "RTRIM" + # The decimal column might interfere with collation parsing. + # Thus, add this column type and some other string column afterwards. + t.decimal :decimal_col, precision: 6, scale: 2 + t.string :string_after_decimal_nocase, collation: "NOCASE" end end @@ -22,6 +26,11 @@ class SQLite3CollationTest < ActiveRecord::SQLite3TestCase column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == "string_nocase" } assert_equal :string, column.type assert_equal "NOCASE", column.collation + + # Verify collation of a column behind the decimal column as well. + column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == "string_after_decimal_nocase" } + assert_equal :string, column.type + assert_equal "NOCASE", column.collation end test "text column with collation" do diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 49f754be63..cbe48a374f 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -37,8 +37,8 @@ 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 }).order(:id).to_a - assert_equal 3, assert_no_queries { authors.size } - assert_equal 10, assert_no_queries { authors[0].comments.size } + assert_equal 3, assert_queries(0) { authors.size } + assert_equal 10, assert_queries(0) { authors[0].comments.size } end def test_eager_association_loading_grafts_stashed_associations_to_correct_parent @@ -103,14 +103,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase 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 } - assert_equal companies(:first_firm).account.firm.account, assert_no_queries { firms.first.account.firm.account } + assert_equal companies(:first_firm).account, assert_queries(0) { firms.first.account.firm.account } + assert_equal companies(:first_firm).account.firm.account, assert_queries(0) { firms.first.account.firm.account } end def test_eager_association_loading_with_has_many_sti 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_queries(0) do assert_equal first, topics[0].replies.size assert_equal second, topics[1].replies.size end @@ -131,13 +131,13 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase 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 } + assert_equal topics(:first), assert_queries(0) { 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 assert_equal authors(:david), author - assert_no_queries do + assert_queries(0) do author.posts.first.special_comments author.posts.first.very_special_comment end @@ -146,7 +146,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase 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 assert_equal [authors(:david)], authors - assert_no_queries do + assert_queries(0) do authors.first.posts.first.special_comments.first.post.special_comments authors.first.posts.first.special_comments.first.post.very_special_comment end @@ -155,14 +155,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase 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 assert_equal [authors(:bob), authors(:mary), authors(:david)], authors - assert_no_queries do + assert_queries(0) do authors[2].post_about_thinking.comments.first end end def test_preload_through_missing_records post = Post.where.not(author_id: Author.select(:id)).preload(author: { comments: :post }).first! - assert_no_queries { assert_nil post.author } + assert_queries(0) { assert_nil post.author } end def test_eager_association_loading_with_missing_first_record @@ -172,12 +172,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase 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 - assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first } + assert_equal vertices(:vertex_4), assert_queries(0) { 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 - assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first } + assert_equal vertices(:vertex_1), assert_queries(0) { sink.sources.first.sources.first.sources.first.sources.first } end def test_eager_association_loading_with_cascaded_interdependent_one_level_and_two_levels diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index b865002f03..38723b6c19 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -101,6 +101,17 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal [taggings(:normal_comment_rating)], rating.taggings_without_tag end + def test_loading_association_with_string_joins + rating = Rating.first + assert_equal [taggings(:normal_comment_rating)], rating.taggings_with_no_tag + + rating = Rating.preload(:taggings_with_no_tag).first + assert_equal [taggings(:normal_comment_rating)], rating.taggings_with_no_tag + + rating = Rating.eager_load(:taggings_with_no_tag).first + assert_equal [taggings(:normal_comment_rating)], rating.taggings_with_no_tag + end + def test_loading_with_scope_including_joins member = Member.first assert_equal members(:groucho), member @@ -512,7 +523,7 @@ class EagerAssociationTest < ActiveRecord::TestCase 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(Arel.sql(quoted_posts_id)) + Comment.includes(:post).references(:posts).order(quoted_posts_id) end end @@ -778,7 +789,6 @@ class EagerAssociationTest < ActiveRecord::TestCase .where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'") .references(:comments) .scoping do - posts = authors(:david).posts.limit(2).to_a assert_equal 2, posts.size end @@ -787,7 +797,6 @@ class EagerAssociationTest < ActiveRecord::TestCase .where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')") .references(:authors, :comments) .scoping do - count = Post.limit(2).count assert_equal count, posts.size end @@ -1234,7 +1243,7 @@ class EagerAssociationTest < ActiveRecord::TestCase 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 } + assert_equal posts(:welcome).comments.sort_by(&:id), assert_no_queries { posts[0].comments.sort_by(&:id) } end def test_eager_loading_with_conditions_on_join_model_preloads @@ -1246,8 +1255,8 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_preload_belongs_to_uses_exclusive_scope - people = Person.males.merge(includes: :primary_contact).to_a - assert_not_equal people.length, 0 + people = Person.males.includes(:primary_contact).to_a + assert_equal 2, people.length people.each do |person| assert_no_queries { assert_not_nil person.primary_contact } assert_equal Person.find(person.id).primary_contact, person.primary_contact @@ -1256,16 +1265,17 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_preload_has_many_uses_exclusive_scope people = Person.males.includes(:agents).to_a + assert_equal 2, people.length people.each do |person| - assert_equal Person.find(person.id).agents, person.agents + assert_equal Person.find(person.id).agents.sort_by(&:id), person.agents.sort_by(&:id) end end def test_preload_has_many_using_primary_key - expected = Firm.first.clients_using_primary_key.to_a + expected = Firm.first.clients_using_primary_key.sort_by(&:id) firm = Firm.includes(:clients_using_primary_key).first assert_no_queries do - assert_equal expected, firm.clients_using_primary_key + assert_equal expected, firm.clients_using_primary_key.sort_by(&:id) end end @@ -1516,6 +1526,24 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_match message, error.message end + test "preloading and eager loading of optional instance dependent associations is not supported" do + message = "association scope 'posts_mentioning_author' is" + error = assert_raises(ArgumentError) do + Author.includes(:posts_mentioning_author).to_a + end + assert_match message, error.message + + error = assert_raises(ArgumentError) do + Author.preload(:posts_mentioning_author).to_a + end + assert_match message, error.message + + error = assert_raises(ArgumentError) do + Author.eager_load(:posts_mentioning_author).to_a + end + assert_match message, error.message + end + test "preload with invalid argument" do exception = assert_raises(ArgumentError) do Author.preload(10).to_a diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index d93d787f7c..5e6e3e8ec4 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -70,8 +70,8 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase extend!(Developer) extend!(MyApplication::Business::Developer) - assert Object.const_get "DeveloperAssociationNameAssociationExtension" - assert MyApplication::Business.const_get "DeveloperAssociationNameAssociationExtension" + assert Developer.const_get "AssociationNameAssociationExtension" + assert MyApplication::Business::Developer.const_get "AssociationNameAssociationExtension" end def test_proxy_association_after_scoped 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 fe8bdd03ba..25cfa0a723 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 @@ -313,10 +313,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_build devel = Developer.find(1) - # Load schema information so we don't query below if running just this test. - Project.define_attribute_methods - - proj = assert_no_queries { devel.projects.build("name" => "Projekt") } + proj = assert_queries(0) { devel.projects.build("name" => "Projekt") } assert_not_predicate devel.projects, :loaded? assert_equal devel.projects.last, proj @@ -332,10 +329,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_new_aliased_to_build devel = Developer.find(1) - # Load schema information so we don't query below if running just this test. - Project.define_attribute_methods - - proj = assert_no_queries { devel.projects.new("name" => "Projekt") } + proj = assert_queries(0) { devel.projects.new("name" => "Projekt") } assert_not_predicate devel.projects, :loaded? assert_equal devel.projects.last, proj @@ -556,7 +550,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase developer = project.developers.first - assert_no_queries do + assert_queries(0) do assert_predicate project.developers, :loaded? assert_includes project.developers, developer end @@ -751,7 +745,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_get_ids_for_loaded_associations developer = developers(:david) developer.projects.reload - assert_no_queries do + assert_queries(0) do developer.project_ids developer.project_ids end @@ -879,7 +873,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_has_and_belongs_to_many_associations_on_new_records_use_null_relations projects = Developer.new.projects - assert_no_queries do + assert_queries(0) do assert_equal [], projects assert_equal [], projects.where(title: "omg") assert_equal [], projects.pluck(:title) diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 32285f269a..fe7e5dc559 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -468,10 +468,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase company = companies(:first_firm) new_clients = [] - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - - assert_no_queries do + assert_queries(0) do new_clients << company.clients_of_firm.build(name: "Another Client") new_clients << company.clients_of_firm.build(name: "Another Client II") new_clients << company.clients_of_firm.build(name: "Another Client III") @@ -492,10 +489,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase company = companies(:first_firm) new_clients = [] - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - - assert_no_queries do + assert_queries(0) do new_clients << company.clients_of_firm.build(name: "Another Client") new_clients << company.clients_of_firm.build(name: "Another Client II") new_clients << company.clients_of_firm.build(name: "Another Client III") @@ -1015,11 +1009,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_transactions_when_adding_to_new_record - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - firm = Firm.new - assert_no_queries do + assert_queries(0) do firm.clients_of_firm.concat(Client.new("name" => "Natural Company")) end end @@ -1034,10 +1025,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_new_aliased_to_build company = companies(:first_firm) - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - - new_client = assert_no_queries { company.clients_of_firm.new("name" => "Another Client") } + new_client = assert_queries(0) { company.clients_of_firm.new("name" => "Another Client") } assert_not_predicate company.clients_of_firm, :loaded? assert_equal "Another Client", new_client.name @@ -1048,10 +1036,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_build company = companies(:first_firm) - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - - new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") } + new_client = assert_queries(0) { company.clients_of_firm.build("name" => "Another Client") } assert_not_predicate company.clients_of_firm, :loaded? assert_equal "Another Client", new_client.name @@ -1109,10 +1094,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_build_many company = companies(:first_firm) - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - - new_clients = assert_no_queries { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) } + new_clients = assert_queries(0) { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) } assert_equal 2, new_clients.size end @@ -1127,10 +1109,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, first_topic.replies.length - # Load schema information so we don't query below if running just this test. - Reply.define_attribute_methods - - assert_no_queries do + assert_queries(0) do first_topic.replies.build(title: "Not saved", content: "Superstars") assert_equal 2, first_topic.replies.size end @@ -1141,10 +1120,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_build_via_block company = companies(:first_firm) - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - - new_client = assert_no_queries { company.clients_of_firm.build { |client| client.name = "Another Client" } } + new_client = assert_queries(0) { company.clients_of_firm.build { |client| client.name = "Another Client" } } assert_not_predicate company.clients_of_firm, :loaded? assert_equal "Another Client", new_client.name @@ -1155,10 +1131,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_build_many_via_block company = companies(:first_firm) - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - - new_clients = assert_no_queries do + new_clients = assert_queries(0) do company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) do |client| client.name = "changed" end @@ -1447,11 +1420,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_transaction_when_deleting_new_record - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - firm = Firm.new - assert_no_queries do + assert_queries(0) do client = Client.new("name" => "New Client") firm.clients_of_firm << client firm.clients_of_firm.destroy(client) @@ -1966,11 +1936,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_transactions_when_replacing_on_new_record - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - firm = Firm.new - assert_no_queries do + assert_queries(0) do firm.clients_of_firm = [Client.new("name" => "New Client")] end end @@ -2024,11 +1991,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_get_ids_for_association_on_new_record_does_not_try_to_find_records - # Load schema information so we don't query below if running just this test. - companies(:first_client).contract_ids - company = Company.new - assert_no_queries do + assert_queries(0) do company.contract_ids end @@ -2711,18 +2675,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase bulb = Bulb.create! tyre = Tyre.create! - car = Car.create! do |c| + car = Car.create!(name: "honda") do |c| c.bulbs << bulb c.tyres << tyre end + assert_equal [nil, "honda"], car.saved_change_to_name + assert_equal 1, car.bulbs.count assert_equal 1, car.tyres.count end test "associations replace in memory when records have the same id" do bulb = Bulb.create! - car = Car.create!(bulbs: [bulb]) + car = Car.create!(name: "honda", bulbs: [bulb]) + + assert_equal [nil, "honda"], car.saved_change_to_name new_bulb = Bulb.find(bulb.id) new_bulb.name = "foo" @@ -2733,7 +2701,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase test "in memory replacement executes no queries" do bulb = Bulb.create! - car = Car.create!(bulbs: [bulb]) + car = Car.create!(name: "honda", bulbs: [bulb]) + + assert_equal [nil, "honda"], car.saved_change_to_name new_bulb = Bulb.find(bulb.id) @@ -2765,7 +2735,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase test "in memory replacements sets inverse instance" do bulb = Bulb.create! - car = Car.create!(bulbs: [bulb]) + car = Car.create!(name: "honda", bulbs: [bulb]) + + assert_equal [nil, "honda"], car.saved_change_to_name new_bulb = Bulb.find(bulb.id) car.bulbs = [new_bulb] @@ -2785,7 +2757,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase test "in memory replacement maintains order" do first_bulb = Bulb.create! second_bulb = Bulb.create! - car = Car.create!(bulbs: [first_bulb, second_bulb]) + car = Car.create!(name: "honda", bulbs: [first_bulb, second_bulb]) + + assert_equal [nil, "honda"], car.saved_change_to_name same_bulb = Bulb.find(first_bulb.id) car.bulbs = [second_bulb, same_bulb] @@ -2958,6 +2932,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [], reference.ideal_jobs end + def test_has_many_preloading_with_duplicate_records + posts = Post.joins(:comments).preload(:comments).to_a + assert_equal [1, 2], posts.first.comments.map(&:id) + end + private def force_signal37_to_load_all_clients_of_firm 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 0ab99aa6cd..6faa9664f7 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -58,6 +58,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal preloaded, Marshal.load(Marshal.dump(preloaded)) end + def test_through_association_with_joins + assert_equal [comments(:eager_other_comment1)], authors(:mary).comments.merge(Post.joins(:comments)) + end + + def test_through_association_with_left_joins + assert_equal [comments(:eager_other_comment1)], authors(:mary).comments.merge(Post.left_joins(:comments)) + end + def test_preload_with_nested_association posts = Post.preload(:author, :author_favorites_with_scope).to_a @@ -299,10 +307,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_queries(1) { posts(:thinking) } new_person = nil # so block binding catches it - # Load schema information so we don't query below if running just this test. - Person.define_attribute_methods - - assert_no_queries do + assert_queries(0) do new_person = Person.new first_name: "bob" end @@ -322,10 +327,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_associate_new_by_building assert_queries(1) { posts(:thinking) } - # Load schema information so we don't query below if running just this test. - Person.define_attribute_methods - - assert_no_queries do + assert_queries(0) do posts(:thinking).people.build(first_name: "Bob") posts(:thinking).people.new(first_name: "Ted") end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index fd727757a3..3ef25c7027 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -256,11 +256,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_build_association_dont_create_transaction - # Load schema information so we don't query below if running just this test. - Account.define_attribute_methods - firm = Firm.new - assert_no_queries do + assert_queries(0) do firm.build_account end end diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb index 0a8863c35d..d44c6407f5 100644 --- a/activerecord/test/cases/associations/left_outer_join_association_test.rb +++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb @@ -32,6 +32,10 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase assert_equal 17, Post.left_outer_joins(:comments).count end + def test_merging_left_joins_should_be_left_joins + assert_equal 5, Author.left_joins(:posts).merge(Post.no_comments).count + end + def test_left_joins_aliases_left_outer_joins assert_equal Post.left_outer_joins(:comments).to_sql, Post.left_joins(:comments).to_sql end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 1a0732c14b..2fb5ca4152 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -644,10 +644,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa def test_build_before_save company = companies(:first_firm) - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - - new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") } + new_client = assert_queries(0) { company.clients_of_firm.build("name" => "Another Client") } assert_not_predicate company.clients_of_firm, :loaded? company.name += "-changed" @@ -659,10 +656,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa def test_build_many_before_save company = companies(:first_firm) - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - - assert_no_queries { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) } + assert_queries(0) { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) } company.name += "-changed" assert_queries(3) { assert company.save } @@ -672,10 +666,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa def test_build_via_block_before_save company = companies(:first_firm) - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - - new_client = assert_no_queries { company.clients_of_firm.build { |client| client.name = "Another Client" } } + new_client = assert_queries(0) { company.clients_of_firm.build { |client| client.name = "Another Client" } } assert_not_predicate company.clients_of_firm, :loaded? company.name += "-changed" @@ -687,10 +678,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa def test_build_many_via_block_before_save company = companies(:first_firm) - # Load schema information so we don't query below if running just this test. - Client.define_attribute_methods - - assert_no_queries do + assert_queries(0) do company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) do |client| client.name = "changed" end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 16c2a3661d..e42af3686e 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -185,7 +185,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_limit_is_kept return if current_adapter?(:OracleAdapter) - queries = assert_sql { Account.limit(1).count } + queries = capture_sql { Account.limit(1).count } assert_equal 1, queries.length assert_match(/LIMIT/, queries.first) end @@ -193,7 +193,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_offset_is_kept return if current_adapter?(:OracleAdapter) - queries = assert_sql { Account.offset(1).count } + queries = capture_sql { Account.offset(1).count } assert_equal 1, queries.length assert_match(/OFFSET/, queries.first) end @@ -201,14 +201,14 @@ class CalculationsTest < ActiveRecord::TestCase def test_limit_with_offset_is_kept return if current_adapter?(:OracleAdapter) - queries = assert_sql { Account.limit(1).offset(1).count } + queries = capture_sql { Account.limit(1).offset(1).count } assert_equal 1, queries.length assert_match(/LIMIT/, queries.first) assert_match(/OFFSET/, queries.first) end def test_no_limit_no_offset - queries = assert_sql { Account.count } + queries = capture_sql { Account.count } assert_equal 1, queries.length assert_no_match(/LIMIT/, queries.first) assert_no_match(/OFFSET/, queries.first) @@ -224,15 +224,12 @@ class CalculationsTest < ActiveRecord::TestCase end def test_apply_distinct_in_count - queries = assert_sql do + queries = capture_sql do Account.distinct.count Account.group(:firm_id).distinct.count end queries.each do |query| - # `table_alias_length` in `column_alias_for` would execute - # "SHOW max_identifier_length" statement in PostgreSQL adapter. - next if query == "SHOW max_identifier_length" assert_match %r{\ASELECT(?! DISTINCT) COUNT\(DISTINCT\b}, query end end @@ -464,7 +461,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_not_perform_joined_include_by_default assert_equal Account.count, Account.includes(:firm).count - queries = assert_sql { Account.includes(:firm).count } + queries = capture_sql { Account.includes(:firm).count } assert_no_match(/join/i, queries.last) end @@ -840,7 +837,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) + actual = Topic.joins(:replies).order(:id) .pluck("topics.title", "replies_topics.title") assert_equal expected, actual end @@ -860,28 +857,25 @@ class CalculationsTest < ActiveRecord::TestCase end def test_pluck_loaded_relation - Company.attribute_names # Load schema information so we don't query below companies = Company.order(:id).limit(3).load - assert_no_queries do + assert_queries(0) do assert_equal ["37signals", "Summit", "Microsoft"], companies.pluck(:name) end end def test_pluck_loaded_relation_multiple_columns - Company.attribute_names # Load schema information so we don't query below companies = Company.order(:id).limit(3).load - assert_no_queries do + assert_queries(0) do assert_equal [[1, "37signals"], [2, "Summit"], [3, "Microsoft"]], companies.pluck(:id, :name) end end def test_pluck_loaded_relation_sql_fragment - Company.attribute_names # Load schema information so we don't query below companies = Company.order(:name).limit(3).load - assert_queries 1 do + assert_queries(1) do assert_equal ["37signals", "Apex", "Ex Nihilo"], companies.pluck(Arel.sql("DISTINCT name")) end end diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb index 584e03d196..25e2f20676 100644 --- a/activerecord/test/cases/comment_test.rb +++ b/activerecord/test/cases/comment_test.rb @@ -14,6 +14,9 @@ if ActiveRecord::Base.connection.supports_comments? class BlankComment < ActiveRecord::Base end + class PkCommented < ActiveRecord::Base + end + setup do @connection = ActiveRecord::Base.connection @@ -35,8 +38,13 @@ if ActiveRecord::Base.connection.supports_comments? t.index :absent_comment end + @connection.create_table("pk_commenteds", comment: "Table comment", id: false, force: true) do |t| + t.integer :id, comment: "Primary key comment", primary_key: true + end + Commented.reset_column_information BlankComment.reset_column_information + PkCommented.reset_column_information end teardown do @@ -44,6 +52,11 @@ if ActiveRecord::Base.connection.supports_comments? @connection.drop_table "blank_comments", if_exists: true end + def test_default_primary_key_comment + column = Commented.columns_hash["id"] + assert_nil column.comment + end + def test_column_created_in_block column = Commented.columns_hash["name"] assert_equal :string, column.type @@ -164,5 +177,17 @@ if ActiveRecord::Base.connection.supports_comments? column = Commented.columns_hash["name"] assert_nil column.comment end + + def test_comment_on_primary_key + column = PkCommented.columns_hash["id"] + assert_equal "Primary key comment", column.comment + assert_equal "Table comment", @connection.table_comment("pk_commenteds") + end + + def test_schema_dump_with_primary_key_comment + output = dump_table_schema "pk_commenteds" + assert_match %r[create_table "pk_commenteds",.*\s+comment: "Table comment"], output + assert_no_match %r[create_table "pk_commenteds",.*\s+comment: "Primary key comment"], output + end end end diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 6282759a10..27589966af 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -367,11 +367,24 @@ module ActiveRecord assert_same klass2.connection, ActiveRecord::Base.connection end + class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + end + + class MyClass < ApplicationRecord + end + def test_connection_specification_name_should_fallback_to_parent klassA = Class.new(Base) klassB = Class.new(klassA) + klassC = Class.new(MyClass) assert_equal klassB.connection_specification_name, klassA.connection_specification_name + assert_equal klassC.connection_specification_name, klassA.connection_specification_name + + assert_equal "primary", klassA.connection_specification_name + assert_equal "primary", klassC.connection_specification_name + klassA.connection_specification_name = "readonly" assert_equal "readonly", klassB.connection_specification_name end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index a15ad9a45b..82f5f45a4c 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -507,7 +507,6 @@ module ActiveRecord pool.schema_cache = schema_cache pool.with_connection do |conn| - assert_not_same pool.schema_cache, conn.schema_cache assert_equal pool.schema_cache.size, conn.schema_cache.size assert_same pool.schema_cache.columns(:posts), conn.schema_cache.columns(:posts) end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 8e8ed494d9..255885b599 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -854,7 +854,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter? classname = ActiveRecord::Base.connection.class.name[/[^:]*$/] expected_query_count = { - "Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not + "Mysql2Adapter" => 1, # mysql2 supports creating two indexes using one statement "PostgreSQLAdapter" => 2, }.fetch(classname) { raise "need an expected query count for #{classname}" @@ -886,7 +886,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter? classname = ActiveRecord::Base.connection.class.name[/[^:]*$/] expected_query_count = { - "Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not + "Mysql2Adapter" => 1, # mysql2 supports dropping and creating two indexes using one statement "PostgreSQLAdapter" => 2, }.fetch(classname) { raise "need an expected query count for #{classname}" diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index 6c1e3e7fec..aad30ddea0 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -18,7 +18,7 @@ require "support/stubs/strong_parameters" module ActiveRecord class WhereTest < ActiveRecord::TestCase - fixtures :posts, :edges, :authors, :author_addresses, :binaries, :essays, :cars, :treasures, :price_estimates, :topics + fixtures :posts, :comments, :edges, :authors, :author_addresses, :binaries, :essays, :cars, :treasures, :price_estimates, :topics def test_in_clause_is_correctly_sliced assert_called(Author.connection, :in_clause_length, returns: 1) do @@ -27,6 +27,11 @@ module ActiveRecord end end + def test_type_casting_nested_joins + comment = comments(:eager_other_comment1) + assert_equal [comment], Comment.joins(post: :author).where(authors: { id: "2-foo" }) + end + def test_where_copies_bind_params author = authors(:david) posts = author.posts.where("posts.id != 1") diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 2417775ef1..732bca4bf5 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -337,21 +337,21 @@ class RelationTest < ActiveRecord::TestCase def test_reverse_order_with_nulls_first_or_last assert_raises(ActiveRecord::IrreversibleOrderError) do - Topic.order(Arel.sql("title NULLS FIRST")).reverse_order + Topic.order("title NULLS FIRST").reverse_order end assert_raises(ActiveRecord::IrreversibleOrderError) do - Topic.order(Arel.sql("title NULLS FIRST")).reverse_order + Topic.order("title NULLS FIRST").reverse_order end assert_raises(ActiveRecord::IrreversibleOrderError) do - Topic.order(Arel.sql("title nulls last")).reverse_order + Topic.order("title nulls last").reverse_order end assert_raises(ActiveRecord::IrreversibleOrderError) do - Topic.order(Arel.sql("title NULLS FIRST, author_name")).reverse_order + Topic.order("title NULLS FIRST, author_name").reverse_order end assert_raises(ActiveRecord::IrreversibleOrderError) do - Topic.order(Arel.sql("author_name, title nulls last")).reverse_order + Topic.order("author_name, title nulls last").reverse_order end - end + end if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) def test_default_reverse_order_on_table_without_primary_key assert_raises(ActiveRecord::IrreversibleOrderError) do @@ -706,7 +706,7 @@ class RelationTest < ActiveRecord::TestCase end def test_to_sql_on_eager_join - expected = assert_sql { + expected = capture_sql { Post.eager_load(:last_comment).order("comments.id DESC").to_a }.first actual = Post.eager_load(:last_comment).order("comments.id DESC").to_sql @@ -1679,7 +1679,7 @@ class RelationTest < ActiveRecord::TestCase scope = Post.order("comments.body") assert_equal ["comments"], scope.references_values - scope = Post.order(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")) + scope = Post.order("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}") if current_adapter?(:OracleAdapter) assert_equal ["COMMENTS"], scope.references_values else @@ -1704,7 +1704,7 @@ class RelationTest < ActiveRecord::TestCase 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}")) + scope = Post.reorder("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}") if current_adapter?(:OracleAdapter) assert_equal ["COMMENTS"], scope.references_values else diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index 5b25432dc0..1b8bad32a4 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -34,7 +34,7 @@ module ActiveRecord ActiveRecord::Base.connection.materialize_transactions SQLCounter.clear_log yield - SQLCounter.log_all.dup + SQLCounter.log.dup end def assert_sql(*patterns_to_match) @@ -107,32 +107,12 @@ module ActiveRecord clear_log - self.ignored_sql = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/] - - # FIXME: this needs to be refactored so specific database can add their own - # ignored SQL, or better yet, use a different notification for the queries - # instead examining the SQL content. - oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im, /^\s*select .* from all_sequences/im] - mysql_ignored = [/^SHOW FULL TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /, /^\s*SELECT (?:column_name|table_name)\b.*\bFROM information_schema\.(?:key_column_usage|tables)\b/im] - postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i, /^\s*SELECT\b.*::regtype::oid\b/im] - sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im] - - [oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql| - ignored_sql.concat db_ignored_sql - end - - attr_reader :ignore - - def initialize(ignore = Regexp.union(self.class.ignored_sql)) - @ignore = ignore - end - def call(name, start, finish, message_id, values) return if values[:cached] sql = values[:sql] self.class.log_all << sql - self.class.log << sql unless ignore.match?(sql) + self.class.log << sql unless ["SCHEMA", "TRANSACTION"].include? values[:name] end end diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 53fe31e087..100bd6a925 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -36,6 +36,8 @@ class TransactionCallbacksTest < ActiveRecord::TestCase has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id" + before_destroy { self.class.find(id).touch if persisted? } + before_commit { |record| record.do_before_commit(nil) } after_commit { |record| record.do_after_commit(nil) } after_save_commit { |record| record.do_after_commit(:save) } @@ -549,6 +551,8 @@ class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase end class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase + self.use_transactional_tests = false + class TopicWithHistory < ActiveRecord::Base self.table_name = :topics @@ -562,11 +566,22 @@ class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase end class TopicWithCallbacksOnDestroy < TopicWithHistory - after_commit(on: :destroy) { |record| record.class.history << :destroy } + after_commit(on: :destroy) { |record| record.class.history << :commit_on_destroy } + after_rollback(on: :destroy) { |record| record.class.history << :rollback_on_destroy } + + before_destroy :before_destroy_for_transaction + + private + def before_destroy_for_transaction; end end class TopicWithCallbacksOnUpdate < TopicWithHistory - after_commit(on: :update) { |record| record.class.history << :update } + after_commit(on: :update) { |record| record.class.history << :commit_on_update } + + before_save :before_save_for_transaction + + private + def before_save_for_transaction; end end def test_trigger_once_on_multiple_deletions @@ -574,10 +589,39 @@ class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase topic = TopicWithCallbacksOnDestroy.new topic.save topic_clone = TopicWithCallbacksOnDestroy.find(topic.id) + + topic.define_singleton_method(:before_destroy_for_transaction) do + topic_clone.destroy + end + topic.destroy - topic_clone.destroy - assert_equal [:destroy], TopicWithCallbacksOnDestroy.history + assert_equal [:commit_on_destroy], TopicWithCallbacksOnDestroy.history + end + + def test_rollback_on_multiple_deletions + TopicWithCallbacksOnDestroy.clear_history + topic = TopicWithCallbacksOnDestroy.new + topic.save + topic_clone = TopicWithCallbacksOnDestroy.find(topic.id) + + topic.define_singleton_method(:before_destroy_for_transaction) do + topic_clone.update!(author_name: "Test Author Clone") + topic_clone.destroy + end + + TopicWithCallbacksOnDestroy.transaction do + topic.update!(author_name: "Test Author") + topic.destroy + raise ActiveRecord::Rollback + end + + assert_not_predicate topic, :destroyed? + assert_not_predicate topic_clone, :destroyed? + assert_equal [nil, "Test Author"], topic.author_name_change_to_be_saved + assert_equal [nil, "Test Author Clone"], topic_clone.author_name_change_to_be_saved + + assert_equal [:rollback_on_destroy], TopicWithCallbacksOnDestroy.history end def test_trigger_on_update_where_row_was_deleted @@ -585,7 +629,11 @@ class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase topic = TopicWithCallbacksOnUpdate.new topic.save topic_clone = TopicWithCallbacksOnUpdate.find(topic.id) - topic.destroy + + topic_clone.define_singleton_method(:before_save_for_transaction) do + topic.destroy + end + topic_clone.author_name = "Test Author" topic_clone.save diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb index d5d8f2a09a..fc92bf73c9 100644 --- a/activerecord/test/cases/unsafe_raw_sql_test.rb +++ b/activerecord/test/cases/unsafe_raw_sql_test.rb @@ -77,7 +77,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase assert_equal ids_expected, ids_disabled end - test "order: allows table and column name" do + test "order: allows table and column names" do ids_expected = Post.order(Arel.sql("title")).pluck(:id) ids_depr = with_unsafe_raw_sql_deprecated { Post.order("posts.title").pluck(:id) } @@ -87,6 +87,17 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase assert_equal ids_expected, ids_disabled end + test "order: allows quoted table and column names" do + ids_expected = Post.order(Arel.sql("title")).pluck(:id) + + quoted_title = Post.connection.quote_table_name("posts.title") + ids_depr = with_unsafe_raw_sql_deprecated { Post.order(quoted_title).pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order(quoted_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) @@ -116,10 +127,10 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase ["asc", "desc", ""].each do |direction| %w(first last).each do |position| - ids_expected = Post.order(Arel.sql("type #{direction} nulls #{position}")).pluck(:id) + ids_expected = Post.order(Arel.sql("type::text #{direction} nulls #{position}")).pluck(:id) - ids_depr = with_unsafe_raw_sql_deprecated { Post.order("type #{direction} nulls #{position}").pluck(:id) } - ids_disabled = with_unsafe_raw_sql_disabled { Post.order("type #{direction} nulls #{position}").pluck(:id) } + ids_depr = with_unsafe_raw_sql_deprecated { Post.order("type::text #{direction} nulls #{position}").pluck(:id) } + ids_disabled = with_unsafe_raw_sql_disabled { Post.order("type::text #{direction} nulls #{position}").pluck(:id) } assert_equal ids_expected, ids_depr assert_equal ids_expected, ids_disabled @@ -262,6 +273,17 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase assert_equal titles_expected, titles_disabled end + test "pluck: allows quoted table and column names" do + titles_expected = Post.pluck(Arel.sql("title")) + + quoted_title = Post.connection.quote_table_name("posts.title") + titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck(quoted_title) } + titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck(quoted_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 67be59a1fe..b52b643ad7 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -153,6 +153,7 @@ class Author < ActiveRecord::Base has_many :comments_on_posts_with_default_include, through: :posts_with_default_include, source: :comments has_many :posts_with_signature, ->(record) { where("posts.title LIKE ?", "%by #{record.name.downcase}%") }, class_name: "Post" + has_many :posts_mentioning_author, ->(record = nil) { where("posts.body LIKE ?", "%#{record&.name&.downcase}%") }, class_name: "Post" has_many :posts_with_extension, -> { order(:title) }, class_name: "Post" do def extension_method; end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index c6574cf6e7..92d01ba338 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -2,13 +2,13 @@ require "ostruct" -module DeveloperProjectsAssociationExtension2 - def find_least_recent - order("id ASC").first +class Developer < ActiveRecord::Base + module ProjectsAssociationExtension2 + def find_least_recent + order("id ASC").first + end end -end -class Developer < ActiveRecord::Base self.ignored_columns = %w(first_name last_name) has_and_belongs_to_many :projects do @@ -24,19 +24,19 @@ class Developer < ActiveRecord::Base has_and_belongs_to_many :shared_computers, class_name: "Computer" has_and_belongs_to_many :projects_extended_by_name, - -> { extending(DeveloperProjectsAssociationExtension) }, + -> { extending(ProjectsAssociationExtension) }, class_name: "Project", join_table: "developers_projects", association_foreign_key: "project_id" has_and_belongs_to_many :projects_extended_by_name_twice, - -> { extending(DeveloperProjectsAssociationExtension, DeveloperProjectsAssociationExtension2) }, + -> { extending(ProjectsAssociationExtension, ProjectsAssociationExtension2) }, class_name: "Project", join_table: "developers_projects", association_foreign_key: "project_id" has_and_belongs_to_many :projects_extended_by_name_and_block, - -> { extending(DeveloperProjectsAssociationExtension) }, + -> { extending(ProjectsAssociationExtension) }, class_name: "Project", join_table: "developers_projects", association_foreign_key: "project_id" do diff --git a/activerecord/test/models/face.rb b/activerecord/test/models/face.rb index e900fd40fb..45ccc442ba 100644 --- a/activerecord/test/models/face.rb +++ b/activerecord/test/models/face.rb @@ -6,7 +6,7 @@ class Face < ActiveRecord::Base belongs_to :polymorphic_man, polymorphic: true, inverse_of: :polymorphic_face # Oracle identifier length is limited to 30 bytes or less, `polymorphic` renamed `poly` belongs_to :poly_man_without_inverse, polymorphic: true - # These is a "broken" inverse_of for the purposes of testing + # These are "broken" inverse_of associations for the purposes of testing belongs_to :horrible_man, class_name: "Man", inverse_of: :horrible_face belongs_to :horrible_polymorphic_man, polymorphic: true, inverse_of: :horrible_polymorphic_face diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 090b576a5d..50c0dddcf2 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -43,6 +43,7 @@ class Post < ActiveRecord::Base has_one :first_comment, -> { order("id ASC") }, class_name: "Comment" has_one :last_comment, -> { order("id desc") }, class_name: "Comment" + scope :no_comments, -> { left_joins(:comments).where(comments: { id: nil }) } scope :with_special_comments, -> { joins(:comments).where(comments: { type: "SpecialComment" }) } scope :with_very_special_comments, -> { joins(:comments).where(comments: { type: "VerySpecialComment" }) } scope :with_post, ->(post_id) { joins(:comments).where(comments: { post_id: post_id }) } diff --git a/activerecord/test/models/rating.rb b/activerecord/test/models/rating.rb index 49aa38285f..2a18ea45ac 100644 --- a/activerecord/test/models/rating.rb +++ b/activerecord/test/models/rating.rb @@ -4,4 +4,5 @@ class Rating < ActiveRecord::Base belongs_to :comment has_many :taggings, as: :taggable has_many :taggings_without_tag, -> { left_joins(:tag).where("tags.id": nil) }, as: :taggable, class_name: "Tagging" + has_many :taggings_with_no_tag, -> { joins("LEFT OUTER JOIN tags ON tags.id = taggings.tag_id").where("tags.id": nil) }, as: :taggable, class_name: "Tagging" end |