aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/test')
-rw-r--r--activerecord/test/cases/adapter_test.rb88
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb24
-rw-r--r--activerecord/test/cases/adapters/mysql2/annotate_test.rb37
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb11
-rw-r--r--activerecord/test/cases/adapters/mysql2/count_deleted_rows_with_lock_test.rb28
-rw-r--r--activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb3
-rw-r--r--activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb93
-rw-r--r--activerecord/test/cases/adapters/mysql2/optimizer_hints_test.rb48
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/sp_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/annotate_test.rb37
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb14
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/extension_migration_test.rb13
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb25
-rw-r--r--activerecord/test/cases/adapters/postgresql/optimizer_hints_test.rb52
-rw-r--r--activerecord/test/cases/adapters/postgresql/partitions_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb16
-rw-r--r--activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb20
-rw-r--r--activerecord/test/cases/adapters/sqlite3/annotate_test.rb37
-rw-r--r--activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb20
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb10
-rw-r--r--activerecord/test/cases/ar_schema_test.rb17
-rw-r--r--activerecord/test/cases/arel/insert_manager_test.rb19
-rw-r--r--activerecord/test/cases/arel/nodes/comment_test.rb22
-rw-r--r--activerecord/test/cases/arel/nodes/select_core_test.rb8
-rw-r--r--activerecord/test/cases/arel/select_manager_test.rb23
-rw-r--r--activerecord/test/cases/arel/support/fake_record.rb8
-rw-r--r--activerecord/test/cases/arel/visitors/depth_first_test.rb8
-rw-r--r--activerecord/test/cases/arel/visitors/dot_test.rb2
-rw-r--r--activerecord/test/cases/arel/visitors/to_sql_test.rb14
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb9
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb8
-rw-r--r--activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb2
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb4
-rw-r--r--activerecord/test/cases/associations/eager_test.rb70
-rw-r--r--activerecord/test/cases/associations/extension_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb74
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb59
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb39
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb5
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb9
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb14
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb15
-rw-r--r--activerecord/test/cases/associations_test.rb99
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb75
-rw-r--r--activerecord/test/cases/attributes_test.rb11
-rw-r--r--activerecord/test/cases/autosave_association_test.rb39
-rw-r--r--activerecord/test/cases/base_test.rb49
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb103
-rw-r--r--activerecord/test/cases/boolean_test.rb9
-rw-r--r--activerecord/test/cases/calculations_test.rb80
-rw-r--r--activerecord/test/cases/callbacks_test.rb4
-rw-r--r--activerecord/test/cases/collection_cache_key_test.rb34
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb43
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb10
-rw-r--r--activerecord/test/cases/connection_adapters/schema_cache_test.rb69
-rw-r--r--activerecord/test/cases/database_configurations_test.rb117
-rw-r--r--activerecord/test/cases/database_selector_test.rb4
-rw-r--r--activerecord/test/cases/date_test.rb14
-rw-r--r--activerecord/test/cases/date_time_precision_test.rb24
-rw-r--r--activerecord/test/cases/defaults_test.rb2
-rw-r--r--activerecord/test/cases/dirty_test.rb2
-rw-r--r--activerecord/test/cases/enum_test.rb5
-rw-r--r--activerecord/test/cases/finder_test.rb17
-rw-r--r--activerecord/test/cases/fixtures_test.rb8
-rw-r--r--activerecord/test/cases/forbidden_attributes_protection_test.rb38
-rw-r--r--activerecord/test/cases/helper.rb18
-rw-r--r--activerecord/test/cases/inheritance_test.rb3
-rw-r--r--activerecord/test/cases/insert_all_test.rb276
-rw-r--r--activerecord/test/cases/instrumentation_test.rb8
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb61
-rw-r--r--activerecord/test/cases/legacy_configurations_test.rb43
-rw-r--r--activerecord/test/cases/locking_test.rb11
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb6
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb8
-rw-r--r--activerecord/test/cases/migration/columns_test.rb25
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb34
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb33
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb178
-rw-r--r--activerecord/test/cases/migration/index_test.rb13
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb31
-rw-r--r--activerecord/test/cases/migration_test.rb69
-rw-r--r--activerecord/test/cases/persistence_test.rb4
-rw-r--r--activerecord/test/cases/primary_keys_test.rb8
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb30
-rw-r--r--activerecord/test/cases/relation/delete_all_test.rb36
-rw-r--r--activerecord/test/cases/relation/merging_test.rb12
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb4
-rw-r--r--activerecord/test/cases/relation/select_test.rb12
-rw-r--r--activerecord/test/cases/relation/update_all_test.rb103
-rw-r--r--activerecord/test/cases/relation/where_clause_test.rb2
-rw-r--r--activerecord/test/cases/relation/where_test.rb93
-rw-r--r--activerecord/test/cases/relation_test.rb56
-rw-r--r--activerecord/test/cases/relations_test.rb200
-rw-r--r--activerecord/test/cases/sanitize_test.rb13
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb10
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb19
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb66
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb34
-rw-r--r--activerecord/test/cases/store_test.rb68
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb182
-rw-r--r--activerecord/test/cases/test_case.rb22
-rw-r--r--activerecord/test/cases/time_precision_test.rb22
-rw-r--r--activerecord/test/cases/timestamp_test.rb14
-rw-r--r--activerecord/test/cases/touch_later_test.rb2
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb102
-rw-r--r--activerecord/test/cases/transactions_test.rb160
-rw-r--r--activerecord/test/cases/unconnected_test.rb6
-rw-r--r--activerecord/test/cases/validations/i18n_generate_message_validation_test.rb20
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb12
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb47
-rw-r--r--activerecord/test/cases/validations_test.rb12
-rw-r--r--activerecord/test/models/author.rb10
-rw-r--r--activerecord/test/models/bird.rb4
-rw-r--r--activerecord/test/models/category.rb1
-rw-r--r--activerecord/test/models/club.rb4
-rw-r--r--activerecord/test/models/company.rb2
-rw-r--r--activerecord/test/models/contract.rb8
-rw-r--r--activerecord/test/models/developer.rb17
-rw-r--r--activerecord/test/models/pirate.rb16
-rw-r--r--activerecord/test/models/post.rb25
-rw-r--r--activerecord/test/models/rating.rb1
-rw-r--r--activerecord/test/models/reply.rb14
-rw-r--r--activerecord/test/models/section.rb6
-rw-r--r--activerecord/test/models/seminar.rb6
-rw-r--r--activerecord/test/models/session.rb6
-rw-r--r--activerecord/test/models/topic.rb9
-rw-r--r--activerecord/test/schema/schema.rb68
-rw-r--r--activerecord/test/support/stubs/strong_parameters.rb21
134 files changed, 3508 insertions, 791 deletions
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 05d8aa59c4..ce2ed06c1d 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -348,6 +348,10 @@ module ActiveRecord
assert_equal "special_db_type", @connection.type_to_sql(:special_db_type)
end
+ def test_supports_foreign_keys_in_create_is_deprecated
+ assert_deprecated { @connection.supports_foreign_keys_in_create? }
+ end
+
def test_supports_multi_insert_is_deprecated
assert_deprecated { @connection.supports_multi_insert? }
end
@@ -448,19 +452,21 @@ module ActiveRecord
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
self.use_transactional_tests = false
- class Klass < ActiveRecord::Base
- end
+ fixtures :posts, :authors, :author_addresses
def setup
- Klass.establish_connection :arunit
- @connection = Klass.connection
- end
-
- teardown do
- Klass.remove_connection
+ @connection = ActiveRecord::Base.connection
end
unless in_memory_db?
+ test "reconnect after a disconnect" do
+ assert_predicate @connection, :active?
+ @connection.disconnect!
+ assert_not_predicate @connection, :active?
+ @connection.reconnect!
+ assert_predicate @connection, :active?
+ end
+
test "transaction state is reset after a reconnect" do
@connection.begin_transaction
assert_predicate @connection, :transaction_open?
@@ -473,9 +479,65 @@ module ActiveRecord
assert_predicate @connection, :transaction_open?
@connection.disconnect!
assert_not_predicate @connection, :transaction_open?
+ ensure
+ @connection.reconnect!
end
end
+ def test_truncate
+ assert_operator Post.count, :>, 0
+
+ @connection.truncate("posts")
+
+ assert_equal 0, Post.count
+ ensure
+ reset_fixtures("posts")
+ end
+
+ def test_truncate_with_query_cache
+ @connection.enable_query_cache!
+
+ assert_operator Post.count, :>, 0
+
+ @connection.truncate("posts")
+
+ assert_equal 0, Post.count
+ ensure
+ reset_fixtures("posts")
+ @connection.disable_query_cache!
+ end
+
+ def test_truncate_tables
+ assert_operator Post.count, :>, 0
+ assert_operator Author.count, :>, 0
+ assert_operator AuthorAddress.count, :>, 0
+
+ @connection.truncate_tables("author_addresses", "authors", "posts")
+
+ assert_equal 0, Post.count
+ assert_equal 0, Author.count
+ assert_equal 0, AuthorAddress.count
+ ensure
+ reset_fixtures("posts", "authors", "author_addresses")
+ end
+
+ def test_truncate_tables_with_query_cache
+ @connection.enable_query_cache!
+
+ assert_operator Post.count, :>, 0
+ assert_operator Author.count, :>, 0
+ assert_operator AuthorAddress.count, :>, 0
+
+ @connection.truncate_tables("author_addresses", "authors", "posts")
+
+ assert_equal 0, Post.count
+ assert_equal 0, Author.count
+ assert_equal 0, AuthorAddress.count
+ ensure
+ reset_fixtures("posts", "authors", "author_addresses")
+ @connection.disable_query_cache!
+ end
+
# test resetting sequences in odd tables in PostgreSQL
if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
require "models/movie"
@@ -495,6 +557,16 @@ module ActiveRecord
assert_nothing_raised { sub.save! }
end
end
+
+ private
+
+ def reset_fixtures(*fixture_names)
+ ActiveRecord::FixtureSet.reset_cache
+
+ fixture_names.each do |fixture_name|
+ ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, fixture_name)
+ end
+ end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 88c2ac5d0a..c2c357d0c1 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -10,7 +10,15 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
ActiveRecord::Base.connection.send(:default_row_format)
ActiveRecord::Base.connection.singleton_class.class_eval do
alias_method :execute_without_stub, :execute
- def execute(sql, name = nil) sql end
+ def execute(sql, name = nil)
+ ActiveSupport::Notifications.instrumenter.instrument(
+ "sql.active_record",
+ sql: sql,
+ name: name,
+ connection: self) do
+ sql
+ end
+ end
end
end
@@ -89,17 +97,19 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
expected = "ALTER TABLE `people` ADD #{type} INDEX `index_people_on_last_name` (`last_name`)"
- actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
- t.index :last_name, type: type
+ assert_sql(expected) do
+ ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
+ t.index :last_name, type: type
+ end
end
- assert_equal expected, actual
end
expected = "ALTER TABLE `people` ADD INDEX `index_people_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
- actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
- t.index :last_name, length: 10, using: :btree, algorithm: :copy
+ assert_sql(expected) do
+ ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
+ t.index :last_name, length: 10, using: :btree, algorithm: :copy
+ end
end
- assert_equal expected, actual
end
def test_drop_table
diff --git a/activerecord/test/cases/adapters/mysql2/annotate_test.rb b/activerecord/test/cases/adapters/mysql2/annotate_test.rb
new file mode 100644
index 0000000000..b512540073
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/annotate_test.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/post"
+
+class Mysql2AnnotateTest < ActiveRecord::Mysql2TestCase
+ fixtures :posts
+
+ def test_annotate_wraps_content_in_an_inline_comment
+ assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/}) do
+ posts = Post.select(:id).annotate("foo")
+ assert posts.first
+ end
+ end
+
+ def test_annotate_is_sanitized
+ assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/}) do
+ posts = Post.select(:id).annotate("*/foo/*")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/}) do
+ posts = Post.select(:id).annotate("**//foo//**")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/ /\* bar \*/}) do
+ posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* \+ MAX_EXECUTION_TIME\(1\) \*/}) do
+ posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
+ assert posts.first
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 3103589186..9c6566106a 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -28,17 +28,6 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
end
- def test_truncate
- rows = ActiveRecord::Base.connection.exec_query("select count(*) from comments")
- count = rows.first.values.first
- assert_operator count, :>, 0
-
- ActiveRecord::Base.connection.truncate("comments")
- rows = ActiveRecord::Base.connection.exec_query("select count(*) from comments")
- count = rows.first.values.first
- assert_equal 0, count
- end
-
def test_no_automatic_reconnection_after_timeout
assert_predicate @connection, :active?
@connection.update("set @@wait_timeout=1")
diff --git a/activerecord/test/cases/adapters/mysql2/count_deleted_rows_with_lock_test.rb b/activerecord/test/cases/adapters/mysql2/count_deleted_rows_with_lock_test.rb
new file mode 100644
index 0000000000..4d361e405c
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/count_deleted_rows_with_lock_test.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "support/connection_helper"
+require "models/author"
+require "models/bulb"
+
+module ActiveRecord
+ class CountDeletedRowsWithLockTest < ActiveRecord::Mysql2TestCase
+ test "delete and create in different threads synchronize correctly" do
+ Bulb.unscoped.delete_all
+ Bulb.create!(name: "Jimmy", color: "blue")
+
+ delete_thread = Thread.new do
+ Bulb.unscoped.delete_all
+ end
+
+ create_thread = Thread.new do
+ Author.create!(name: "Tommy")
+ end
+
+ delete_thread.join
+ create_thread.join
+
+ assert_equal 1, delete_thread.value
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
index 00a075e063..cbe55f1d53 100644
--- a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
@@ -46,10 +46,7 @@ class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase
def stub_version(full_version_string)
@connection.stub(:full_version, full_version_string) do
- @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
yield
end
- ensure
- @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
index 7bc86476b6..6ade2eec24 100644
--- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
@@ -56,7 +56,7 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
@conn.columns_for_distinct("posts.id", [order])
end
- def test_errors_for_bigint_fks_on_integer_pk_table
+ def test_errors_for_bigint_fks_on_integer_pk_table_in_alter_table
# table old_cars has primary key of integer
error = assert_raises(ActiveRecord::MismatchedForeignKey) do
@@ -64,9 +64,86 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
@conn.add_foreign_key :engines, :old_cars
end
- assert_match "Column `old_car_id` on table `engines` has a type of `bigint(20)`", error.message
+ assert_includes error.message, <<~MSG.squish
+ Column `old_car_id` on table `engines` does not match column `id` on `old_cars`,
+ which has type `int(11)`. To resolve this issue, change the type of the `old_car_id`
+ column on `engines` to be :integer. (For example `t.integer :old_car_id`).
+ MSG
assert_not_nil error.cause
- @conn.exec_query("ALTER TABLE engines DROP COLUMN old_car_id")
+ ensure
+ @conn.execute("ALTER TABLE engines DROP COLUMN old_car_id") rescue nil
+ end
+
+ def test_errors_for_bigint_fks_on_integer_pk_table_in_create_table
+ # table old_cars has primary key of integer
+
+ error = assert_raises(ActiveRecord::MismatchedForeignKey) do
+ @conn.execute(<<~SQL)
+ CREATE TABLE activerecord_unittest.foos (
+ id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ old_car_id bigint,
+ INDEX index_foos_on_old_car_id (old_car_id),
+ CONSTRAINT fk_rails_ff771f3c96 FOREIGN KEY (old_car_id) REFERENCES old_cars (id)
+ )
+ SQL
+ end
+
+ assert_includes error.message, <<~MSG.squish
+ Column `old_car_id` on table `foos` does not match column `id` on `old_cars`,
+ which has type `int(11)`. To resolve this issue, change the type of the `old_car_id`
+ column on `foos` to be :integer. (For example `t.integer :old_car_id`).
+ MSG
+ assert_not_nil error.cause
+ ensure
+ @conn.drop_table :foos, if_exists: true
+ end
+
+ def test_errors_for_integer_fks_on_bigint_pk_table_in_create_table
+ # table old_cars has primary key of bigint
+
+ error = assert_raises(ActiveRecord::MismatchedForeignKey) do
+ @conn.execute(<<~SQL)
+ CREATE TABLE activerecord_unittest.foos (
+ id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ car_id int,
+ INDEX index_foos_on_car_id (car_id),
+ CONSTRAINT fk_rails_ff771f3c96 FOREIGN KEY (car_id) REFERENCES cars (id)
+ )
+ SQL
+ end
+
+ assert_includes error.message, <<~MSG.squish
+ Column `car_id` on table `foos` does not match column `id` on `cars`,
+ which has type `bigint(20)`. To resolve this issue, change the type of the `car_id`
+ column on `foos` to be :bigint. (For example `t.bigint :car_id`).
+ MSG
+ assert_not_nil error.cause
+ ensure
+ @conn.drop_table :foos, if_exists: true
+ end
+
+ def test_errors_for_bigint_fks_on_string_pk_table_in_create_table
+ # table old_cars has primary key of string
+
+ error = assert_raises(ActiveRecord::MismatchedForeignKey) do
+ @conn.execute(<<~SQL)
+ CREATE TABLE activerecord_unittest.foos (
+ id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ subscriber_id bigint,
+ INDEX index_foos_on_subscriber_id (subscriber_id),
+ CONSTRAINT fk_rails_ff771f3c96 FOREIGN KEY (subscriber_id) REFERENCES subscribers (nick)
+ )
+ SQL
+ end
+
+ assert_includes error.message, <<~MSG.squish
+ Column `subscriber_id` on table `foos` does not match column `nick` on `subscribers`,
+ which has type `varchar(255)`. To resolve this issue, change the type of the `subscriber_id`
+ column on `foos` to be :string. (For example `t.string :subscriber_id`).
+ MSG
+ assert_not_nil error.cause
+ ensure
+ @conn.drop_table :foos, if_exists: true
end
def test_errors_when_an_insert_query_is_called_while_preventing_writes
@@ -123,7 +200,15 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
def test_doesnt_error_when_a_set_query_is_called_while_preventing_writes
@conn.while_preventing_writes do
- assert_nil @conn.execute("SET NAMES utf8")
+ assert_nil @conn.execute("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci")
+ end
+ end
+
+ def test_doesnt_error_when_a_read_query_with_leading_chars_is_called_while_preventing_writes
+ @conn.execute("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
+
+ @conn.while_preventing_writes do
+ assert_equal 1, @conn.execute("(\n( SELECT `engines`.* FROM `engines` WHERE `engines`.`car_id` = '138853948594' ) )").entries.count
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/optimizer_hints_test.rb b/activerecord/test/cases/adapters/mysql2/optimizer_hints_test.rb
new file mode 100644
index 0000000000..628802b216
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/optimizer_hints_test.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/post"
+
+if supports_optimizer_hints?
+ class Mysql2OptimzerHintsTest < ActiveRecord::Mysql2TestCase
+ fixtures :posts
+
+ def test_optimizer_hints
+ assert_sql(%r{\ASELECT /\*\+ NO_RANGE_OPTIMIZATION\(posts index_posts_on_author_id\) \*/}) do
+ posts = Post.optimizer_hints("NO_RANGE_OPTIMIZATION(posts index_posts_on_author_id)")
+ posts = posts.select(:id).where(author_id: [0, 1])
+ assert_includes posts.explain, "| index | index_posts_on_author_id | index_posts_on_author_id |"
+ end
+ end
+
+ def test_optimizer_hints_with_count_subquery
+ assert_sql(%r{\ASELECT /\*\+ NO_RANGE_OPTIMIZATION\(posts index_posts_on_author_id\) \*/}) do
+ posts = Post.optimizer_hints("NO_RANGE_OPTIMIZATION(posts index_posts_on_author_id)")
+ posts = posts.select(:id).where(author_id: [0, 1]).limit(5)
+ assert_equal 5, posts.count
+ end
+ end
+
+ def test_optimizer_hints_is_sanitized
+ assert_sql(%r{\ASELECT /\*\+ NO_RANGE_OPTIMIZATION\(posts index_posts_on_author_id\) \*/}) do
+ posts = Post.optimizer_hints("/*+ NO_RANGE_OPTIMIZATION(posts index_posts_on_author_id) */")
+ posts = posts.select(:id).where(author_id: [0, 1])
+ assert_includes posts.explain, "| index | index_posts_on_author_id | index_posts_on_author_id |"
+ end
+
+ assert_sql(%r{\ASELECT /\*\+ `posts`\.\*, \*/}) do
+ posts = Post.optimizer_hints("**// `posts`.*, //**")
+ posts = posts.select(:id).where(author_id: [0, 1])
+ assert_equal({ "id" => 1 }, posts.first.as_json)
+ end
+ end
+
+ def test_optimizer_hints_with_unscope
+ assert_sql(%r{\ASELECT `posts`\.`id`}) do
+ posts = Post.optimizer_hints("/*+ NO_RANGE_OPTIMIZATION(posts index_posts_on_author_id) */")
+ posts = posts.select(:id).where(author_id: [0, 1])
+ posts.unscope(:optimizer_hints).load
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 1283b0642c..b8f51acba0 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -41,7 +41,7 @@ module ActiveRecord
column_24 = @connection.columns(:mysql_doubles).find { |c| c.name == "float_24" }
column_25 = @connection.columns(:mysql_doubles).find { |c| c.name == "float_25" }
- # Mysql floats are precision 0..24, Mysql doubles are precision 25..53
+ # MySQL floats are precision 0..24, MySQL doubles are precision 25..53
assert_equal 24, column_no_limit.limit
assert_equal 24, column_short.limit
assert_equal 53, column_long.limit
diff --git a/activerecord/test/cases/adapters/mysql2/sp_test.rb b/activerecord/test/cases/adapters/mysql2/sp_test.rb
index 7b6dce71e9..626ef59570 100644
--- a/activerecord/test/cases/adapters/mysql2/sp_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/sp_test.rb
@@ -9,7 +9,7 @@ class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase
def setup
@connection = ActiveRecord::Base.connection
- unless ActiveRecord::Base.connection.version >= "5.6.0"
+ unless ActiveRecord::Base.connection.database_version >= "5.6.0"
skip("no stored procedure support")
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/annotate_test.rb b/activerecord/test/cases/adapters/postgresql/annotate_test.rb
new file mode 100644
index 0000000000..42a2861511
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/annotate_test.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/post"
+
+class PostgresqlAnnotateTest < ActiveRecord::PostgreSQLTestCase
+ fixtures :posts
+
+ def test_annotate_wraps_content_in_an_inline_comment
+ assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
+ posts = Post.select(:id).annotate("foo")
+ assert posts.first
+ end
+ end
+
+ def test_annotate_is_sanitized
+ assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
+ posts = Post.select(:id).annotate("*/foo/*")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
+ posts = Post.select(:id).annotate("**//foo//**")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/ /\* bar \*/}) do
+ posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* \+ MAX_EXECUTION_TIME\(1\) \*/}) do
+ posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
+ assert posts.first
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 3988c2adca..531e6b2328 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -35,7 +35,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
def test_binary_columns_are_limitless_the_upper_limit_is_one_GB
assert_equal "bytea", @connection.type_to_sql(:binary, limit: 100_000)
- assert_raise ActiveRecord::ActiveRecordError do
+ assert_raise ArgumentError do
@connection.type_to_sql(:binary, limit: 4294967295)
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 40ab158c05..9494863a75 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -25,28 +25,20 @@ module ActiveRecord
super
end
- def test_truncate
- count = ActiveRecord::Base.connection.execute("select count(*) from comments").first["count"].to_i
- assert_operator count, :>, 0
- ActiveRecord::Base.connection.truncate("comments")
- count = ActiveRecord::Base.connection.execute("select count(*) from comments").first["count"].to_i
- assert_equal 0, count
- 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/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index b7535d5c9a..562cf1f2d1 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -64,7 +64,7 @@ class PostgresqlDataTypeTest < ActiveRecord::PostgreSQLTestCase
def test_text_columns_are_limitless_the_upper_limit_is_one_GB
assert_equal "text", @connection.type_to_sql(:text, limit: 100_000)
- assert_raise ActiveRecord::ActiveRecordError do
+ assert_raise ArgumentError do
@connection.type_to_sql(:text, limit: 4294967295)
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
index df97ab11e7..0fd7b2c6ed 100644
--- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
@@ -22,23 +22,26 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase
@connection = ActiveRecord::Base.connection
- @old_schema_migration_table_name = ActiveRecord::SchemaMigration.table_name
@old_table_name_prefix = ActiveRecord::Base.table_name_prefix
@old_table_name_suffix = ActiveRecord::Base.table_name_suffix
ActiveRecord::Base.table_name_prefix = "p_"
ActiveRecord::Base.table_name_suffix = "_s"
+ ActiveRecord::SchemaMigration.reset_table_name
+ ActiveRecord::InternalMetadata.reset_table_name
+
ActiveRecord::SchemaMigration.delete_all rescue nil
- ActiveRecord::SchemaMigration.table_name = "p_schema_migrations_s"
ActiveRecord::Migration.verbose = false
end
def teardown
- ActiveRecord::Base.table_name_prefix = @old_table_name_prefix
- ActiveRecord::Base.table_name_suffix = @old_table_name_suffix
ActiveRecord::SchemaMigration.delete_all rescue nil
ActiveRecord::Migration.verbose = true
- ActiveRecord::SchemaMigration.table_name = @old_schema_migration_table_name
+
+ ActiveRecord::Base.table_name_prefix = @old_table_name_prefix
+ ActiveRecord::Base.table_name_suffix = @old_table_name_suffix
+ ActiveRecord::SchemaMigration.reset_table_name
+ ActiveRecord::InternalMetadata.reset_table_name
super
end
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index 8c6f046553..14c262f4ce 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -247,7 +247,7 @@ class PostgreSQLGeometricLineTest < ActiveRecord::PostgreSQLTestCase
class PostgresqlLine < ActiveRecord::Base; end
setup do
- unless ActiveRecord::Base.connection.send(:postgresql_version) >= 90400
+ unless ActiveRecord::Base.connection.database_version >= 90400
skip("line type is not fully implemented")
end
@connection = ActiveRecord::Base.connection
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index 4b061a9375..671d8211a7 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -2,6 +2,7 @@
require "cases/helper"
require "support/schema_dumping_helper"
+require "support/stubs/strong_parameters"
class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
@@ -11,12 +12,6 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
store_accessor :settings, :language, :timezone
end
- class FakeParameters
- def to_unsafe_h
- { "hi" => "hi" }
- end
- end
-
def setup
@connection = ActiveRecord::Base.connection
@@ -158,6 +153,22 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
assert_equal "GMT", y.timezone
end
+ def test_changes_with_store_accessors
+ x = Hstore.new(language: "de")
+ assert x.language_changed?
+ assert_nil x.language_was
+ assert_equal [nil, "de"], x.language_change
+ x.save!
+
+ assert_not x.language_changed?
+ x.reload
+
+ x.settings = nil
+ assert x.language_changed?
+ assert_equal "de", x.language_was
+ assert_equal ["de", nil], x.language_change
+ end
+
def test_changes_in_place
hstore = Hstore.create!(settings: { "one" => "two" })
hstore.settings["three"] = "four"
@@ -344,7 +355,7 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
end
def test_supports_to_unsafe_h_values
- assert_equal("\"hi\"=>\"hi\"", @type.serialize(FakeParameters.new))
+ assert_equal "\"hi\"=>\"hi\"", @type.serialize(ProtectedParams.new("hi" => "hi"))
end
private
diff --git a/activerecord/test/cases/adapters/postgresql/optimizer_hints_test.rb b/activerecord/test/cases/adapters/postgresql/optimizer_hints_test.rb
new file mode 100644
index 0000000000..5b9f5e0832
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/optimizer_hints_test.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/post"
+
+if supports_optimizer_hints?
+ class PostgresqlOptimzerHintsTest < ActiveRecord::PostgreSQLTestCase
+ fixtures :posts
+
+ def setup
+ enable_extension!("pg_hint_plan", ActiveRecord::Base.connection)
+ end
+
+ def test_optimizer_hints
+ assert_sql(%r{\ASELECT /\*\+ SeqScan\(posts\) \*/}) do
+ posts = Post.optimizer_hints("SeqScan(posts)")
+ posts = posts.select(:id).where(author_id: [0, 1])
+ assert_includes posts.explain, "Seq Scan on posts"
+ end
+ end
+
+ def test_optimizer_hints_with_count_subquery
+ assert_sql(%r{\ASELECT /\*\+ SeqScan\(posts\) \*/}) do
+ posts = Post.optimizer_hints("SeqScan(posts)")
+ posts = posts.select(:id).where(author_id: [0, 1]).limit(5)
+ assert_equal 5, posts.count
+ end
+ end
+
+ def test_optimizer_hints_is_sanitized
+ assert_sql(%r{\ASELECT /\*\+ SeqScan\(posts\) \*/}) do
+ posts = Post.optimizer_hints("/*+ SeqScan(posts) */")
+ posts = posts.select(:id).where(author_id: [0, 1])
+ assert_includes posts.explain, "Seq Scan on posts"
+ end
+
+ assert_sql(%r{\ASELECT /\*\+ "posts"\.\*, \*/}) do
+ posts = Post.optimizer_hints("**// \"posts\".*, //**")
+ posts = posts.select(:id).where(author_id: [0, 1])
+ assert_equal({ "id" => 1 }, posts.first.as_json)
+ end
+ end
+
+ def test_optimizer_hints_with_unscope
+ assert_sql(%r{\ASELECT "posts"\."id"}) do
+ posts = Post.optimizer_hints("/*+ SeqScan(posts) */")
+ posts = posts.select(:id).where(author_id: [0, 1])
+ posts.unscope(:optimizer_hints).load
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/partitions_test.rb b/activerecord/test/cases/adapters/postgresql/partitions_test.rb
index 0ac9ca1200..4015bc94f9 100644
--- a/activerecord/test/cases/adapters/postgresql/partitions_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/partitions_test.rb
@@ -12,7 +12,7 @@ class PostgreSQLPartitionsTest < ActiveRecord::PostgreSQLTestCase
end
def test_partitions_table_exists
- skip unless ActiveRecord::Base.connection.postgresql_version >= 100000
+ skip unless ActiveRecord::Base.connection.database_version >= 100000
@connection.create_table :partitioned_events, force: true, id: false,
options: "partition by range (issued_at)" do |t|
t.timestamp :issued_at
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 34b4fc344b..fbd3cbf90f 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -432,6 +432,16 @@ module ActiveRecord
end
end
+ def test_doesnt_error_when_a_read_query_with_leading_chars_is_called_while_preventing_writes
+ with_example_table do
+ @connection.execute("INSERT INTO ex (data) VALUES ('138853948594')")
+
+ @connection.while_preventing_writes do
+ assert_equal 1, @connection.execute("(\n( SELECT * FROM ex WHERE data = '138853948594' ) )").entries.count
+ end
+ end
+ end
+
private
def with_example_table(definition = "id serial primary key, number integer, data character varying(255)", &block)
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index 478cd5aa76..068f1e8bea 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -375,6 +375,22 @@ class PostgresqlRangeTest < ActiveRecord::PostgreSQLTestCase
assert_equal(-Float::INFINITY...Float::INFINITY, record.float_range)
end
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0")
+ def test_endless_range_values
+ record = PostgresqlRange.create!(
+ int4_range: eval("1.."),
+ int8_range: eval("10.."),
+ float_range: eval("0.5..")
+ )
+
+ record = PostgresqlRange.find(record.id)
+
+ assert_equal 1...Float::INFINITY, record.int4_range
+ assert_equal 10...Float::INFINITY, record.int8_range
+ assert_equal 0.5...Float::INFINITY, record.float_range
+ end
+ end
+
private
def assert_equal_round_trip(range, attribute, value)
round_trip(range, attribute, value)
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/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 9912763c1b..d2d8ea8042 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -114,6 +114,22 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
assert_equal "foobar", uuid.guid_before_type_cast
end
+ def test_invalid_uuid_dont_match_to_nil
+ UUIDType.create!
+ assert_empty UUIDType.where(guid: "")
+ assert_empty UUIDType.where(guid: "foobar")
+ end
+
+ class DuckUUID
+ def initialize(uuid)
+ @uuid = uuid
+ end
+
+ def to_s
+ @uuid
+ end
+ end
+
def test_acceptable_uuid_regex
# Valid uuids
["A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11",
@@ -125,9 +141,11 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
# so we shouldn't block it either. (Pay attention to "fb6d" – the "f" here
# is invalid – it must be one of 8, 9, A, B, a, b according to the spec.)
"{a0eebc99-9c0b-4ef8-fb6d-6bb9bd380a11}",
+ # Support Object-Oriented UUIDs which respond to #to_s
+ DuckUUID.new("A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11"),
].each do |valid_uuid|
uuid = UUIDType.new guid: valid_uuid
- assert_not_nil uuid.guid
+ assert_instance_of String, uuid.guid
end
# Invalid uuids
diff --git a/activerecord/test/cases/adapters/sqlite3/annotate_test.rb b/activerecord/test/cases/adapters/sqlite3/annotate_test.rb
new file mode 100644
index 0000000000..6567a5eca3
--- /dev/null
+++ b/activerecord/test/cases/adapters/sqlite3/annotate_test.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/post"
+
+class SQLite3AnnotateTest < ActiveRecord::SQLite3TestCase
+ fixtures :posts
+
+ def test_annotate_wraps_content_in_an_inline_comment
+ assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
+ posts = Post.select(:id).annotate("foo")
+ assert posts.first
+ end
+ end
+
+ def test_annotate_is_sanitized
+ assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
+ posts = Post.select(:id).annotate("*/foo/*")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
+ posts = Post.select(:id).annotate("**//foo//**")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/ /\* bar \*/}) do
+ posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* \+ MAX_EXECUTION_TIME\(1\) \*/}) do
+ posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
+ assert posts.first
+ end
+ end
+end
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/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 5c41c14171..806cfbfc00 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -625,6 +625,16 @@ module ActiveRecord
end
end
+ def test_doesnt_error_when_a_read_query_with_leading_chars_is_called_while_preventing_writes
+ with_example_table "id int, data string" do
+ @conn.execute("INSERT INTO ex (data) VALUES ('138853948594')")
+
+ @conn.while_preventing_writes do
+ assert_equal 1, @conn.execute(" SELECT data from ex WHERE data = '138853948594'").count
+ end
+ end
+ end
+
private
def assert_logged(logs)
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 9d88b14dab..7aa6d089c5 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -51,11 +51,11 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
assert_equal 7, @connection.migration_context.current_version
end
- def test_schema_define_w_table_name_prefix
- table_name = ActiveRecord::SchemaMigration.table_name
+ def test_schema_define_with_table_name_prefix
old_table_name_prefix = ActiveRecord::Base.table_name_prefix
ActiveRecord::Base.table_name_prefix = "nep_"
- ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}"
+ ActiveRecord::SchemaMigration.reset_table_name
+ ActiveRecord::InternalMetadata.reset_table_name
ActiveRecord::Schema.define(version: 7) do
create_table :fruits do |t|
t.column :color, :string
@@ -67,7 +67,8 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
assert_equal 7, @connection.migration_context.current_version
ensure
ActiveRecord::Base.table_name_prefix = old_table_name_prefix
- ActiveRecord::SchemaMigration.table_name = table_name
+ ActiveRecord::SchemaMigration.reset_table_name
+ ActiveRecord::InternalMetadata.reset_table_name
end
def test_schema_raises_an_error_for_invalid_column_type
@@ -159,7 +160,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
end
if subsecond_precision_supported?
- def test_timestamps_sets_presicion_on_create_table
+ def test_timestamps_sets_precision_on_create_table
ActiveRecord::Schema.define do
create_table :has_timestamps do |t|
t.timestamps
@@ -170,7 +171,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
assert @connection.column_exists?(:has_timestamps, :updated_at, precision: 6, null: false)
end
- def test_timestamps_sets_presicion_on_change_table
+ def test_timestamps_sets_precision_on_change_table
ActiveRecord::Schema.define do
create_table :has_timestamps
@@ -184,7 +185,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
end
if ActiveRecord::Base.connection.supports_bulk_alter?
- def test_timestamps_sets_presicion_on_change_table_with_bulk
+ def test_timestamps_sets_precision_on_change_table_with_bulk
ActiveRecord::Schema.define do
create_table :has_timestamps
@@ -198,7 +199,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
end
end
- def test_timestamps_sets_presicion_on_add_timestamps
+ def test_timestamps_sets_precision_on_add_timestamps
ActiveRecord::Schema.define do
create_table :has_timestamps
add_timestamps :has_timestamps, default: Time.now
diff --git a/activerecord/test/cases/arel/insert_manager_test.rb b/activerecord/test/cases/arel/insert_manager_test.rb
index 2376ad8d37..79b85742ee 100644
--- a/activerecord/test/cases/arel/insert_manager_test.rb
+++ b/activerecord/test/cases/arel/insert_manager_test.rb
@@ -11,19 +11,18 @@ module Arel
end
describe "insert" do
- it "can create a Values node" do
+ it "can create a ValuesList node" do
manager = Arel::InsertManager.new
- values = manager.create_values %w{ a b }, %w{ c d }
+ values = manager.create_values_list([%w{ a b }, %w{ c d }])
- assert_kind_of Arel::Nodes::Values, values
- assert_equal %w{ a b }, values.left
- assert_equal %w{ c d }, values.right
+ assert_kind_of Arel::Nodes::ValuesList, values
+ assert_equal [%w{ a b }, %w{ c d }], values.rows
end
it "allows sql literals" do
manager = Arel::InsertManager.new
manager.into Table.new(:users)
- manager.values = manager.create_values [Arel.sql("*")], %w{ a }
+ manager.values = manager.create_values([Arel.sql("*")])
manager.to_sql.must_be_like %{
INSERT INTO \"users\" VALUES (*)
}
@@ -186,9 +185,9 @@ module Arel
manager = Arel::InsertManager.new
manager.into table
- manager.values = Nodes::Values.new [1]
+ manager.values = Nodes::ValuesList.new([[1], [2]])
manager.to_sql.must_be_like %{
- INSERT INTO "users" VALUES (1)
+ INSERT INTO "users" VALUES (1), (2)
}
end
@@ -210,11 +209,11 @@ module Arel
manager = Arel::InsertManager.new
manager.into table
- manager.values = Nodes::Values.new [1, "aaron"]
+ manager.values = Nodes::ValuesList.new([[1, "aaron"], [2, "david"]])
manager.columns << table[:id]
manager.columns << table[:name]
manager.to_sql.must_be_like %{
- INSERT INTO "users" ("id", "name") VALUES (1, 'aaron')
+ INSERT INTO "users" ("id", "name") VALUES (1, 'aaron'), (2, 'david')
}
end
end
diff --git a/activerecord/test/cases/arel/nodes/comment_test.rb b/activerecord/test/cases/arel/nodes/comment_test.rb
new file mode 100644
index 0000000000..bf5eaf4c5a
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/comment_test.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+require "yaml"
+
+module Arel
+ module Nodes
+ class CommentTest < Arel::Spec
+ describe "equality" do
+ it "is equal with equal contents" do
+ array = [Comment.new(["foo"]), Comment.new(["foo"])]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different contents" do
+ array = [Comment.new(["foo"]), Comment.new(["bar"])]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/select_core_test.rb b/activerecord/test/cases/arel/nodes/select_core_test.rb
index 0b698205ff..6860f2a395 100644
--- a/activerecord/test/cases/arel/nodes/select_core_test.rb
+++ b/activerecord/test/cases/arel/nodes/select_core_test.rb
@@ -37,6 +37,7 @@ module Arel
core1.groups = %w[j k l]
core1.windows = %w[m n o]
core1.havings = %w[p q r]
+ core1.comment = Arel::Nodes::Comment.new(["comment"])
core2 = SelectCore.new
core2.froms = %w[a b c]
core2.projections = %w[d e f]
@@ -44,6 +45,7 @@ module Arel
core2.groups = %w[j k l]
core2.windows = %w[m n o]
core2.havings = %w[p q r]
+ core2.comment = Arel::Nodes::Comment.new(["comment"])
array = [core1, core2]
assert_equal 1, array.uniq.size
end
@@ -56,6 +58,7 @@ module Arel
core1.groups = %w[j k l]
core1.windows = %w[m n o]
core1.havings = %w[p q r]
+ core1.comment = Arel::Nodes::Comment.new(["comment"])
core2 = SelectCore.new
core2.froms = %w[a b c]
core2.projections = %w[d e f]
@@ -63,6 +66,11 @@ module Arel
core2.groups = %w[j k l]
core2.windows = %w[m n o]
core2.havings = %w[l o l]
+ core2.comment = Arel::Nodes::Comment.new(["comment"])
+ array = [core1, core2]
+ assert_equal 2, array.uniq.size
+ core2.havings = %w[p q r]
+ core2.comment = Arel::Nodes::Comment.new(["other"])
array = [core1, core2]
assert_equal 2, array.uniq.size
end
diff --git a/activerecord/test/cases/arel/select_manager_test.rb b/activerecord/test/cases/arel/select_manager_test.rb
index 5220950905..e6c49cd429 100644
--- a/activerecord/test/cases/arel/select_manager_test.rb
+++ b/activerecord/test/cases/arel/select_manager_test.rb
@@ -1221,5 +1221,28 @@ module Arel
manager.distinct_on(false).must_equal manager
end
end
+
+ describe "comment" do
+ it "chains" do
+ manager = Arel::SelectManager.new
+ manager.comment("selecting").must_equal manager
+ end
+
+ it "appends a comment to the generated query" do
+ manager = Arel::SelectManager.new
+ table = Table.new :users
+ manager.from(table).project(table["id"])
+
+ manager.comment("selecting")
+ manager.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" /* selecting */
+ }
+
+ manager.comment("selecting", "with", "comment")
+ manager.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" /* selecting */ /* with */ /* comment */
+ }
+ end
+ end
end
end
diff --git a/activerecord/test/cases/arel/support/fake_record.rb b/activerecord/test/cases/arel/support/fake_record.rb
index 559ff5d4e6..5ebeabd4a3 100644
--- a/activerecord/test/cases/arel/support/fake_record.rb
+++ b/activerecord/test/cases/arel/support/fake_record.rb
@@ -58,6 +58,14 @@ module FakeRecord
"\"#{name}\""
end
+ def sanitize_as_sql_comment(comment)
+ comment
+ end
+
+ def in_clause_length
+ 3
+ end
+
def schema_cache
self
end
diff --git a/activerecord/test/cases/arel/visitors/depth_first_test.rb b/activerecord/test/cases/arel/visitors/depth_first_test.rb
index f94ad521d7..106be2311d 100644
--- a/activerecord/test/cases/arel/visitors/depth_first_test.rb
+++ b/activerecord/test/cases/arel/visitors/depth_first_test.rb
@@ -33,6 +33,7 @@ module Arel
Arel::Nodes::Ordering,
Arel::Nodes::StringJoin,
Arel::Nodes::UnqualifiedColumn,
+ Arel::Nodes::ValuesList,
Arel::Nodes::Limit,
Arel::Nodes::Else,
].each do |klass|
@@ -100,6 +101,12 @@ module Arel
assert_equal [:a, :b, join], @collector.calls
end
+ def test_comment
+ comment = Nodes::Comment.new ["foo"]
+ @visitor.accept comment
+ assert_equal ["foo", ["foo"], comment], @collector.calls
+ end
+
[
Arel::Nodes::Assignment,
Arel::Nodes::Between,
@@ -116,7 +123,6 @@ module Arel
Arel::Nodes::NotIn,
Arel::Nodes::Or,
Arel::Nodes::TableAlias,
- Arel::Nodes::Values,
Arel::Nodes::As,
Arel::Nodes::DeleteStatement,
Arel::Nodes::JoinSource,
diff --git a/activerecord/test/cases/arel/visitors/dot_test.rb b/activerecord/test/cases/arel/visitors/dot_test.rb
index 6b3c132f83..ade53c358e 100644
--- a/activerecord/test/cases/arel/visitors/dot_test.rb
+++ b/activerecord/test/cases/arel/visitors/dot_test.rb
@@ -37,6 +37,7 @@ module Arel
Arel::Nodes::Offset,
Arel::Nodes::Ordering,
Arel::Nodes::UnqualifiedColumn,
+ Arel::Nodes::ValuesList,
Arel::Nodes::Limit,
].each do |klass|
define_method("test_#{klass.name.gsub('::', '_')}") do
@@ -61,7 +62,6 @@ module Arel
Arel::Nodes::NotIn,
Arel::Nodes::Or,
Arel::Nodes::TableAlias,
- Arel::Nodes::Values,
Arel::Nodes::As,
Arel::Nodes::DeleteStatement,
Arel::Nodes::JoinSource,
diff --git a/activerecord/test/cases/arel/visitors/to_sql_test.rb b/activerecord/test/cases/arel/visitors/to_sql_test.rb
index 4bfa799a96..fd19574876 100644
--- a/activerecord/test/cases/arel/visitors/to_sql_test.rb
+++ b/activerecord/test/cases/arel/visitors/to_sql_test.rb
@@ -23,9 +23,9 @@ module Arel
sql.must_be_like "?"
end
- it "does not quote BindParams used as part of a Values" do
+ it "does not quote BindParams used as part of a ValuesList" do
bp = Nodes::BindParam.new(1)
- values = Nodes::Values.new([bp])
+ values = Nodes::ValuesList.new([[bp]])
sql = compile values
sql.must_be_like "VALUES (?)"
end
@@ -395,6 +395,11 @@ module Arel
compile(node).must_be_like %{
"users"."id" IN (1, 2, 3)
}
+
+ node = @attr.in [1, 2, 3, 4, 5]
+ compile(node).must_be_like %{
+ ("users"."id" IN (1, 2, 3) OR "users"."id" IN (4, 5))
+ }
end
it "should return 1=0 when empty right which is always false" do
@@ -545,6 +550,11 @@ module Arel
compile(node).must_be_like %{
"users"."id" NOT IN (1, 2, 3)
}
+
+ node = @attr.not_in [1, 2, 3, 4, 5]
+ compile(node).must_be_like %{
+ "users"."id" NOT IN (1, 2, 3) AND "users"."id" NOT IN (4, 5)
+ }
end
it "should return 1=1 when empty right which is always true" do
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index c01138c059..3525fa2ab8 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -448,8 +448,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_with_select
- assert_equal 1, Company.find(2).firm_with_select.attributes.size
- assert_equal 1, Company.all.merge!(includes: :firm_with_select).find(2).firm_with_select.attributes.size
+ assert_equal 1, Post.find(2).author_with_select.attributes.size
+ assert_equal 1, Post.includes(:author_with_select).find(2).author_with_select.attributes.size
+ end
+
+ def test_custom_attribute_with_select
+ assert_equal 2, Company.find(2).firm_with_select.attributes.size
+ assert_equal 2, Company.includes(:firm_with_select).find(2).firm_with_select.attributes.size
end
def test_belongs_to_without_counter_cache_option
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index a9e22c7643..49f754be63 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.includes(posts: :comments).order(: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.includes({ posts: :comments }, :categorizations).order(:id).to_a
assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal 3, authors[1].posts.size
@@ -36,7 +36,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
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
+ 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 }
end
@@ -121,7 +121,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
assert reply.save
topics = Topic.all.merge!(includes: :replies, order: ["topics.id", "replies_topics.id"]).to_a
- assert_no_queries do
+ assert_queries(0) do
assert_equal 2, topics[0].replies.size
assert_equal 0, topics[1].replies.size
end
diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
index 5fca972aee..673d5f1dcf 100644
--- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
+++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
@@ -21,7 +21,7 @@ module PolymorphicFullStiClassNamesSharedTest
ActiveRecord::Base.store_full_sti_class = store_full_sti_class
post = Namespaced::Post.create(title: "Great stuff", body: "This is not", author_id: 1)
- @tagging = Tagging.create(taggable: post)
+ @tagging = post.create_tagging!
end
def teardown
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 525ad3197a..849939de75 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -110,10 +110,10 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase
end
teardown do
- @davey_mcdave.destroy
- @first_post.destroy
@first_comment.destroy
@first_categorization.destroy
+ @davey_mcdave.destroy
+ @first_post.destroy
end
def test_missing_data_in_a_nested_include_should_not_cause_errors_when_constructing_objects
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index b37e59038e..49bcb1d010 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -4,6 +4,7 @@ require "cases/helper"
require "models/post"
require "models/tagging"
require "models/tag"
+require "models/rating"
require "models/comment"
require "models/author"
require "models/essay"
@@ -44,7 +45,7 @@ end
class EagerAssociationTest < ActiveRecord::TestCase
fixtures :posts, :comments, :authors, :essays, :author_addresses, :categories, :categories_posts,
- :companies, :accounts, :tags, :taggings, :people, :readers, :categorizations,
+ :companies, :accounts, :tags, :taggings, :ratings, :people, :readers, :categorizations,
:owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books,
:developers, :projects, :developers_projects, :members, :memberships, :clubs, :sponsors
@@ -89,6 +90,17 @@ class EagerAssociationTest < ActiveRecord::TestCase
"expected to find only david's posts"
end
+ def test_loading_polymorphic_association_with_mixed_table_conditions
+ rating = Rating.first
+ assert_equal [taggings(:normal_comment_rating)], rating.taggings_without_tag
+
+ rating = Rating.preload(:taggings_without_tag).first
+ assert_equal [taggings(:normal_comment_rating)], rating.taggings_without_tag
+
+ rating = Rating.eager_load(:taggings_without_tag).first
+ assert_equal [taggings(:normal_comment_rating)], rating.taggings_without_tag
+ end
+
def test_loading_with_scope_including_joins
member = Member.first
assert_equal members(:groucho), member
@@ -228,7 +240,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_load_associated_records_in_one_query_when_adapter_has_no_limit
- assert_called(Comment.connection, :in_clause_length, returns: nil) do
+ assert_not_called(Comment.connection, :in_clause_length) do
post = posts(:welcome)
assert_queries(2) do
Post.includes(:comments).where(id: post.id).to_a
@@ -237,16 +249,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_load_associated_records_in_several_queries_when_many_ids_passed
- assert_called(Comment.connection, :in_clause_length, returns: 1) do
+ assert_called(Comment.connection, :in_clause_length, times: 2, returns: 1) do
post1, post2 = posts(:welcome), posts(:thinking)
- assert_queries(3) do
+ assert_queries(2) do
Post.includes(:comments).where(id: [post1.id, post2.id]).to_a
end
end
end
def test_load_associated_records_in_one_query_when_a_few_ids_passed
- assert_called(Comment.connection, :in_clause_length, returns: 3) do
+ assert_not_called(Comment.connection, :in_clause_length) do
post = posts(:welcome)
assert_queries(2) do
Post.includes(:comments).where(id: post.id).to_a
@@ -1259,12 +1271,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_include_has_many_using_primary_key
expected = Firm.find(1).clients_using_primary_key.sort_by(&:name)
- # Oracle adapter truncates alias to 30 characters
- 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)
- end
+ firm = Firm.all.merge!(includes: :clients_using_primary_key, order: "clients_using_primary_keys_companies.name").find(1)
assert_no_queries do
assert_equal expected, firm.clients_using_primary_key
end
@@ -1393,11 +1400,24 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal expected, FirstPost.unscoped.find(2)
end
- test "preload ignores the scoping" do
- assert_equal(
- Comment.find(1).post,
- Post.where("1 = 0").scoping { Comment.preload(:post).find(1).post }
- )
+ test "belongs_to association ignores the scoping" do
+ post = Comment.find(1).post
+
+ Post.where("1=0").scoping do
+ assert_equal post, Comment.find(1).post
+ assert_equal post, Comment.preload(:post).find(1).post
+ assert_equal post, Comment.eager_load(:post).find(1).post
+ end
+ end
+
+ test "has_many association ignores the scoping" do
+ comments = Post.find(1).comments.to_a
+
+ Comment.where("1=0").scoping do
+ assert_equal comments, Post.find(1).comments
+ assert_equal comments, Post.preload(:comments).find(1).comments
+ assert_equal comments, Post.eager_load(:comments).find(1).comments
+ end
end
test "deep preload" do
@@ -1496,6 +1516,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 aef8f31112..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
@@ -89,6 +89,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
private
def extend!(model)
- ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) { }
+ ActiveRecord::Associations::Builder::HasMany.send(:define_extensions, model, :association_name) { }
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 bf44be1811..32285f269a 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1230,10 +1230,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_not_predicate Ship.reflect_on_association(:treasures), :has_cached_counter?
# Count should come from sql count() of treasures rather than treasures_count attribute
- assert_queries(1) do
- assert_equal ship.treasures.size, 0
- assert_predicate ship.treasures, :loaded?
- end
+ assert_equal ship.treasures.size, 0
assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed" do
ship.treasures.create(name: "Gold")
@@ -1354,20 +1351,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
post = posts(:welcome)
assert_no_queries do
assert_not_empty post.comments
- assert_equal 2, post.comments.size
- assert_not_predicate post.comments, :loaded?
- end
- post = posts(:misc_by_bob)
- assert_no_queries do
- assert_empty post.comments
- assert_predicate post.comments, :loaded?
- end
- end
-
- def test_empty_association_loading_with_counter_cache
- post = posts(:misc_by_bob)
- assert_no_queries do
- assert_empty post.comments.to_a
end
end
@@ -1755,6 +1738,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_nil posts.first
end
+ def test_destroy_all_on_desynced_counter_cache_association
+ category = categories(:general)
+ assert_operator category.categorizations.count, :>, 0
+
+ category.categorizations.destroy_all
+ assert_equal 0, category.categorizations.count
+ end
+
def test_destroy_on_association_clears_scope
author = Author.create!(name: "Gannon")
posts = author.posts
@@ -2004,6 +1995,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_not_predicate company.clients, :loaded?
end
+ def test_counter_cache_on_unloaded_association
+ car = Car.create(name: "My AppliCar")
+ assert_equal 0, car.engines.size
+ end
+
def test_ids_reader_cache_not_used_for_size_when_association_is_dirty
firm = Firm.create!(name: "Startup")
assert_equal 0, firm.client_ids.size
@@ -2019,24 +2015,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [3, 11], firm.client_ids
end
- def test_zero_counter_cache_usage_on_unloaded_association
- car = Car.create!(name: "My AppliCar")
- assert_no_queries do
- assert_equal car.engines.size, 0
- assert_predicate car.engines, :loaded?
- end
- end
-
- def test_counter_cache_on_new_record_unloaded_association
- car = Car.new(name: "My AppliCar")
- # Ensure no schema queries inside assertion
- Engine.primary_key
- assert_no_queries do
- assert_equal car.engines.size, 0
- assert_predicate car.engines, :loaded?
- end
- end
-
def test_get_ids_ignores_include_option
assert_equal [readers(:michael_welcome).id], posts(:welcome).readers_with_person_ids
end
@@ -2095,10 +2073,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_associations_order_should_be_priority_over_throughs_order
- david = authors(:david)
+ original = authors(:david)
expected = [12, 10, 9, 8, 7, 6, 5, 3, 2, 1]
- assert_equal expected, david.comments_desc.map(&:id)
- assert_equal expected, Author.includes(:comments_desc).find(david.id).comments_desc.map(&:id)
+ assert_equal expected, original.comments_desc.map(&:id)
+ preloaded = Author.includes(:comments_desc).find(original.id)
+ assert_equal expected, preloaded.comments_desc.map(&:id)
+ assert_equal original.posts_sorted_by_id.first.comments.map(&:id), preloaded.posts_sorted_by_id.first.comments.map(&:id)
end
def test_dynamic_find_should_respect_association_order_for_through
@@ -2598,22 +2578,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "association with extend option" do
post = posts(:welcome)
- assert_equal "lifo", post.comments_with_extend.author
- assert_equal "hello", post.comments_with_extend.greeting
+ assert_equal "lifo", post.comments_with_extend.author
+ assert_equal "hello :)", post.comments_with_extend.greeting
end
test "association with extend option with multiple extensions" do
post = posts(:welcome)
- assert_equal "lifo", post.comments_with_extend_2.author
- assert_equal "hullo", post.comments_with_extend_2.greeting
+ assert_equal "lifo", post.comments_with_extend_2.author
+ assert_equal "hullo :)", post.comments_with_extend_2.greeting
end
test "extend option affects per association" do
post = posts(:welcome)
- assert_equal "lifo", post.comments_with_extend.author
- assert_equal "lifo", post.comments_with_extend_2.author
- assert_equal "hello", post.comments_with_extend.greeting
- assert_equal "hullo", post.comments_with_extend_2.greeting
+ assert_equal "lifo", post.comments_with_extend.author
+ assert_equal "lifo", post.comments_with_extend_2.author
+ assert_equal "hello :)", post.comments_with_extend.greeting
+ assert_equal "hullo :)", post.comments_with_extend_2.greeting
end
test "delete record with complex joins" do
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 0ac56c6168..0ab99aa6cd 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -33,6 +33,9 @@ require "models/organization"
require "models/user"
require "models/family"
require "models/family_tree"
+require "models/section"
+require "models/seminar"
+require "models/session"
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags,
@@ -55,6 +58,15 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal preloaded, Marshal.load(Marshal.dump(preloaded))
end
+ def test_preload_with_nested_association
+ posts = Post.preload(:author, :author_favorites_with_scope).to_a
+
+ assert_no_queries do
+ posts.each(&:author)
+ posts.each(&:author_favorites_with_scope)
+ end
+ end
+
def test_preload_sti_rhs_class
developers = Developer.includes(:firms).all.to_a
assert_no_queries do
@@ -737,10 +749,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
firm = companies(:first_firm)
lifo = Developer.new(name: "lifo")
- assert_raises(ActiveRecord::RecordInvalid) { firm.developers << lifo }
+ assert_raises(ActiveRecord::RecordInvalid) do
+ assert_deprecated { firm.developers << lifo }
+ end
lifo = Developer.create!(name: "lifo")
- assert_raises(ActiveRecord::RecordInvalid) { firm.developers << lifo }
+ assert_raises(ActiveRecord::RecordInvalid) do
+ assert_deprecated { firm.developers << lifo }
+ end
end
end
@@ -1151,7 +1167,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_create_should_not_raise_exception_when_join_record_has_errors
repair_validations(Categorization) do
Categorization.validate { |r| r.errors[:base] << "Invalid Categorization" }
- Category.create(name: "Fishing", authors: [Author.first])
+ assert_deprecated { Category.create(name: "Fishing", authors: [Author.first]) }
end
end
@@ -1164,7 +1180,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
repair_validations(Categorization) do
Categorization.validate { |r| r.errors[:base] << "Invalid Categorization" }
assert_raises(ActiveRecord::RecordInvalid) do
- Category.create!(name: "Fishing", authors: [Author.first])
+ assert_deprecated { Category.create!(name: "Fishing", authors: [Author.first]) }
end
end
end
@@ -1174,7 +1190,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
Categorization.validate { |r| r.errors[:base] << "Invalid Categorization" }
c = Category.new(name: "Fishing", authors: [Author.first])
assert_raises(ActiveRecord::RecordInvalid) do
- c.save!
+ assert_deprecated { c.save! }
end
end
end
@@ -1183,7 +1199,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
repair_validations(Categorization) do
Categorization.validate { |r| r.errors[:base] << "Invalid Categorization" }
c = Category.new(name: "Fishing", authors: [Author.first])
- assert_not c.save
+ assert_deprecated { assert_not c.save }
end
end
@@ -1465,6 +1481,37 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [subscription2], post.subscriptions.to_a
end
+ def test_child_is_visible_to_join_model_in_add_association_callbacks
+ [:before_add, :after_add].each do |callback_name|
+ sentient_treasure = Class.new(Treasure) do
+ def self.name; "SentientTreasure"; end
+
+ has_many :pet_treasures, foreign_key: :treasure_id, callback_name => :check_pet!
+ has_many :pets, through: :pet_treasures
+
+ def check_pet!(added)
+ raise "No pet!" if added.pet.nil?
+ end
+ end
+
+ treasure = sentient_treasure.new
+ assert_nothing_raised { treasure.pets << pets(:mochi) }
+ end
+ end
+
+ def test_circular_autosave_association_correctly_saves_multiple_records
+ cs180 = Seminar.new(name: "CS180")
+ fall = Session.new(name: "Fall")
+ sections = [
+ cs180.sections.build(short_name: "A"),
+ cs180.sections.build(short_name: "B"),
+ ]
+ fall.sections << sections
+ fall.save!
+ fall.reload
+ assert_equal sections, fall.sections.sort_by(&:id)
+ end
+
private
def make_model(name)
Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 7bb629466d..fd727757a3 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -15,10 +15,13 @@ require "models/post"
require "models/drink_designer"
require "models/chef"
require "models/department"
+require "models/club"
+require "models/membership"
class HasOneAssociationsTest < ActiveRecord::TestCase
self.use_transactional_tests = false unless supports_savepoints?
- fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates, :authors, :author_addresses
+ fixtures :accounts, :companies, :developers, :projects, :developers_projects,
+ :ships, :pirates, :authors, :author_addresses, :memberships, :clubs
def setup
Account.destroyed_account_ids.clear
@@ -706,6 +709,40 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_has_one_with_touch_option_on_create
+ assert_queries(3) {
+ Club.create(name: "1000 Oaks", membership_attributes: { favourite: true })
+ }
+ end
+
+ def test_has_one_with_touch_option_on_update
+ new_club = Club.create(name: "1000 Oaks")
+ new_club.create_membership
+
+ assert_queries(2) { new_club.update(name: "Effingut") }
+ end
+
+ def test_has_one_with_touch_option_on_touch
+ new_club = Club.create(name: "1000 Oaks")
+ new_club.create_membership
+
+ assert_queries(1) { new_club.touch }
+ end
+
+ def test_has_one_with_touch_option_on_destroy
+ new_club = Club.create(name: "1000 Oaks")
+ new_club.create_membership
+
+ assert_queries(2) { new_club.destroy }
+ end
+
+ def test_has_one_with_touch_option_on_empty_update
+ new_club = Club.create(name: "1000 Oaks")
+ new_club.create_membership
+
+ assert_no_queries { new_club.save }
+ end
+
class SpecialBook < ActiveRecord::Base
self.table_name = "books"
belongs_to :author, class_name: "SpecialAuthor"
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index c33dcdee61..e0dac01f4a 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -29,7 +29,10 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associations_with_left_outer_joins
sql = Person.joins(agents: :agents).left_outer_joins(agents: :agents).to_sql
- assert_match(/agents_people_4/i, sql)
+ assert_match(/agents_people_2/i, sql)
+ assert_match(/INNER JOIN/i, sql)
+ assert_no_match(/agents_people_4/i, sql)
+ assert_no_match(/LEFT OUTER JOIN/i, sql)
end
def test_construct_finder_sql_does_not_table_name_collide_with_string_joins
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index da3a42e2b5..669e176dcb 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -8,6 +8,7 @@ require "models/zine"
require "models/club"
require "models/sponsor"
require "models/rating"
+require "models/post"
require "models/comment"
require "models/car"
require "models/bulb"
@@ -62,6 +63,14 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
assert_equal rating_reflection, comment_reflection.inverse_of, "The Comment reflection's inverse should be the Rating reflection"
end
+ def test_has_many_and_belongs_to_should_find_inverse_automatically_for_extension_block
+ comment_reflection = Comment.reflect_on_association(:post)
+ post_reflection = Post.reflect_on_association(:comments)
+
+ assert_predicate post_reflection, :has_inverse?
+ assert_equal comment_reflection, post_reflection.inverse_of
+ end
+
def test_has_many_and_belongs_to_should_find_inverse_automatically_for_sti
author_reflection = Author.reflect_on_association(:posts)
author_child_reflection = Author.reflect_on_association(:special_posts)
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 0e54e8c1b0..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
@@ -46,6 +50,12 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
assert queries.any? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
end
+ def test_left_outer_joins_is_deduped_when_same_association_is_joined
+ queries = capture_sql { Author.joins(:posts).left_outer_joins(:posts).to_a }
+ assert queries.any? { |sql| /INNER JOIN/i.match?(sql) }
+ assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
+ end
+
def test_construct_finder_sql_ignores_empty_left_outer_joins_hash
queries = capture_sql { Author.left_outer_joins({}).to_a }
assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
@@ -60,6 +70,10 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
assert_raise(ArgumentError) { Author.left_outer_joins('LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"').to_a }
end
+ def test_left_outer_joins_with_string_join
+ assert_equal 16, Author.left_outer_joins(:posts).joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id").count
+ end
+
def test_join_conditions_added_to_join_clause
queries = capture_sql { Author.left_outer_joins(:essays).to_a }
assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1|\:a1)/i.match?(sql) }
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index 5821744530..35da74102d 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -548,6 +548,15 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_through_association_preload_doesnt_reset_source_association_if_already_preloaded
+ blue = tags(:blue)
+ authors = Author.preload(posts: :first_blue_tags_2, misc_post_first_blue_tags_2: {}).to_a.sort_by(&:id)
+
+ assert_no_queries do
+ assert_equal [blue], authors[2].posts.first.first_blue_tags_2
+ end
+ end
+
def test_nested_has_many_through_with_conditions_on_source_associations_preload_via_joins
# Pointless condition to force single-query loading
assert_includes_and_joins_equal(
@@ -610,6 +619,12 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
assert_equal hotel, Hotel.joins(:cake_designers, :drink_designers).take
end
+ def test_has_many_through_reset_source_reflection_after_loading_is_complete
+ preloaded = Category.preload(:ordered_post_comments).find(1, 2).last
+ original = Category.find(2)
+ assert_equal original.ordered_post_comments.ids, preloaded.ordered_post_comments.ids
+ end
+
private
def assert_includes_and_joins_equal(query, expected, association)
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 081da95df7..84130ec208 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -21,6 +21,11 @@ require "models/molecule"
require "models/electron"
require "models/man"
require "models/interest"
+require "models/pirate"
+require "models/parrot"
+require "models/bird"
+require "models/treasure"
+require "models/price_estimate"
class AssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :developers_projects,
@@ -368,3 +373,97 @@ class GeneratedMethodsTest < ActiveRecord::TestCase
assert_equal :none, MyArticle.new.comments
end
end
+
+class WithAnnotationsTest < ActiveRecord::TestCase
+ fixtures :pirates, :parrots
+
+ def test_belongs_to_with_annotation_includes_a_query_comment
+ pirate = SpacePirate.where.not(parrot_id: nil).first
+ assert pirate, "should have a Pirate record"
+
+ log = capture_sql do
+ pirate.parrot
+ end
+ assert_not_predicate log, :empty?
+ assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
+
+ assert_sql(%r{/\* that tells jokes \*/}) do
+ pirate.parrot_with_annotation
+ end
+ end
+
+ def test_has_and_belongs_to_many_with_annotation_includes_a_query_comment
+ pirate = SpacePirate.first
+ assert pirate, "should have a Pirate record"
+
+ log = capture_sql do
+ pirate.parrots.first
+ end
+ assert_not_predicate log, :empty?
+ assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
+
+ assert_sql(%r{/\* that are very colorful \*/}) do
+ pirate.parrots_with_annotation.first
+ end
+ end
+
+ def test_has_one_with_annotation_includes_a_query_comment
+ pirate = SpacePirate.first
+ assert pirate, "should have a Pirate record"
+
+ log = capture_sql do
+ pirate.ship
+ end
+ assert_not_predicate log, :empty?
+ assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
+
+ assert_sql(%r{/\* that is a rocket \*/}) do
+ pirate.ship_with_annotation
+ end
+ end
+
+ def test_has_many_with_annotation_includes_a_query_comment
+ pirate = SpacePirate.first
+ assert pirate, "should have a Pirate record"
+
+ log = capture_sql do
+ pirate.birds.first
+ end
+ assert_not_predicate log, :empty?
+ assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
+
+ assert_sql(%r{/\* that are also parrots \*/}) do
+ pirate.birds_with_annotation.first
+ end
+ end
+
+ def test_has_many_through_with_annotation_includes_a_query_comment
+ pirate = SpacePirate.first
+ assert pirate, "should have a Pirate record"
+
+ log = capture_sql do
+ pirate.treasure_estimates.first
+ end
+ assert_not_predicate log, :empty?
+ assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
+
+ assert_sql(%r{/\* yarrr \*/}) do
+ pirate.treasure_estimates_with_annotation.first
+ end
+ end
+
+ def test_has_many_through_with_annotation_includes_a_query_comment_when_eager_loading
+ pirate = SpacePirate.first
+ assert pirate, "should have a Pirate record"
+
+ log = capture_sql do
+ pirate.treasure_estimates.first
+ end
+ assert_not_predicate log, :empty?
+ assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
+
+ assert_sql(%r{/\* yarrr \*/}) do
+ SpacePirate.includes(:treasure_estimates_with_annotation, :treasures).first
+ end
+ end
+end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index d341dd0083..5cbe5d796d 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -433,6 +433,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
assert_equal true, Topic.new(author_name: "Name").author_name?
+
+ ActiveModel::Type::Boolean::FALSE_VALUES.each do |value|
+ assert_predicate Topic.new(author_name: value), :author_name?
+ end
end
test "number attribute predicate" do
@@ -454,6 +458,69 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ test "user-defined text attribute predicate" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = Topic.table_name
+
+ attribute :user_defined_text, :text
+ end
+
+ topic = klass.new(user_defined_text: "text")
+ assert_predicate topic, :user_defined_text?
+
+ ActiveModel::Type::Boolean::FALSE_VALUES.each do |value|
+ topic = klass.new(user_defined_text: value)
+ assert_predicate topic, :user_defined_text?
+ end
+ end
+
+ test "user-defined date attribute predicate" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = Topic.table_name
+
+ attribute :user_defined_date, :date
+ end
+
+ topic = klass.new(user_defined_date: Date.current)
+ assert_predicate topic, :user_defined_date?
+ end
+
+ test "user-defined datetime attribute predicate" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = Topic.table_name
+
+ attribute :user_defined_datetime, :datetime
+ end
+
+ topic = klass.new(user_defined_datetime: Time.current)
+ assert_predicate topic, :user_defined_datetime?
+ end
+
+ test "user-defined time attribute predicate" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = Topic.table_name
+
+ attribute :user_defined_time, :time
+ end
+
+ topic = klass.new(user_defined_time: Time.current)
+ assert_predicate topic, :user_defined_time?
+ end
+
+ test "user-defined json attribute predicate" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = Topic.table_name
+
+ attribute :user_defined_json, :json
+ end
+
+ topic = klass.new(user_defined_json: { key: "value" })
+ assert_predicate topic, :user_defined_json?
+
+ topic = klass.new(user_defined_json: {})
+ assert_not_predicate topic, :user_defined_json?
+ end
+
test "custom field attribute predicate" do
object = Company.find_by_sql(<<~SQL).first
SELECT c1.*, c2.type as string_value, c2.rating as int_value
@@ -711,6 +778,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
record.written_on = "Jan 01 00:00:00 2014"
assert_equal record, YAML.load(YAML.dump(record))
end
+ ensure
+ # NOTE: Reset column info because global topics
+ # don't have tz-aware attributes by default.
+ Topic.reset_column_information
end
test "setting a time zone-aware time in the current time zone" do
@@ -1010,9 +1081,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal ["title"], model.accessed_fields
end
- test "generated attribute methods ancestors have correct class" do
+ test "generated attribute methods ancestors have correct module" do
mod = Topic.send(:generated_attribute_methods)
- assert_match %r(GeneratedAttributeMethods), mod.inspect
+ assert_equal "Topic::GeneratedAttributeMethods", mod.inspect
end
private
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 2632aec7ab..a6fb9f0af7 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -56,6 +56,17 @@ module ActiveRecord
assert_equal 255, UnoverloadedType.type_for_attribute("overloaded_string_with_limit").limit
end
+ test "extra options are forwarded to the type caster constructor" do
+ klass = Class.new(OverloadedType) do
+ attribute :starts_at, :datetime, precision: 3, limit: 2, scale: 1
+ end
+
+ starts_at_type = klass.type_for_attribute(:starts_at)
+ assert_equal 3, starts_at_type.precision
+ assert_equal 2, starts_at_type.limit
+ assert_equal 1, starts_at_type.scale
+ end
+
test "nonexistent attribute" do
data = OverloadedType.new(non_existent_decimal: 1)
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 88df0eed55..1a0732c14b 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "cases/helper"
+require "models/author"
require "models/bird"
require "models/post"
require "models/comment"
@@ -1319,21 +1320,45 @@ end
class TestAutosaveAssociationOnAHasOneThroughAssociation < ActiveRecord::TestCase
self.use_transactional_tests = false unless supports_savepoints?
- def setup
- super
+ def create_member_with_organization
organization = Organization.create
- @member = Member.create
- MemberDetail.create(organization: organization, member: @member)
+ member = Member.create
+ MemberDetail.create(organization: organization, member: member)
+
+ member
end
def test_should_not_has_one_through_model
- class << @member.organization
+ member = create_member_with_organization
+
+ class << member.organization
+ def save(*args)
+ super
+ raise "Oh noes!"
+ end
+ end
+ assert_nothing_raised { member.save }
+ end
+
+ def create_author_with_post_with_comment
+ Author.create! name: "David" # make comment_id not match author_id
+ author = Author.create! name: "Sergiy"
+ post = Post.create! author: author, title: "foo", body: "bar"
+ Comment.create! post: post, body: "cool comment"
+
+ author
+ end
+
+ def test_should_not_reversed_has_one_through_model
+ author = create_author_with_post_with_comment
+
+ class << author.comment_on_first_post
def save(*args)
super
raise "Oh noes!"
end
end
- assert_nothing_raised { @member.save }
+ assert_nothing_raised { author.save }
end
end
@@ -1806,7 +1831,7 @@ class TestAutosaveAssociationOnAHasManyAssociationWithInverse < ActiveRecord::Te
end
class TestAutosaveAssociationOnAHasManyAssociationDefinedInSubclassWithAcceptsNestedAttributes < ActiveRecord::TestCase
- def test_should_update_children_when_asssociation_redefined_in_subclass
+ def test_should_update_children_when_association_redefined_in_subclass
agency = Agency.create!(name: "Agency")
valid_project = Project.create!(firm: agency, name: "Initial")
agency.update!(
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 363593ca19..ddafa468ed 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -67,6 +67,16 @@ end
class BasicsTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :author_addresses, :categorizations, :categories, :posts
+ def test_generated_association_methods_module_name
+ mod = Post.send(:generated_association_methods)
+ assert_equal "Post::GeneratedAssociationMethods", mod.inspect
+ end
+
+ def test_generated_relation_methods_module_name
+ mod = Post.send(:generated_relation_methods)
+ assert_equal "Post::GeneratedRelationMethods", mod.inspect
+ end
+
def test_column_names_are_escaped
conn = ActiveRecord::Base.connection
classname = conn.class.name[/[^:]*$/]
@@ -438,12 +448,6 @@ class BasicsTest < ActiveRecord::TestCase
Post.reset_table_name
end
- 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!")
- end
- end
-
def test_null_fields
assert_nil Topic.find(1).parent_id
assert_nil Topic.create("title" => "Hey you").parent_id
@@ -691,6 +695,9 @@ class BasicsTest < ActiveRecord::TestCase
topic = Topic.find(1)
topic.attributes = attributes
assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
+
+ topic.save!
+ assert_equal topic, Topic.find_by(attributes)
end
end
@@ -1038,11 +1045,6 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- def test_find_last
- last = Developer.last
- assert_equal last, Developer.all.merge!(order: "id desc").first
- end
-
def test_last
assert_equal Developer.all.merge!(order: "id desc").first, Developer.last
end
@@ -1058,23 +1060,23 @@ class BasicsTest < ActiveRecord::TestCase
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.order("developers.salary ASC").last
+ assert_equal last, Developer.order("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.order("developers.salary DESC").last
+ assert_equal last, Developer.order("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.order("developers.name, developers.salary DESC").last
+ assert_equal last, Developer.order(:"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.order("developers.name, developers.salary").to_a
+ assert_equal combined, Developer.order(:"developers.name", :"developers.salary").to_a
end
def test_find_keeps_multiple_group_values
@@ -1213,6 +1215,8 @@ class BasicsTest < ActiveRecord::TestCase
wr.close
assert Marshal.load rd.read
rd.close
+ ensure
+ self.class.send(:remove_const, "Post") if self.class.const_defined?("Post", false)
end
end
@@ -1226,14 +1230,15 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_attribute_names
- assert_equal ["id", "type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id", "description"],
- Company.attribute_names
+ expected = ["id", "type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id", "description", "metadata"]
+ assert_equal expected, Company.attribute_names
end
def test_has_attribute
assert Company.has_attribute?("id")
assert Company.has_attribute?("type")
assert Company.has_attribute?("name")
+ assert Company.has_attribute?("metadata")
assert_not Company.has_attribute?("lastname")
assert_not Company.has_attribute?("age")
end
@@ -1535,7 +1540,7 @@ class BasicsTest < ActiveRecord::TestCase
Bird.create!(name: "Bluejay")
ActiveRecord::Base.connection.while_preventing_writes do
- assert_nothing_raised { Bird.where(name: "Bluejay").explain }
+ assert_queries(2) { Bird.where(name: "Bluejay").explain }
end
end
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 22a98036f3..85685d1d00 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -2,6 +2,7 @@
require "cases/helper"
require "models/topic"
+require "models/reply"
require "models/author"
require "models/post"
@@ -34,6 +35,77 @@ if ActiveRecord::Base.connection.prepared_statements
ActiveSupport::Notifications.unsubscribe(@subscription)
end
+ def test_statement_cache
+ @connection.clear_cache!
+
+ topics = Topic.where(id: 1)
+ assert_equal [1], topics.map(&:id)
+ assert_includes statement_cache, to_sql_key(topics.arel)
+
+ @connection.clear_cache!
+
+ assert_not_includes statement_cache, to_sql_key(topics.arel)
+ end
+
+ def test_statement_cache_with_query_cache
+ @connection.enable_query_cache!
+ @connection.clear_cache!
+
+ topics = Topic.where(id: 1)
+ assert_equal [1], topics.map(&:id)
+ assert_includes statement_cache, to_sql_key(topics.arel)
+ ensure
+ @connection.disable_query_cache!
+ end
+
+ def test_statement_cache_with_find
+ @connection.clear_cache!
+
+ assert_equal 1, Topic.find(1).id
+ assert_raises(RecordNotFound) { SillyReply.find(2) }
+
+ topic_sql = cached_statement(Topic, Topic.primary_key)
+ assert_includes statement_cache, to_sql_key(topic_sql)
+
+ e = assert_raise { cached_statement(SillyReply, SillyReply.primary_key) }
+ assert_equal "SillyReply has no cached statement by \"id\"", e.message
+
+ replies = SillyReply.where(id: 2).limit(1)
+ assert_includes statement_cache, to_sql_key(replies.arel)
+ end
+
+ def test_statement_cache_with_find_by
+ @connection.clear_cache!
+
+ assert_equal 1, Topic.find_by!(id: 1).id
+ assert_raises(RecordNotFound) { SillyReply.find_by!(id: 2) }
+
+ topic_sql = cached_statement(Topic, [:id])
+ assert_includes statement_cache, to_sql_key(topic_sql)
+
+ e = assert_raise { cached_statement(SillyReply, [:id]) }
+ assert_equal "SillyReply has no cached statement by [:id]", e.message
+
+ replies = SillyReply.where(id: 2).limit(1)
+ assert_includes statement_cache, to_sql_key(replies.arel)
+ end
+
+ def test_statement_cache_with_in_clause
+ @connection.clear_cache!
+
+ topics = Topic.where(id: [1, 3])
+ assert_equal [1, 3], topics.map(&:id)
+ assert_not_includes statement_cache, to_sql_key(topics.arel)
+ end
+
+ def test_statement_cache_with_sql_string_literal
+ @connection.clear_cache!
+
+ topics = Topic.where("topics.id = ?", 1)
+ assert_equal [1], topics.map(&:id)
+ assert_not_includes statement_cache, to_sql_key(topics.arel)
+ end
+
def test_too_many_binds
bind_params_length = @connection.send(:bind_params_length)
@@ -44,6 +116,19 @@ if ActiveRecord::Base.connection.prepared_statements
assert_equal 0, topics.count
end
+ def test_too_many_binds_with_query_cache
+ @connection.enable_query_cache!
+
+ bind_params_length = @connection.send(:bind_params_length)
+ topics = Topic.where(id: (1 .. bind_params_length + 1).to_a)
+ assert_equal Topic.count, topics.count
+
+ topics = Topic.where.not(id: (1 .. bind_params_length + 1).to_a)
+ assert_equal 0, topics.count
+ ensure
+ @connection.disable_query_cache!
+ end
+
def test_bind_from_join_in_subquery
subquery = Author.joins(:thinking_posts).where(name: "David")
scope = Author.from(subquery, "authors").where(id: 1)
@@ -78,12 +163,28 @@ if ActiveRecord::Base.connection.prepared_statements
end
private
+ def to_sql_key(arel)
+ sql = @connection.to_sql(arel)
+ @connection.respond_to?(:sql_key, true) ? @connection.send(:sql_key, sql) : sql
+ end
+
+ def cached_statement(klass, key)
+ cache = klass.send(:cached_find_by_statement, key) do
+ raise "#{klass} has no cached statement by #{key.inspect}"
+ end
+ cache.send(:query_builder).instance_variable_get(:@sql)
+ end
+
+ def statement_cache
+ @connection.instance_variable_get(:@statements).send(:cache)
+ end
+
def assert_logs_binds(binds)
payload = {
name: "SQL",
sql: "select * from topics where id = ?",
binds: binds,
- type_casted_binds: @connection.type_casted_binds(binds)
+ type_casted_binds: @connection.send(:type_casted_binds, binds)
}
event = ActiveSupport::Notifications::Event.new(
diff --git a/activerecord/test/cases/boolean_test.rb b/activerecord/test/cases/boolean_test.rb
index ab9f974e2c..18824004d2 100644
--- a/activerecord/test/cases/boolean_test.rb
+++ b/activerecord/test/cases/boolean_test.rb
@@ -40,4 +40,13 @@ class BooleanTest < ActiveRecord::TestCase
assert_equal b_false, Boolean.find_by(value: "false")
assert_equal b_true, Boolean.find_by(value: "true")
end
+
+ def test_find_by_falsy_boolean_symbol
+ ActiveModel::Type::Boolean::FALSE_VALUES.each do |value|
+ b_false = Boolean.create!(value: value)
+
+ assert_not_predicate b_false, :value?
+ assert_equal b_false, Boolean.find_by(id: b_false.id, value: value.to_s.to_sym)
+ end
+ end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 850bc49676..16c2a3661d 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -19,6 +19,7 @@ require "models/developer"
require "models/post"
require "models/comment"
require "models/rating"
+require "support/stubs/strong_parameters"
class CalculationsTest < ActiveRecord::TestCase
fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books, :posts, :comments
@@ -242,6 +243,12 @@ class CalculationsTest < ActiveRecord::TestCase
assert_queries(1) { assert_equal 11, posts.count(:all) }
end
+ def test_count_with_eager_loading_and_custom_select_and_order
+ posts = Post.includes(:comments).order("comments.id").select(:type)
+ assert_queries(1) { assert_equal 11, posts.count }
+ assert_queries(1) { assert_equal 11, posts.count(:all) }
+ end
+
def test_count_with_eager_loading_and_custom_order_and_distinct
posts = Post.includes(:comments).order("comments.id").distinct
assert_queries(1) { assert_equal 11, posts.count }
@@ -278,6 +285,18 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 3, Account.joins(:firm).distinct.order(:firm_id).limit(3).offset(2).count
end
+ def test_distinct_joins_count_with_group_by
+ expected = { nil => 4, 1 => 1, 2 => 1, 4 => 1, 5 => 1, 7 => 1 }
+ assert_equal expected, Post.left_joins(:comments).group(:post_id).distinct.count(:author_id)
+ assert_equal expected, Post.left_joins(:comments).group(:post_id).distinct.select(:author_id).count
+ assert_equal expected, Post.left_joins(:comments).group(:post_id).count("DISTINCT posts.author_id")
+ assert_equal expected, Post.left_joins(:comments).group(:post_id).select("DISTINCT posts.author_id").count
+
+ expected = { nil => 6, 1 => 1, 2 => 1, 4 => 1, 5 => 1, 7 => 1 }
+ assert_equal expected, Post.left_joins(:comments).group(:post_id).distinct.count(:all)
+ assert_equal expected, Post.left_joins(:comments).group(:post_id).distinct.select(:author_id).count(:all)
+ end
+
def test_distinct_count_with_group_by_and_order_and_limit
assert_equal({ 6 => 2 }, Account.group(:firm_id).distinct.order("1 DESC").limit(1).count)
end
@@ -344,6 +363,17 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 60, c[2]
end
+ def test_should_calculate_grouped_with_longer_field
+ field = "a" * Account.connection.max_identifier_length
+
+ Account.update_all("#{field} = credit_limit")
+
+ c = Account.group(:firm_id).sum(field)
+ assert_equal 50, c[1]
+ assert_equal 105, c[6]
+ assert_equal 60, c[2]
+ end
+
def test_should_calculate_with_invalid_field
assert_equal 6, Account.calculate(:count, "*")
assert_equal 6, Account.calculate(:count, :all)
@@ -460,6 +490,24 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 6, Account.select("DISTINCT accounts.id").includes(:firm).count
end
+ def test_should_count_manual_select_with_count_all
+ assert_equal 5, Account.select("DISTINCT accounts.firm_id").count(:all)
+ end
+
+ def test_should_count_with_manual_distinct_select_and_distinct
+ assert_equal 4, Account.select("DISTINCT accounts.firm_id").distinct(true).count
+ end
+
+ def test_should_count_manual_select_with_group_with_count_all
+ expected = { nil => 1, 1 => 1, 2 => 1, 6 => 2, 9 => 1 }
+ actual = Account.select("DISTINCT accounts.firm_id").group("accounts.firm_id").count(:all)
+ assert_equal expected, actual
+ end
+
+ def test_should_count_manual_with_count_all
+ assert_equal 6, Account.count(:all)
+ end
+
def test_count_selected_arel_attribute
assert_equal 5, Account.select(Account.arel_table[:firm_id]).count
assert_equal 4, Account.distinct.select(Account.arel_table[:firm_id]).count
@@ -511,8 +559,10 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_count_field_of_root_table_with_conflicting_group_by_column
- assert_equal({ 1 => 1 }, Firm.joins(:accounts).group(:firm_id).count)
- assert_equal({ 1 => 1 }, Firm.joins(:accounts).group("accounts.firm_id").count)
+ expected = { 1 => 2, 2 => 1, 4 => 5, 5 => 2, 7 => 1 }
+ assert_equal expected, Post.joins(:comments).group(:post_id).count
+ assert_equal expected, Post.joins(:comments).group("comments.post_id").count
+ assert_equal expected, Post.joins(:comments).group(:post_id).select("DISTINCT posts.author_id").count(:all)
end
def test_count_with_no_parameters_isnt_deprecated
@@ -690,8 +740,9 @@ class CalculationsTest < ActiveRecord::TestCase
end
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)
+ company = Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
+ metadata = company.contracts.first.metadata
+ assert_equal [metadata], Company.joins(:contracts).pluck(:metadata)
end
def test_pluck_with_selection_clause
@@ -882,26 +933,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_having_with_strong_parameters
- protected_params = Class.new do
- attr_reader :permitted
- alias :permitted? :permitted
-
- def initialize(parameters)
- @parameters = parameters
- @permitted = false
- end
-
- def to_h
- @parameters
- end
-
- def permit!
- @permitted = true
- self
- end
- end
-
- params = protected_params.new(credit_limit: "50")
+ params = ProtectedParams.new(credit_limit: "50")
assert_raises(ActiveModel::ForbiddenAttributesError) do
Account.group(:id).having(params)
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 4d6a112af5..b4026078f1 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -458,10 +458,6 @@ class CallbacksTest < ActiveRecord::TestCase
[ :before_validation, :object ],
[ :before_validation, :block ],
[ :before_validation, :throwing_abort ],
- [ :after_rollback, :block ],
- [ :after_rollback, :object ],
- [ :after_rollback, :proc ],
- [ :after_rollback, :method ],
], david.history
end
diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb
index 483383257b..f07f3c42e6 100644
--- a/activerecord/test/cases/collection_cache_key_test.rb
+++ b/activerecord/test/cases/collection_cache_key_test.rb
@@ -171,5 +171,39 @@ module ActiveRecord
assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
end
+
+ test "cache_key should be stable when using collection_cache_versioning" do
+ with_collection_cache_versioning do
+ developers = Developer.where(salary: 100000)
+
+ assert_match(/\Adevelopers\/query-(\h+)\z/, developers.cache_key)
+
+ /\Adevelopers\/query-(\h+)\z/ =~ developers.cache_key
+
+ assert_equal ActiveSupport::Digest.hexdigest(developers.to_sql), $1
+ end
+ end
+
+ test "cache_version for relation" do
+ with_collection_cache_versioning do
+ developers = Developer.where(salary: 100000).order(updated_at: :desc)
+ last_developer_timestamp = developers.first.updated_at
+
+ assert_match(/(\d+)-(\d+)\z/, developers.cache_version)
+
+ /(\d+)-(\d+)\z/ =~ developers.cache_version
+
+ assert_equal developers.count.to_s, $1
+ assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $2
+ end
+ end
+
+ def with_collection_cache_versioning(value = true)
+ @old_collection_cache_versioning = ActiveRecord::Base.collection_cache_versioning
+ ActiveRecord::Base.collection_cache_versioning = value
+ yield
+ ensure
+ ActiveRecord::Base.collection_cache_versioning = @old_collection_cache_versioning
+ end
end
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
index 36591097b6..d3184f39f5 100644
--- a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
@@ -203,26 +203,53 @@ module ActiveRecord
assert_equal "must provide a `database` or a `role`.", error.message
end
- def test_switching_connections_with_database_symbol
+ def test_switching_connections_with_database_symbol_uses_default_role
previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
config = {
"default_env" => {
- "readonly" => { adapter: "sqlite3", database: "db/readonly.sqlite3" },
- "primary" => { adapter: "sqlite3", database: "db/primary.sqlite3" }
+ "animals" => { adapter: "sqlite3", database: "db/animals.sqlite3" },
+ "primary" => { adapter: "sqlite3", database: "db/primary.sqlite3" }
}
}
@prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
- ActiveRecord::Base.connected_to(database: :readonly) do
- assert_equal :readonly, ActiveRecord::Base.current_role
- assert ActiveRecord::Base.connected_to?(role: :readonly)
+ ActiveRecord::Base.connected_to(database: :animals) do
+ assert_equal :writing, ActiveRecord::Base.current_role
+ assert ActiveRecord::Base.connected_to?(role: :writing)
handler = ActiveRecord::Base.connection_handler
- assert_equal handler, ActiveRecord::Base.connection_handlers[:readonly]
+ assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
+
+ assert_not_nil pool = handler.retrieve_connection_pool("primary")
+ assert_equal(config["default_env"]["animals"], pool.spec.config)
+ end
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ActiveRecord::Base.establish_connection(:arunit)
+ ENV["RAILS_ENV"] = previous_env
+ end
+
+ def test_switching_connections_with_database_hash_uses_passed_role_and_database
+ previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
+
+ config = {
+ "default_env" => {
+ "animals" => { adapter: "sqlite3", database: "db/animals.sqlite3" },
+ "primary" => { adapter: "sqlite3", database: "db/primary.sqlite3" }
+ }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ ActiveRecord::Base.connected_to(database: { writing: :primary }) do
+ assert_equal :writing, ActiveRecord::Base.current_role
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+
+ handler = ActiveRecord::Base.connection_handler
+ assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
assert_not_nil pool = handler.retrieve_connection_pool("primary")
- assert_equal(config["default_env"]["readonly"], pool.spec.config)
+ assert_equal(config["default_env"]["primary"], pool.spec.config)
end
ensure
ActiveRecord::Base.configurations = @prev_configs
diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
index 225cccc62c..515bf5df06 100644
--- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -72,6 +72,16 @@ module ActiveRecord
assert_equal expected, actual
end
+ def test_resolver_with_database_uri_and_multiple_envs
+ ENV["DATABASE_URL"] = "postgres://localhost"
+ ENV["RAILS_ENV"] = "test"
+
+ config = { "production" => { "adapter" => "postgresql", "database" => "foo_prod" }, "test" => { "adapter" => "postgresql", "database" => "foo_test" } }
+ actual = resolve_spec(:test, config)
+ expected = { "adapter" => "postgresql", "database" => "foo_test", "host" => "localhost", "name" => "test" }
+ assert_equal expected, actual
+ end
+
def test_resolver_with_database_uri_and_unknown_symbol_key
ENV["DATABASE_URL"] = "postgres://localhost/foo"
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
index 727cab77f5..28e232b88f 100644
--- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb
+++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
@@ -6,8 +6,9 @@ module ActiveRecord
module ConnectionAdapters
class SchemaCacheTest < ActiveRecord::TestCase
def setup
- connection = ActiveRecord::Base.connection
- @cache = SchemaCache.new connection
+ @connection = ActiveRecord::Base.connection
+ @cache = SchemaCache.new @connection
+ @database_version = @connection.get_database_version
end
def test_primary_key
@@ -19,6 +20,7 @@ module ActiveRecord
@cache.columns_hash("posts")
@cache.data_sources("posts")
@cache.primary_keys("posts")
+ @cache.indexes("posts")
new_cache = YAML.load(YAML.dump(@cache))
assert_no_queries do
@@ -26,19 +28,47 @@ module ActiveRecord
assert_equal 12, new_cache.columns_hash("posts").size
assert new_cache.data_sources("posts")
assert_equal "id", new_cache.primary_keys("posts")
+ assert_equal 1, new_cache.indexes("posts").size
+ assert_equal @database_version.to_s, new_cache.database_version.to_s
end
end
def test_yaml_loads_5_1_dump
- body = File.open(schema_dump_path).read
- cache = YAML.load(body)
+ @cache = YAML.load(File.read(schema_dump_path))
assert_no_queries do
- assert_equal 11, cache.columns("posts").size
- assert_equal 11, cache.columns_hash("posts").size
- assert cache.data_sources("posts")
- assert_equal "id", cache.primary_keys("posts")
+ assert_equal 11, @cache.columns("posts").size
+ assert_equal 11, @cache.columns_hash("posts").size
+ assert @cache.data_sources("posts")
+ assert_equal "id", @cache.primary_keys("posts")
+ end
+ end
+
+ def test_yaml_loads_5_1_dump_without_indexes_still_queries_for_indexes
+ @cache = YAML.load(File.read(schema_dump_path))
+
+ # Simulate assignment in railtie after loading the cache.
+ old_cache, @connection.schema_cache = @connection.schema_cache, @cache
+
+ assert_queries :any, ignore_none: true do
+ assert_equal 1, @cache.indexes("posts").size
end
+ ensure
+ @connection.schema_cache = old_cache
+ end
+
+ def test_yaml_loads_5_1_dump_without_database_version_still_queries_for_database_version
+ @cache = YAML.load(File.read(schema_dump_path))
+
+ # Simulate assignment in railtie after loading the cache.
+ old_cache, @connection.schema_cache = @connection.schema_cache, @cache
+
+ # We can't verify queries get executed because the database version gets
+ # cached in both MySQL and PostgreSQL outside of the schema cache.
+ assert_nil @cache.instance_variable_get(:@database_version)
+ assert_equal @database_version.to_s, @cache.database_version.to_s
+ ensure
+ @connection.schema_cache = old_cache
end
def test_primary_key_for_non_existent_table
@@ -55,15 +85,34 @@ module ActiveRecord
assert_equal columns_hash, @cache.columns_hash("posts")
end
+ def test_caches_indexes
+ indexes = @cache.indexes("posts")
+ assert_equal indexes, @cache.indexes("posts")
+ end
+
+ def test_caches_database_version
+ @cache.database_version # cache database_version
+
+ assert_no_queries do
+ assert_equal @database_version.to_s, @cache.database_version.to_s
+
+ if current_adapter?(:Mysql2Adapter)
+ assert_not_nil @cache.database_version.full_version_string
+ end
+ end
+ end
+
def test_clearing
@cache.columns("posts")
@cache.columns_hash("posts")
@cache.data_sources("posts")
@cache.primary_keys("posts")
+ @cache.indexes("posts")
@cache.clear!
assert_equal 0, @cache.size
+ assert_nil @cache.instance_variable_get(:@database_version)
end
def test_dump_and_load
@@ -71,6 +120,7 @@ module ActiveRecord
@cache.columns_hash("posts")
@cache.data_sources("posts")
@cache.primary_keys("posts")
+ @cache.indexes("posts")
@cache = Marshal.load(Marshal.dump(@cache))
@@ -79,6 +129,8 @@ module ActiveRecord
assert_equal 12, @cache.columns_hash("posts").size
assert @cache.data_sources("posts")
assert_equal "id", @cache.primary_keys("posts")
+ assert_equal 1, @cache.indexes("posts").size
+ assert_equal @database_version.to_s, @cache.database_version.to_s
end
end
@@ -108,7 +160,6 @@ module ActiveRecord
end
private
-
def schema_dump_path
"test/assets/schema_dump_5_1.yml"
end
diff --git a/activerecord/test/cases/database_configurations_test.rb b/activerecord/test/cases/database_configurations_test.rb
new file mode 100644
index 0000000000..ed8151f01a
--- /dev/null
+++ b/activerecord/test/cases/database_configurations_test.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+
+class DatabaseConfigurationsTest < ActiveRecord::TestCase
+ unless in_memory_db?
+ def test_empty_returns_true_when_db_configs_are_empty
+ old_config = ActiveRecord::Base.configurations
+ config = {}
+
+ ActiveRecord::Base.configurations = config
+
+ assert_predicate ActiveRecord::Base.configurations, :empty?
+ assert_predicate ActiveRecord::Base.configurations, :blank?
+ ensure
+ ActiveRecord::Base.configurations = old_config
+ ActiveRecord::Base.establish_connection :arunit
+ end
+ end
+
+ def test_configs_for_getter_with_env_name
+ configs = ActiveRecord::Base.configurations.configs_for(env_name: "arunit")
+
+ assert_equal 1, configs.size
+ assert_equal ["arunit"], configs.map(&:env_name)
+ end
+
+ def test_configs_for_getter_with_env_and_spec_name
+ config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", spec_name: "primary")
+
+ assert_equal "arunit", config.env_name
+ assert_equal "primary", config.spec_name
+ end
+
+ def test_default_hash_returns_config_hash_from_default_env
+ original_rails_env = ENV["RAILS_ENV"]
+ ENV["RAILS_ENV"] = "arunit"
+
+ assert_equal ActiveRecord::Base.configurations.configs_for(env_name: "arunit", spec_name: "primary").config, ActiveRecord::Base.configurations.default_hash
+ ensure
+ ENV["RAILS_ENV"] = original_rails_env
+ end
+
+ def test_find_db_config_returns_a_db_config_object_for_the_given_env
+ config = ActiveRecord::Base.configurations.find_db_config("arunit2")
+
+ assert_equal "arunit2", config.env_name
+ assert_equal "primary", config.spec_name
+ end
+
+ def test_to_h_turns_db_config_object_back_into_a_hash
+ configs = ActiveRecord::Base.configurations
+ assert_equal "ActiveRecord::DatabaseConfigurations", configs.class.name
+ assert_equal "Hash", configs.to_h.class.name
+ assert_equal ["arunit", "arunit2", "arunit_without_prepared_statements"], ActiveRecord::Base.configurations.to_h.keys.sort
+ end
+end
+
+class LegacyDatabaseConfigurationsTest < ActiveRecord::TestCase
+ unless in_memory_db?
+ def test_setting_configurations_hash
+ old_config = ActiveRecord::Base.configurations
+ config = { "adapter" => "sqlite3" }
+
+ assert_deprecated do
+ ActiveRecord::Base.configurations["readonly"] = config
+ end
+
+ assert_equal ["arunit", "arunit2", "arunit_without_prepared_statements", "readonly"], ActiveRecord::Base.configurations.configs_for.map(&:env_name).sort
+ ensure
+ ActiveRecord::Base.configurations = old_config
+ ActiveRecord::Base.establish_connection :arunit
+ end
+ end
+
+ def test_can_turn_configurations_into_a_hash
+ assert ActiveRecord::Base.configurations.to_h.is_a?(Hash), "expected to be a hash but was not."
+ assert_equal ["arunit", "arunit2", "arunit_without_prepared_statements"].sort, ActiveRecord::Base.configurations.to_h.keys.sort
+ end
+
+ def test_each_is_deprecated
+ assert_deprecated do
+ ActiveRecord::Base.configurations.each do |db_config|
+ assert_equal "primary", db_config.spec_name
+ end
+ end
+ end
+
+ def test_first_is_deprecated
+ assert_deprecated do
+ db_config = ActiveRecord::Base.configurations.first
+ assert_equal "arunit", db_config.env_name
+ assert_equal "primary", db_config.spec_name
+ end
+ end
+
+ def test_fetch_is_deprecated
+ assert_deprecated do
+ db_config = ActiveRecord::Base.configurations.fetch("arunit").first
+ assert_equal "arunit", db_config.env_name
+ assert_equal "primary", db_config.spec_name
+ end
+ end
+
+ def test_values_are_deprecated
+ config_hashes = ActiveRecord::Base.configurations.configurations.map(&:config)
+ assert_deprecated do
+ assert_equal config_hashes, ActiveRecord::Base.configurations.values
+ end
+ end
+
+ def test_unsupported_method_raises
+ assert_raises NotImplementedError do
+ ActiveRecord::Base.configurations.select { |a| a == "foo" }
+ end
+ end
+end
diff --git a/activerecord/test/cases/database_selector_test.rb b/activerecord/test/cases/database_selector_test.rb
index 4106a6ec46..fd02d2acb4 100644
--- a/activerecord/test/cases/database_selector_test.rb
+++ b/activerecord/test/cases/database_selector_test.rb
@@ -11,6 +11,10 @@ module ActiveRecord
@session = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session.new(@session_store)
end
+ teardown do
+ ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
+ end
+
def test_empty_session
assert_equal Time.at(0), @session.last_write_timestamp
end
diff --git a/activerecord/test/cases/date_test.rb b/activerecord/test/cases/date_test.rb
index 9f412cdb63..2475d4a34b 100644
--- a/activerecord/test/cases/date_test.rb
+++ b/activerecord/test/cases/date_test.rb
@@ -23,23 +23,13 @@ class DateTest < ActiveRecord::TestCase
valid_dates.each do |date_src|
topic = Topic.new("last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s)
- # Oracle DATE columns are datetime columns and Oracle adapter returns Time value
- if current_adapter?(:OracleAdapter)
- assert_equal(topic.last_read.to_date, Date.new(*date_src))
- else
- assert_equal(topic.last_read, Date.new(*date_src))
- end
+ assert_equal(topic.last_read, Date.new(*date_src))
end
invalid_dates.each do |date_src|
assert_nothing_raised do
topic = Topic.new("last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s)
- # Oracle DATE columns are datetime columns and Oracle adapter returns Time value
- if current_adapter?(:OracleAdapter)
- assert_equal(topic.last_read.to_date, Time.local(*date_src).to_date, "The date should be modified according to the behavior of the Time object")
- else
- assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behavior of the Time object")
- end
+ assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behavior of the Time object")
end
end
end
diff --git a/activerecord/test/cases/date_time_precision_test.rb b/activerecord/test/cases/date_time_precision_test.rb
index e64a8372d0..79d63949ca 100644
--- a/activerecord/test/cases/date_time_precision_test.rb
+++ b/activerecord/test/cases/date_time_precision_test.rb
@@ -29,7 +29,7 @@ if subsecond_precision_supported?
def test_datetime_precision_is_truncated_on_assignment
@connection.create_table(:foos, force: true)
- @connection.add_column :foos, :created_at, :datetime, precision: 0
+ @connection.add_column :foos, :created_at, :datetime, precision: 0
@connection.add_column :foos, :updated_at, :datetime, precision: 6
time = ::Time.now.change(nsec: 123456789)
@@ -45,6 +45,26 @@ if subsecond_precision_supported?
assert_equal 123456000, foo.updated_at.nsec
end
+ unless current_adapter?(:Mysql2Adapter)
+ def test_no_datetime_precision_isnt_truncated_on_assignment
+ @connection.create_table(:foos, force: true)
+ @connection.add_column :foos, :created_at, :datetime
+ @connection.add_column :foos, :updated_at, :datetime, precision: 6
+
+ time = ::Time.now.change(nsec: 123)
+ foo = Foo.new(created_at: time, updated_at: time)
+
+ assert_equal 123, foo.created_at.nsec
+ assert_equal 0, foo.updated_at.nsec
+
+ foo.save!
+ foo.reload
+
+ assert_equal 0, foo.created_at.nsec
+ assert_equal 0, foo.updated_at.nsec
+ end
+ end
+
def test_timestamps_helper_with_custom_precision
@connection.create_table(:foos, force: true) do |t|
t.timestamps precision: 4
@@ -62,7 +82,7 @@ if subsecond_precision_supported?
end
def test_invalid_datetime_precision_raises_error
- assert_raises ActiveRecord::ActiveRecordError do
+ assert_raises ArgumentError do
@connection.create_table(:foos, force: true) do |t|
t.timestamps precision: 7
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index 5d02e59ef6..50a86b0a19 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -89,7 +89,7 @@ if current_adapter?(:PostgreSQLAdapter)
test "schema dump includes default expression" do
output = dump_table_schema("defaults")
- if ActiveRecord::Base.connection.postgresql_version >= 100000
+ if ActiveRecord::Base.connection.database_version >= 100000
assert_match %r/t\.date\s+"modified_date",\s+default: -> { "CURRENT_DATE" }/, output
assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
else
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index dfd74bfcb4..a2a501a794 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -352,7 +352,7 @@ class DirtyTest < ActiveRecord::TestCase
Person.where(id: person.id).update_all(first_name: "baz")
end
- old_lock_version = person.lock_version
+ old_lock_version = person.lock_version + 1
with_partial_writes Person, true do
assert_no_queries { 2.times { person.save! } }
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index 8a0f6f6df1..ae0ce195b3 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -44,6 +44,11 @@ class EnumTest < ActiveRecord::TestCase
assert_equal books(:rfr), authors(:david).unpublished_books.first
end
+ test "find via negative scope" do
+ assert Book.not_published.exclude?(@book)
+ assert Book.not_proposed.include?(@book)
+ end
+
test "find via where with values" do
published, written = Book.statuses[:published], Book.statuses[:written]
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 4040682280..ca114d468e 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -3,6 +3,7 @@
require "cases/helper"
require "models/post"
require "models/author"
+require "models/account"
require "models/categorization"
require "models/comment"
require "models/company"
@@ -226,14 +227,14 @@ class FinderTest < ActiveRecord::TestCase
end
def test_exists_with_strong_parameters
- assert_equal false, Subscriber.exists?(Parameters.new(nick: "foo").permit!)
+ assert_equal false, Subscriber.exists?(ProtectedParams.new(nick: "foo").permit!)
Subscriber.create!(nick: "foo")
- assert_equal true, Subscriber.exists?(Parameters.new(nick: "foo").permit!)
+ assert_equal true, Subscriber.exists?(ProtectedParams.new(nick: "foo").permit!)
assert_raises(ActiveModel::ForbiddenAttributesError) do
- Subscriber.exists?(Parameters.new(nick: "foo"))
+ Subscriber.exists?(ProtectedParams.new(nick: "foo"))
end
end
@@ -461,14 +462,14 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_by_association_subquery
- author = authors(:david)
- assert_equal author.post, Post.find_by(author: Author.where(id: author))
- assert_equal author.post, Post.find_by(author_id: Author.where(id: author))
+ firm = companies(:first_firm)
+ assert_equal firm.account, Account.find_by(firm: Firm.where(id: firm))
+ assert_equal firm.account, Account.find_by(firm_id: Firm.where(id: firm))
end
def test_find_by_and_where_consistency_with_active_record_instance
- author = authors(:david)
- assert_equal Post.where(author_id: author).take, Post.find_by(author_id: author)
+ firm = companies(:first_firm)
+ assert_equal Account.where(firm_id: firm).take, Account.find_by(firm_id: firm)
end
def test_take
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index b4f28fbfd6..0cb868da6e 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -209,7 +209,7 @@ class FixturesTest < ActiveRecord::TestCase
conn = ActiveRecord::Base.connection
mysql_margin = 2
packet_size = 1024
- bytes_needed_to_have_a_1024_bytes_fixture = 858
+ bytes_needed_to_have_a_1024_bytes_fixture = 906
fixtures = {
"traffic_lights" => [
{ "location" => "US", "state" => ["NY"], "long_state" => ["a" * bytes_needed_to_have_a_1024_bytes_fixture] },
@@ -496,11 +496,7 @@ class FixturesTest < ActiveRecord::TestCase
ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/naked/yml", "parrots")
end
- if current_adapter?(:SQLite3Adapter)
- assert_equal(%(table "parrots" has no column named "arrr".), e.message)
- else
- assert_equal(%(table "parrots" has no columns named "arrr", "foobar".), e.message)
- end
+ assert_equal(%(table "parrots" has no columns named "arrr", "foobar".), e.message)
end
def test_yaml_file_with_symbol_columns
diff --git a/activerecord/test/cases/forbidden_attributes_protection_test.rb b/activerecord/test/cases/forbidden_attributes_protection_test.rb
index 101fa118c8..e7e31b6d2d 100644
--- a/activerecord/test/cases/forbidden_attributes_protection_test.rb
+++ b/activerecord/test/cases/forbidden_attributes_protection_test.rb
@@ -1,48 +1,12 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_support/core_ext/hash/indifferent_access"
-
require "models/company"
require "models/person"
require "models/ship"
require "models/ship_part"
require "models/treasure"
-
-class ProtectedParams
- attr_accessor :permitted
- alias :permitted? :permitted
-
- delegate :keys, :key?, :has_key?, :empty?, to: :@parameters
-
- def initialize(attributes)
- @parameters = attributes.with_indifferent_access
- @permitted = false
- end
-
- def permit!
- @permitted = true
- self
- end
-
- def [](key)
- @parameters[key]
- end
-
- def to_h
- @parameters
- end
-
- def stringify_keys
- dup
- end
-
- def dup
- super.tap do |duplicate|
- duplicate.instance_variable_set :@permitted, @permitted
- end
- end
-end
+require "support/stubs/strong_parameters"
class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase
def test_forbidden_attributes_cannot_be_used_for_mass_assignment
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 730cd663a2..543a0aeb39 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -53,12 +53,22 @@ def supports_default_expression?
true
elsif current_adapter?(:Mysql2Adapter)
conn = ActiveRecord::Base.connection
- !conn.mariadb? && conn.version >= "8.0.13"
+ !conn.mariadb? && conn.database_version >= "8.0.13"
end
end
-def supports_savepoints?
- ActiveRecord::Base.connection.supports_savepoints?
+%w[
+ supports_savepoints?
+ supports_partial_index?
+ supports_insert_returning?
+ supports_insert_on_duplicate_skip?
+ supports_insert_on_duplicate_update?
+ supports_insert_conflict_target?
+ supports_optimizer_hints?
+].each do |method_name|
+ define_method method_name do
+ ActiveRecord::Base.connection.public_send(method_name)
+ end
end
def with_env_tz(new_tz = "US/Eastern")
@@ -192,3 +202,5 @@ module InTimeZone
ActiveRecord::Base.time_zone_aware_attributes = old_tz
end
end
+
+require_relative "../../../tools/test_common"
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 19655a2d38..629167e9ed 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -514,10 +514,12 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
# Should fail without FirmOnTheFly in the type condition.
assert_raise(ActiveRecord::RecordNotFound) { Firm.find(foo.id) }
+ assert_raise(ActiveRecord::RecordNotFound) { Firm.find_by!(id: foo.id) }
# Nest FirmOnTheFly in the test case where Dependencies won't see it.
self.class.const_set :FirmOnTheFly, Class.new(Firm)
assert_raise(ActiveRecord::SubclassNotFound) { Firm.find(foo.id) }
+ assert_raise(ActiveRecord::SubclassNotFound) { Firm.find_by!(id: foo.id) }
# Nest FirmOnTheFly in Firm where Dependencies will see it.
# This is analogous to nesting models in a migration.
@@ -526,6 +528,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
# And instantiate will find the existing constant rather than trying
# to require firm_on_the_fly.
assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) }
+ assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find_by!(id: foo.id) }
end
end
diff --git a/activerecord/test/cases/insert_all_test.rb b/activerecord/test/cases/insert_all_test.rb
new file mode 100644
index 0000000000..f24c63031c
--- /dev/null
+++ b/activerecord/test/cases/insert_all_test.rb
@@ -0,0 +1,276 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/book"
+
+class ReadonlyNameBook < Book
+ attr_readonly :name
+end
+
+class InsertAllTest < ActiveRecord::TestCase
+ fixtures :books
+
+ def test_insert
+ skip unless supports_insert_on_duplicate_skip?
+
+ id = 1_000_000
+
+ assert_difference "Book.count", +1 do
+ Book.insert(id: id, name: "Rework", author_id: 1)
+ end
+
+ Book.upsert(id: id, name: "Remote", author_id: 1)
+
+ assert_equal "Remote", Book.find(id).name
+ end
+
+ def test_insert!
+ assert_difference "Book.count", +1 do
+ Book.insert! name: "Rework", author_id: 1
+ end
+ end
+
+ def test_insert_all
+ assert_difference "Book.count", +10 do
+ Book.insert_all! [
+ { name: "Rework", author_id: 1 },
+ { name: "Patterns of Enterprise Application Architecture", author_id: 1 },
+ { name: "Design of Everyday Things", author_id: 1 },
+ { name: "Practical Object-Oriented Design in Ruby", author_id: 1 },
+ { name: "Clean Code", author_id: 1 },
+ { name: "Ruby Under a Microscope", author_id: 1 },
+ { name: "The Principles of Product Development Flow", author_id: 1 },
+ { name: "Peopleware", author_id: 1 },
+ { name: "About Face", author_id: 1 },
+ { name: "Eloquent Ruby", author_id: 1 },
+ ]
+ end
+ end
+
+ def test_insert_all_should_handle_empty_arrays
+ assert_raise ArgumentError do
+ Book.insert_all! []
+ end
+ end
+
+ def test_insert_all_raises_on_duplicate_records
+ assert_raise ActiveRecord::RecordNotUnique do
+ Book.insert_all! [
+ { name: "Rework", author_id: 1 },
+ { name: "Patterns of Enterprise Application Architecture", author_id: 1 },
+ { name: "Agile Web Development with Rails", author_id: 1 },
+ ]
+ end
+ end
+
+ def test_insert_all_returns_ActiveRecord_Result
+ result = Book.insert_all! [{ name: "Rework", author_id: 1 }]
+ assert_kind_of ActiveRecord::Result, result
+ end
+
+ def test_insert_all_returns_primary_key_if_returning_is_supported
+ skip unless supports_insert_returning?
+
+ result = Book.insert_all! [{ name: "Rework", author_id: 1 }]
+ assert_equal %w[ id ], result.columns
+ end
+
+ def test_insert_all_returns_nothing_if_returning_is_empty
+ skip unless supports_insert_returning?
+
+ result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: []
+ assert_equal [], result.columns
+ end
+
+ def test_insert_all_returns_nothing_if_returning_is_false
+ skip unless supports_insert_returning?
+
+ result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: false
+ assert_equal [], result.columns
+ end
+
+ def test_insert_all_returns_requested_fields
+ skip unless supports_insert_returning?
+
+ result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: [:id, :name]
+ assert_equal %w[ Rework ], result.pluck("name")
+ end
+
+ def test_insert_all_can_skip_duplicate_records
+ skip unless supports_insert_on_duplicate_skip?
+
+ assert_no_difference "Book.count" do
+ Book.insert_all [{ id: 1, name: "Agile Web Development with Rails" }]
+ end
+ end
+
+ def test_insert_all_with_skip_duplicates_and_autonumber_id_not_given
+ skip unless supports_insert_on_duplicate_skip?
+
+ assert_difference "Book.count", 1 do
+ # These two books are duplicates according to an index on %i[author_id name]
+ # but their IDs are not specified so they will be assigned different IDs
+ # by autonumber. We will get an exception from MySQL if we attempt to skip
+ # one of these records by assigning its ID.
+ Book.insert_all [
+ { author_id: 8, name: "Refactoring" },
+ { author_id: 8, name: "Refactoring" }
+ ]
+ end
+ end
+
+ def test_insert_all_with_skip_duplicates_and_autonumber_id_given
+ skip unless supports_insert_on_duplicate_skip?
+
+ assert_difference "Book.count", 1 do
+ Book.insert_all [
+ { id: 200, author_id: 8, name: "Refactoring" },
+ { id: 201, author_id: 8, name: "Refactoring" }
+ ]
+ end
+ end
+
+ def test_skip_duplicates_strategy_does_not_secretly_upsert
+ skip unless supports_insert_on_duplicate_skip?
+
+ book = Book.create!(author_id: 8, name: "Refactoring", format: "EXPECTED")
+
+ assert_no_difference "Book.count" do
+ Book.insert(author_id: 8, name: "Refactoring", format: "UNEXPECTED")
+ end
+
+ assert_equal "EXPECTED", book.reload.format
+ end
+
+ def test_insert_all_will_raise_if_duplicates_are_skipped_only_for_a_certain_conflict_target
+ skip unless supports_insert_on_duplicate_skip? && supports_insert_conflict_target?
+
+ assert_raise ActiveRecord::RecordNotUnique do
+ Book.insert_all [{ id: 1, name: "Agile Web Development with Rails" }],
+ unique_by: :index_books_on_author_id_and_name
+ end
+ end
+
+ def test_insert_all_and_upsert_all_with_index_finding_options
+ skip unless supports_insert_conflict_target?
+
+ assert_difference "Book.count", +3 do
+ Book.insert_all [{ name: "Rework", author_id: 1 }], unique_by: :isbn
+ Book.insert_all [{ name: "Remote", author_id: 1 }], unique_by: %i( author_id name )
+ Book.insert_all [{ name: "Renote", author_id: 1 }], unique_by: :index_books_on_isbn
+ end
+
+ assert_raise ActiveRecord::RecordNotUnique do
+ Book.upsert_all [{ name: "Rework", author_id: 1 }], unique_by: :isbn
+ end
+ end
+
+ def test_insert_all_and_upsert_all_raises_when_index_is_missing
+ skip unless supports_insert_conflict_target?
+
+ [ :cats, %i( author_id isbn ), :author_id ].each do |missing_or_non_unique_by|
+ error = assert_raises ArgumentError do
+ Book.insert_all [{ name: "Rework", author_id: 1 }], unique_by: missing_or_non_unique_by
+ end
+ assert_match "No unique index", error.message
+
+ error = assert_raises ArgumentError do
+ Book.upsert_all [{ name: "Rework", author_id: 1 }], unique_by: missing_or_non_unique_by
+ end
+ assert_match "No unique index", error.message
+ end
+ end
+
+ def test_insert_logs_message_including_model_name
+ skip unless supports_insert_conflict_target?
+
+ capture_log_output do |output|
+ Book.insert(name: "Rework", author_id: 1)
+ assert_match "Book Insert", output.string
+ end
+ end
+
+ def test_insert_all_logs_message_including_model_name
+ skip unless supports_insert_conflict_target?
+
+ capture_log_output do |output|
+ Book.insert_all [{ name: "Remote", author_id: 1 }, { name: "Renote", author_id: 1 }]
+ assert_match "Book Bulk Insert", output.string
+ end
+ end
+
+ def test_upsert_logs_message_including_model_name
+ skip unless supports_insert_on_duplicate_update?
+
+ capture_log_output do |output|
+ Book.upsert(name: "Remote", author_id: 1)
+ assert_match "Book Upsert", output.string
+ end
+ end
+
+ def test_upsert_all_logs_message_including_model_name
+ skip unless supports_insert_on_duplicate_update?
+
+ capture_log_output do |output|
+ Book.upsert_all [{ name: "Remote", author_id: 1 }, { name: "Renote", author_id: 1 }]
+ assert_match "Book Bulk Upsert", output.string
+ end
+ end
+
+ def test_upsert_all_updates_existing_records
+ skip unless supports_insert_on_duplicate_update?
+
+ new_name = "Agile Web Development with Rails, 4th Edition"
+ Book.upsert_all [{ id: 1, name: new_name }]
+ assert_equal new_name, Book.find(1).name
+ end
+
+ def test_upsert_all_does_not_update_readonly_attributes
+ skip unless supports_insert_on_duplicate_update?
+
+ new_name = "Agile Web Development with Rails, 4th Edition"
+ ReadonlyNameBook.upsert_all [{ id: 1, name: new_name }]
+ assert_not_equal new_name, Book.find(1).name
+ end
+
+ def test_upsert_all_does_not_update_primary_keys
+ skip unless supports_insert_on_duplicate_update? && supports_insert_conflict_target?
+
+ Book.upsert_all [{ id: 101, name: "Perelandra", author_id: 7 }]
+ Book.upsert_all [{ id: 103, name: "Perelandra", author_id: 7, isbn: "1974522598" }],
+ unique_by: :index_books_on_author_id_and_name
+
+ book = Book.find_by(name: "Perelandra")
+ assert_equal 101, book.id, "Should not have updated the ID"
+ assert_equal "1974522598", book.isbn, "Should have updated the isbn"
+ end
+
+ def test_upsert_all_does_not_perform_an_upsert_if_a_partial_index_doesnt_apply
+ skip unless supports_insert_on_duplicate_update? && supports_insert_conflict_target? && supports_partial_index?
+
+ Book.upsert_all [{ name: "Out of the Silent Planet", author_id: 7, isbn: "1974522598", published_on: Date.new(1938, 4, 1) }]
+ Book.upsert_all [{ name: "Perelandra", author_id: 7, isbn: "1974522598" }],
+ unique_by: :index_books_on_isbn
+
+ assert_equal ["Out of the Silent Planet", "Perelandra"], Book.where(isbn: "1974522598").order(:name).pluck(:name)
+ end
+
+ def test_insert_all_raises_on_unknown_attribute
+ assert_raise ActiveRecord::UnknownAttributeError do
+ Book.insert_all! [{ unknown_attribute: "Test" }]
+ end
+ end
+
+ private
+
+ def capture_log_output
+ output = StringIO.new
+ old_logger, ActiveRecord::Base.logger = ActiveRecord::Base.logger, ActiveSupport::Logger.new(output)
+
+ begin
+ yield output
+ ensure
+ ActiveRecord::Base.logger = old_logger
+ end
+ end
+end
diff --git a/activerecord/test/cases/instrumentation_test.rb b/activerecord/test/cases/instrumentation_test.rb
index c09ea32991..06382b6c7c 100644
--- a/activerecord/test/cases/instrumentation_test.rb
+++ b/activerecord/test/cases/instrumentation_test.rb
@@ -41,8 +41,8 @@ module ActiveRecord
assert_equal "Book Update", event.payload[:name]
end
end
- book = Book.create(name: "test book")
- book.update_attribute(:name, "new name")
+ book = Book.create(name: "test book", format: "paperback")
+ book.update_attribute(:format, "ebook")
ensure
ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
end
@@ -54,8 +54,8 @@ module ActiveRecord
assert_equal "Book Update All", event.payload[:name]
end
end
- Book.create(name: "test book")
- Book.update_all(name: "new name")
+ Book.create(name: "test book", format: "paperback")
+ Book.update_all(format: "ebook")
ensure
ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 6cf17ac15d..7f67b945f0 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -103,6 +103,32 @@ module ActiveRecord
end
end
+ class ChangeColumnComment1 < SilentMigration
+ def change
+ create_table("horses") do |t|
+ t.column :name, :string, comment: "Sekitoba"
+ end
+ end
+ end
+
+ class ChangeColumnComment2 < SilentMigration
+ def change
+ change_column_comment :horses, :name, from: "Sekitoba", to: "Diomed"
+ end
+ end
+
+ class ChangeTableComment1 < SilentMigration
+ def change
+ create_table("horses", comment: "Sekitoba")
+ end
+ end
+
+ class ChangeTableComment2 < SilentMigration
+ def change
+ change_table_comment :horses, from: "Sekitoba", to: "Diomed"
+ end
+ end
+
class DisableExtension1 < SilentMigration
def change
enable_extension "hstore"
@@ -290,6 +316,7 @@ module ActiveRecord
def test_migrate_revert_change_column_default
migration1 = ChangeColumnDefault1.new
migration1.migrate(:up)
+ Horse.reset_column_information
assert_equal "Sekitoba", Horse.new.name
migration2 = ChangeColumnDefault2.new
@@ -302,12 +329,46 @@ module ActiveRecord
assert_equal "Sekitoba", Horse.new.name
end
+ if ActiveRecord::Base.connection.supports_comments?
+ def test_migrate_revert_change_column_comment
+ migration1 = ChangeColumnComment1.new
+ migration1.migrate(:up)
+ Horse.reset_column_information
+ assert_equal "Sekitoba", Horse.columns_hash["name"].comment
+
+ migration2 = ChangeColumnComment2.new
+ migration2.migrate(:up)
+ Horse.reset_column_information
+ assert_equal "Diomed", Horse.columns_hash["name"].comment
+
+ migration2.migrate(:down)
+ Horse.reset_column_information
+ assert_equal "Sekitoba", Horse.columns_hash["name"].comment
+ end
+
+ def test_migrate_revert_change_table_comment
+ connection = ActiveRecord::Base.connection
+ migration1 = ChangeTableComment1.new
+ migration1.migrate(:up)
+ assert_equal "Sekitoba", connection.table_comment("horses")
+
+ migration2 = ChangeTableComment2.new
+ migration2.migrate(:up)
+ assert_equal "Diomed", connection.table_comment("horses")
+
+ migration2.migrate(:down)
+ assert_equal "Sekitoba", connection.table_comment("horses")
+ end
+ end
+
if current_adapter?(:PostgreSQLAdapter)
def test_migrate_enable_and_disable_extension
migration1 = InvertibleMigration.new
migration2 = DisableExtension1.new
migration3 = DisableExtension2.new
+ assert_equal true, Horse.connection.extension_available?("hstore")
+
migration1.migrate(:up)
migration2.migrate(:up)
assert_equal true, Horse.connection.extension_enabled?("hstore")
diff --git a/activerecord/test/cases/legacy_configurations_test.rb b/activerecord/test/cases/legacy_configurations_test.rb
deleted file mode 100644
index c36feb5116..0000000000
--- a/activerecord/test/cases/legacy_configurations_test.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-require "cases/helper"
-
-module ActiveRecord
- class LegacyConfigurationsTest < ActiveRecord::TestCase
- def test_can_turn_configurations_into_a_hash
- assert ActiveRecord::Base.configurations.to_h.is_a?(Hash), "expected to be a hash but was not."
- assert_equal ["arunit", "arunit2", "arunit_without_prepared_statements"].sort, ActiveRecord::Base.configurations.to_h.keys.sort
- end
-
- def test_each_is_deprecated
- assert_deprecated do
- ActiveRecord::Base.configurations.each do |db_config|
- assert_equal "primary", db_config.spec_name
- end
- end
- end
-
- def test_first_is_deprecated
- assert_deprecated do
- db_config = ActiveRecord::Base.configurations.first
- assert_equal "arunit", db_config.env_name
- assert_equal "primary", db_config.spec_name
- end
- end
-
- def test_fetch_is_deprecated
- assert_deprecated do
- db_config = ActiveRecord::Base.configurations.fetch("arunit").first
- assert_equal "arunit", db_config.env_name
- assert_equal "primary", db_config.spec_name
- end
- end
-
- def test_values_are_deprecated
- config_hashes = ActiveRecord::Base.configurations.configurations.map(&:config)
- assert_deprecated do
- assert_equal config_hashes, ActiveRecord::Base.configurations.values
- end
- end
- end
-end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 33bd74e114..04f9b26960 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -182,7 +182,9 @@ class OptimisticLockingTest < ActiveRecord::TestCase
p1.touch
assert_equal 1, p1.lock_version
- assert_not p1.changed?, "Changes should have been cleared"
+ assert_not_predicate p1, :changed?, "Changes should have been cleared"
+ assert_predicate p1, :saved_changes?
+ assert_equal ["lock_version", "updated_at"], p1.saved_changes.keys.sort
end
def test_touch_stale_object
@@ -193,6 +195,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::StaleObjectError) do
stale_person.touch
end
+
+ assert_not_predicate stale_person, :saved_changes?
end
def test_update_with_dirty_primary_key
@@ -296,6 +300,9 @@ class OptimisticLockingTest < ActiveRecord::TestCase
t1.touch
assert_equal 1, t1.lock_version
+ assert_not_predicate t1, :changed?
+ assert_predicate t1, :saved_changes?
+ assert_equal ["lock_version", "updated_at"], t1.saved_changes.keys.sort
end
def test_touch_stale_object_with_lock_without_default
@@ -307,6 +314,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::StaleObjectError) do
stale_object.touch
end
+
+ assert_not_predicate stale_object, :saved_changes?
end
def test_lock_without_default_should_work_with_null_in_the_database
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 7777508349..cc0587fa50 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -462,7 +462,11 @@ module ActiveRecord
end
def test_create_table_with_force_cascade_drops_dependent_objects
- skip "MySQL > 5.5 does not drop dependent objects with DROP TABLE CASCADE" if current_adapter?(:Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
+ skip "MySQL > 5.5 does not drop dependent objects with DROP TABLE CASCADE"
+ elsif current_adapter?(:SQLite3Adapter)
+ skip "SQLite3 does not support DROP TABLE CASCADE syntax"
+ end
# can't re-create table referenced by foreign key
assert_raises(ActiveRecord::StatementInvalid) do
@connection.create_table :trains, force: true
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index 3022121f4c..b6064500ee 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -176,11 +176,9 @@ module ActiveRecord
if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
def test_out_of_range_limit_should_raise
- assert_raise(ActiveRecordError) { add_column :test_models, :integer_too_big, :integer, limit: 10 }
-
- unless current_adapter?(:PostgreSQLAdapter)
- assert_raise(ActiveRecordError) { add_column :test_models, :text_too_big, :text, limit: 0xfffffffff }
- end
+ assert_raise(ArgumentError) { add_column :test_models, :integer_too_big, :integer, limit: 10 }
+ assert_raise(ArgumentError) { add_column :test_models, :text_too_big, :text, limit: 0xfffffffff }
+ assert_raise(ArgumentError) { add_column :test_models, :binary_too_big, :binary, limit: 0xfffffffff }
end
end
end
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index cedd9c44e3..cce3461e18 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -109,18 +109,10 @@ module ActiveRecord
add_index "test_models", ["hat_style", "hat_size"], unique: true
rename_column "test_models", "hat_size", "size"
- if current_adapter? :OracleAdapter
- assert_equal ["i_test_models_hat_style_size"], connection.indexes("test_models").map(&:name)
- else
- assert_equal ["index_test_models_on_hat_style_and_size"], connection.indexes("test_models").map(&:name)
- end
+ assert_equal ["index_test_models_on_hat_style_and_size"], connection.indexes("test_models").map(&:name)
rename_column "test_models", "hat_style", "style"
- if current_adapter? :OracleAdapter
- assert_equal ["i_test_models_style_size"], connection.indexes("test_models").map(&:name)
- else
- assert_equal ["index_test_models_on_style_and_size"], connection.indexes("test_models").map(&:name)
- end
+ assert_equal ["index_test_models_on_style_and_size"], connection.indexes("test_models").map(&:name)
end
def test_rename_column_does_not_rename_custom_named_index
@@ -144,7 +136,7 @@ module ActiveRecord
def test_remove_column_with_multi_column_index
# MariaDB starting with 10.2.8
# Dropping a column that is part of a multi-column UNIQUE constraint is not permitted.
- skip if current_adapter?(:Mysql2Adapter) && connection.mariadb? && connection.version >= "10.2.8"
+ skip if current_adapter?(:Mysql2Adapter) && connection.mariadb? && connection.database_version >= "10.2.8"
add_column "test_models", :hat_size, :integer
add_column "test_models", :hat_style, :string, limit: 100
@@ -318,6 +310,17 @@ module ActiveRecord
ensure
connection.drop_table(:my_table) rescue nil
end
+
+ def test_add_column_without_column_name
+ e = assert_raise ArgumentError do
+ connection.create_table "my_table", force: true do |t|
+ t.timestamp
+ end
+ end
+ assert_equal "Missing column name(s) for timestamp", e.message
+ ensure
+ connection.drop_table :my_table, if_exists: true
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 01f8628fc5..c9f3756b1f 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -182,6 +182,40 @@ module ActiveRecord
assert_equal [:change_column_default, [:table, :column, from: false, to: true]], change
end
+ if ActiveRecord::Base.connection.supports_comments?
+ def test_invert_change_column_comment
+ assert_raises(ActiveRecord::IrreversibleMigration) do
+ @recorder.inverse_of :change_column_comment, [:table, :column, "comment"]
+ end
+ end
+
+ def test_invert_change_column_comment_with_from_and_to
+ change = @recorder.inverse_of :change_column_comment, [:table, :column, from: "old_value", to: "new_value"]
+ assert_equal [:change_column_comment, [:table, :column, from: "new_value", to: "old_value"]], change
+ end
+
+ def test_invert_change_column_comment_with_from_and_to_with_nil
+ change = @recorder.inverse_of :change_column_comment, [:table, :column, from: nil, to: "new_value"]
+ assert_equal [:change_column_comment, [:table, :column, from: "new_value", to: nil]], change
+ end
+
+ def test_invert_change_table_comment
+ assert_raises(ActiveRecord::IrreversibleMigration) do
+ @recorder.inverse_of :change_column_comment, [:table, :column, "comment"]
+ end
+ end
+
+ def test_invert_change_table_comment_with_from_and_to
+ change = @recorder.inverse_of :change_table_comment, [:table, from: "old_value", to: "new_value"]
+ assert_equal [:change_table_comment, [:table, from: "new_value", to: "old_value"]], change
+ end
+
+ def test_invert_change_table_comment_with_from_and_to_with_nil
+ change = @recorder.inverse_of :change_table_comment, [:table, from: nil, to: "new_value"]
+ assert_equal [:change_table_comment, [:table, from: "new_value", to: nil]], change
+ end
+ end
+
def test_invert_change_column_null
add = @recorder.inverse_of :change_column_null, [:table, :column, true]
assert_equal [:change_column_null, [:table, :column, false]], add
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 5753bd7117..726ccf925e 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -220,6 +220,35 @@ module ActiveRecord
end
end
+ if ActiveRecord::Base.connection.supports_comments?
+ def test_change_column_comment_can_be_reverted
+ migration = Class.new(ActiveRecord::Migration[5.2]) {
+ def migrate(x)
+ revert do
+ change_column_comment(:testings, :foo, "comment")
+ end
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+ assert connection.column_exists?(:testings, :foo, comment: "comment")
+ end
+
+ def test_change_table_comment_can_be_reverted
+ migration = Class.new(ActiveRecord::Migration[5.2]) {
+ def migrate(x)
+ revert do
+ change_table_comment(:testings, "comment")
+ end
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert_equal "comment", connection.table_comment("testings")
+ end
+ end
+
if current_adapter?(:PostgreSQLAdapter)
class Testing < ActiveRecord::Base
end
@@ -242,9 +271,9 @@ module ActiveRecord
private
def precision_implicit_default
if current_adapter?(:Mysql2Adapter)
- { presicion: 0 }
+ { precision: 0 }
else
- { presicion: nil }
+ { precision: nil }
end
end
end
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index bb233fbf74..5f1057f093 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -3,7 +3,7 @@
require "cases/helper"
require "support/schema_dumping_helper"
-if ActiveRecord::Base.connection.supports_foreign_keys_in_create?
+if ActiveRecord::Base.connection.supports_foreign_keys?
module ActiveRecord
class Migration
class ForeignKeyInCreateTest < ActiveRecord::TestCase
@@ -31,24 +31,39 @@ if ActiveRecord::Base.connection.supports_foreign_keys_in_create?
belongs_to :rocket
end
- setup do
- @connection = ActiveRecord::Base.connection
- @connection.create_table "rockets", force: true do |t|
- t.string :name
+ class CreateRocketsMigration < ActiveRecord::Migration::Current
+ def up
+ create_table :rockets do |t|
+ t.string :name
+ end
+
+ create_table :astronauts do |t|
+ t.string :name
+ t.references :rocket, foreign_key: true
+ end
end
- @connection.create_table "astronauts", force: true do |t|
- t.string :name
- t.references :rocket, foreign_key: true
+ def down
+ drop_table :astronauts, if_exists: true
+ drop_table :rockets, if_exists: true
end
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @migration = CreateRocketsMigration.new
+ silence_stream($stdout) { @migration.migrate(:up) }
+ Rocket.reset_table_name
Rocket.reset_column_information
+ Astronaut.reset_table_name
Astronaut.reset_column_information
end
- teardown do
- @connection.drop_table "astronauts", if_exists: true
- @connection.drop_table "rockets", if_exists: true
+ def teardown
+ silence_stream($stdout) { @migration.migrate(:down) }
+ Rocket.reset_table_name
Rocket.reset_column_information
+ Astronaut.reset_table_name
Astronaut.reset_column_information
end
@@ -56,53 +71,100 @@ if ActiveRecord::Base.connection.supports_foreign_keys_in_create?
rocket = Rocket.create!(name: "myrocket")
rocket.astronauts << Astronaut.create!
- @connection.change_column_null :rockets, :name, false
+ @connection.change_column_null Rocket.table_name, :name, false
- foreign_keys = @connection.foreign_keys("astronauts")
+ foreign_keys = @connection.foreign_keys(Astronaut.table_name)
assert_equal 1, foreign_keys.size
fk = foreign_keys.first
assert_equal "myrocket", Rocket.first.name
- assert_equal "astronauts", fk.from_table
- assert_equal "rockets", fk.to_table
+ assert_equal Astronaut.table_name, fk.from_table
+ assert_equal Rocket.table_name, fk.to_table
end
def test_rename_column_of_child_table
rocket = Rocket.create!(name: "myrocket")
rocket.astronauts << Astronaut.create!
- @connection.rename_column :astronauts, :name, :astronaut_name
+ @connection.rename_column Astronaut.table_name, :name, :astronaut_name
- foreign_keys = @connection.foreign_keys("astronauts")
+ foreign_keys = @connection.foreign_keys(Astronaut.table_name)
assert_equal 1, foreign_keys.size
fk = foreign_keys.first
assert_equal "myrocket", Rocket.first.name
- assert_equal "astronauts", fk.from_table
- assert_equal "rockets", fk.to_table
+ assert_equal Astronaut.table_name, fk.from_table
+ assert_equal Rocket.table_name, fk.to_table
end
def test_rename_reference_column_of_child_table
rocket = Rocket.create!(name: "myrocket")
rocket.astronauts << Astronaut.create!
- @connection.rename_column :astronauts, :rocket_id, :new_rocket_id
+ @connection.rename_column Astronaut.table_name, :rocket_id, :new_rocket_id
- foreign_keys = @connection.foreign_keys("astronauts")
+ foreign_keys = @connection.foreign_keys(Astronaut.table_name)
assert_equal 1, foreign_keys.size
fk = foreign_keys.first
assert_equal "myrocket", Rocket.first.name
- assert_equal "astronauts", fk.from_table
- assert_equal "rockets", fk.to_table
+ assert_equal Astronaut.table_name, fk.from_table
+ assert_equal Rocket.table_name, fk.to_table
assert_equal "new_rocket_id", fk.options[:column]
end
+
+ def test_remove_reference_column_of_child_table
+ rocket = Rocket.create!(name: "myrocket")
+ rocket.astronauts << Astronaut.create!
+
+ @connection.remove_column Astronaut.table_name, :rocket_id
+
+ assert_empty @connection.foreign_keys(Astronaut.table_name)
+ end
+
+ def test_remove_foreign_key_by_column
+ rocket = Rocket.create!(name: "myrocket")
+ rocket.astronauts << Astronaut.create!
+
+ @connection.remove_foreign_key Astronaut.table_name, column: :rocket_id
+
+ assert_empty @connection.foreign_keys(Astronaut.table_name)
+ end
+
+ def test_remove_foreign_key_by_column_in_change_table
+ rocket = Rocket.create!(name: "myrocket")
+ rocket.astronauts << Astronaut.create!
+
+ @connection.change_table Astronaut.table_name do |t|
+ t.remove_foreign_key column: :rocket_id
+ end
+
+ assert_empty @connection.foreign_keys(Astronaut.table_name)
+ end
+ end
+
+ class ForeignKeyChangeColumnWithPrefixTest < ForeignKeyChangeColumnTest
+ setup do
+ ActiveRecord::Base.table_name_prefix = "p_"
+ end
+
+ teardown do
+ ActiveRecord::Base.table_name_prefix = nil
+ end
+ end
+
+ class ForeignKeyChangeColumnWithSuffixTest < ForeignKeyChangeColumnTest
+ setup do
+ ActiveRecord::Base.table_name_suffix = "_s"
+ end
+
+ teardown do
+ ActiveRecord::Base.table_name_suffix = nil
+ end
end
end
end
-end
-if ActiveRecord::Base.connection.supports_foreign_keys?
module ActiveRecord
class Migration
class ForeignKeyTest < ActiveRecord::TestCase
@@ -141,7 +203,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
assert_equal "fk_test_has_pk", fk.to_table
assert_equal "fk_id", fk.column
assert_equal "pk_id", fk.primary_key
- assert_equal "fk_name", fk.name
+ assert_equal "fk_name", fk.name unless current_adapter?(:SQLite3Adapter)
end
def test_add_foreign_key_inferes_column
@@ -155,7 +217,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
assert_equal "rockets", fk.to_table
assert_equal "rocket_id", fk.column
assert_equal "id", fk.primary_key
- assert_equal("fk_rails_78146ddd2e", fk.name)
+ assert_equal "fk_rails_78146ddd2e", fk.name unless current_adapter?(:SQLite3Adapter)
end
def test_add_foreign_key_with_column
@@ -169,7 +231,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
assert_equal "rockets", fk.to_table
assert_equal "rocket_id", fk.column
assert_equal "id", fk.primary_key
- assert_equal("fk_rails_78146ddd2e", fk.name)
+ assert_equal "fk_rails_78146ddd2e", fk.name unless current_adapter?(:SQLite3Adapter)
end
def test_add_foreign_key_with_non_standard_primary_key
@@ -188,7 +250,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
assert_equal "space_shuttles", fk.to_table
assert_equal "pk", fk.primary_key
ensure
- @connection.remove_foreign_key :astronauts, name: "custom_pk"
+ @connection.remove_foreign_key :astronauts, name: "custom_pk", to_table: "space_shuttles"
@connection.drop_table :space_shuttles
end
@@ -262,6 +324,8 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
def test_foreign_key_exists_by_name
+ skip if current_adapter?(:SQLite3Adapter)
+
@connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk"
assert @connection.foreign_key_exists?(:astronauts, name: "fancy_named_fk")
@@ -293,6 +357,8 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
def test_remove_foreign_key_by_name
+ skip if current_adapter?(:SQLite3Adapter)
+
@connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk"
assert_equal 1, @connection.foreign_keys("astronauts").size
@@ -301,9 +367,22 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
def test_remove_foreign_non_existing_foreign_key_raises
- assert_raises ArgumentError do
+ e = assert_raises ArgumentError do
@connection.remove_foreign_key :astronauts, :rockets
end
+ assert_equal "Table 'astronauts' has no foreign key for rockets", e.message
+ end
+
+ def test_remove_foreign_key_by_the_select_one_on_the_same_table
+ @connection.add_foreign_key :astronauts, :rockets
+ @connection.add_reference :astronauts, :myrocket, foreign_key: { to_table: :rockets }
+
+ assert_equal 2, @connection.foreign_keys("astronauts").size
+
+ @connection.remove_foreign_key :astronauts, :rockets, column: "myrocket_id"
+
+ assert_equal [["astronauts", "rockets", "rocket_id"]],
+ @connection.foreign_keys("astronauts").map { |fk| [fk.from_table, fk.to_table, fk.column] }
end
if ActiveRecord::Base.connection.supports_validate_constraints?
@@ -382,7 +461,11 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
def test_schema_dumping_with_options
output = dump_table_schema "fk_test_has_fk"
- assert_match %r{\s+add_foreign_key "fk_test_has_fk", "fk_test_has_pk", column: "fk_id", primary_key: "pk_id", name: "fk_name"$}, output
+ if current_adapter?(:SQLite3Adapter)
+ assert_match %r{\s+add_foreign_key "fk_test_has_fk", "fk_test_has_pk", column: "fk_id", primary_key: "pk_id"$}, output
+ else
+ assert_match %r{\s+add_foreign_key "fk_test_has_fk", "fk_test_has_pk", column: "fk_id", primary_key: "pk_id", name: "fk_name"$}, output
+ end
end
def test_schema_dumping_with_custom_fk_ignore_pattern
@@ -436,7 +519,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
class CreateSchoolsAndClassesMigration < ActiveRecord::Migration::Current
- def change
+ def up
create_table(:schools)
create_table(:classes) do |t|
@@ -444,6 +527,11 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
add_foreign_key :classes, :schools
end
+
+ def down
+ drop_table :classes, if_exists: true
+ drop_table :schools, if_exists: true
+ end
end
def test_add_foreign_key_with_prefix
@@ -468,30 +556,4 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
end
end
-else
- module ActiveRecord
- class Migration
- class NoForeignKeySupportTest < ActiveRecord::TestCase
- setup do
- @connection = ActiveRecord::Base.connection
- end
-
- def test_add_foreign_key_should_be_noop
- @connection.add_foreign_key :clubs, :categories
- end
-
- def test_remove_foreign_key_should_be_noop
- @connection.remove_foreign_key :clubs, :categories
- end
-
- unless current_adapter?(:SQLite3Adapter)
- def test_foreign_keys_should_raise_not_implemented
- assert_raises NotImplementedError do
- @connection.foreign_keys("clubs")
- end
- end
- end
- end
- end
- end
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index f8fecc83cd..5e688efc2b 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -158,14 +158,11 @@ module ActiveRecord
connection.add_index("testings", ["last_name", "first_name"])
connection.remove_index("testings", column: ["last_name", "first_name"])
- # Oracle adapter cannot have specified index name larger than 30 characters
- # Oracle adapter is shortening index name when just column list is given
- unless current_adapter?(:OracleAdapter)
- connection.add_index("testings", ["last_name", "first_name"])
- connection.remove_index("testings", name: :index_testings_on_last_name_and_first_name)
- connection.add_index("testings", ["last_name", "first_name"])
- connection.remove_index("testings", "last_name_and_first_name")
- end
+ connection.add_index("testings", ["last_name", "first_name"])
+ connection.remove_index("testings", name: :index_testings_on_last_name_and_first_name)
+ connection.add_index("testings", ["last_name", "first_name"])
+ connection.remove_index("testings", "last_name_and_first_name")
+
connection.add_index("testings", ["last_name", "first_name"])
connection.remove_index("testings", ["last_name", "first_name"])
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index 620e9ab6ca..90a50a5651 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -2,7 +2,7 @@
require "cases/helper"
-if ActiveRecord::Base.connection.supports_foreign_keys_in_create?
+if ActiveRecord::Base.connection.supports_foreign_keys?
module ActiveRecord
class Migration
class ReferencesForeignKeyInCreateTest < ActiveRecord::TestCase
@@ -65,9 +65,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys_in_create?
end
end
end
-end
-if ActiveRecord::Base.connection.supports_foreign_keys?
module ActiveRecord
class Migration
class ReferencesForeignKeyTest < ActiveRecord::TestCase
@@ -172,13 +170,18 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
class CreateDogsMigration < ActiveRecord::Migration::Current
- def change
+ def up
create_table :dog_owners
create_table :dogs do |t|
t.references :dog_owner, foreign_key: true
end
end
+
+ def down
+ drop_table :dogs, if_exists: true
+ drop_table :dog_owners, if_exists: true
+ end
end
def test_references_foreign_key_with_prefix
@@ -232,24 +235,4 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
end
end
-else
- class ReferencesWithoutForeignKeySupportTest < ActiveRecord::TestCase
- setup do
- @connection = ActiveRecord::Base.connection
- @connection.create_table(:testing_parents, force: true)
- end
-
- teardown do
- @connection.drop_table("testings", if_exists: true)
- @connection.drop_table("testing_parents", if_exists: true)
- end
-
- test "ignores foreign keys defined with the table" do
- @connection.create_table :testings do |t|
- t.references :testing_parent, foreign_key: true
- end
-
- assert_includes @connection.data_sources, "testings"
- end
- end
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 02031e51ef..255885b599 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -385,6 +385,7 @@ class MigrationTest < ActiveRecord::TestCase
assert_equal "changed", ActiveRecord::SchemaMigration.table_name
ensure
ActiveRecord::Base.schema_migrations_table_name = original_schema_migrations_table_name
+ ActiveRecord::SchemaMigration.reset_table_name
Reminder.reset_table_name
end
@@ -405,6 +406,7 @@ class MigrationTest < ActiveRecord::TestCase
assert_equal "changed", ActiveRecord::InternalMetadata.table_name
ensure
ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name
+ ActiveRecord::InternalMetadata.reset_table_name
Reminder.reset_table_name
end
@@ -567,66 +569,57 @@ class MigrationTest < ActiveRecord::TestCase
end
end
- if current_adapter? :OracleAdapter
- def test_create_table_with_custom_sequence_name
- # table name is 29 chars, the standard sequence name will
- # be 33 chars and should be shortened
- assert_nothing_raised do
- Person.connection.create_table :table_with_name_thats_just_ok do |t|
- t.column :foo, :string, null: false
- end
- ensure
- Person.connection.drop_table :table_with_name_thats_just_ok rescue nil
- end
-
- # should be all good w/ a custom sequence name
- assert_nothing_raised do
- Person.connection.create_table :table_with_name_thats_just_ok,
- sequence_name: "suitably_short_seq" do |t|
- t.column :foo, :string, null: false
- end
-
- Person.connection.execute("select suitably_short_seq.nextval from dual")
-
- ensure
- Person.connection.drop_table :table_with_name_thats_just_ok,
- sequence_name: "suitably_short_seq" rescue nil
- end
-
- # confirm the custom sequence got dropped
- assert_raise(ActiveRecord::StatementInvalid) do
- Person.connection.execute("select suitably_short_seq.nextval from dual")
+ def test_decimal_scale_without_precision_should_raise
+ e = assert_raise(ArgumentError) do
+ Person.connection.create_table :test_decimal_scales, force: true do |t|
+ t.decimal :scaleonly, scale: 10
end
end
+
+ assert_equal "Error adding decimal column: precision cannot be empty if scale is specified", e.message
+ ensure
+ Person.connection.drop_table :test_decimal_scales, if_exists: true
end
if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
def test_out_of_range_integer_limit_should_raise
- e = assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do
+ e = assert_raise(ArgumentError) do
Person.connection.create_table :test_integer_limits, force: true do |t|
t.column :bigone, :integer, limit: 10
end
end
- assert_match(/No integer type has byte size 10/, e.message)
+ assert_includes e.message, "No integer type has byte size 10"
ensure
Person.connection.drop_table :test_integer_limits, if_exists: true
end
- end
- if current_adapter?(:Mysql2Adapter)
def test_out_of_range_text_limit_should_raise
- e = assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do
+ e = assert_raise(ArgumentError) do
Person.connection.create_table :test_text_limits, force: true do |t|
t.text :bigtext, limit: 0xfffffffff
end
end
- assert_match(/No text type has byte length #{0xfffffffff}/, e.message)
+ assert_includes e.message, "No text type has byte size #{0xfffffffff}"
ensure
Person.connection.drop_table :test_text_limits, if_exists: true
end
+ def test_out_of_range_binary_limit_should_raise
+ e = assert_raise(ArgumentError) do
+ Person.connection.create_table :test_binary_limits, force: true do |t|
+ t.binary :bigbinary, limit: 0xfffffffff
+ end
+ end
+
+ assert_includes e.message, "No binary type has byte size #{0xfffffffff}"
+ ensure
+ Person.connection.drop_table :test_binary_limits, if_exists: true
+ end
+ end
+
+ if current_adapter?(:Mysql2Adapter)
def test_invalid_text_size_should_raise
e = assert_raise(ArgumentError) do
Person.connection.create_table :test_text_sizes, force: true do |t|
@@ -634,7 +627,7 @@ class MigrationTest < ActiveRecord::TestCase
end
end
- assert_match(/#{0xfffffffff} is invalid :size value\. Only :tiny, :medium, and :long are allowed\./, e.message)
+ assert_equal "#{0xfffffffff} is invalid :size value. Only :tiny, :medium, and :long are allowed.", e.message
ensure
Person.connection.drop_table :test_text_sizes, if_exists: true
end
@@ -861,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}"
@@ -893,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/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index d5057ad381..7b7aa7e9b7 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -183,7 +183,7 @@ class PersistenceTest < ActiveRecord::TestCase
assert_not_predicate company, :valid?
original_errors = company.errors
client = company.becomes(Client)
- assert_equal original_errors.keys, client.errors.keys
+ assert_equal assert_deprecated { original_errors.keys }, assert_deprecated { client.errors.keys }
end
def test_becomes_errors_base
@@ -197,7 +197,7 @@ class PersistenceTest < ActiveRecord::TestCase
admin.errors.add :token, :invalid
child = admin.becomes(child_class)
- assert_equal [:token], child.errors.keys
+ assert_equal [:token], assert_deprecated { child.errors.keys }
assert_nothing_raised do
child.errors.add :foo, :invalid
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 4759d3b6b2..511d7fc982 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -203,6 +203,14 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_queries(3, ignore_none: true) { klass.create! }
end
+ def test_assign_id_raises_error_if_primary_key_doesnt_exist
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "dashboards"
+ end
+ dashboard = klass.new
+ assert_raises(ActiveModel::MissingAttributeError) { dashboard.id = "1" }
+ end
+
if current_adapter?(:PostgreSQLAdapter)
def test_serial_with_quoted_sequence_name
column = MixedCaseMonkey.columns_hash[MixedCaseMonkey.primary_key]
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index 63ae438de3..085006c9a2 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -44,26 +44,28 @@ module ActiveRecord
end
class QueryingMethodsDelegationTest < ActiveRecord::TestCase
- QUERYING_METHODS = [
- :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :none?, :one?,
- :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!,
- :first_or_create, :first_or_create!, :first_or_initialize,
- :find_or_create_by, :find_or_create_by!, :create_or_find_by, :create_or_find_by!, :find_or_initialize_by,
- :find_by, :find_by!,
- :destroy_all, :delete_all, :update_all,
- :find_each, :find_in_batches, :in_batches,
- :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,
- :count, :average, :minimum, :maximum, :sum, :calculate,
- :pluck, :pick, :ids,
- ]
+ QUERYING_METHODS =
+ ActiveRecord::Batches.public_instance_methods(false) +
+ ActiveRecord::Calculations.public_instance_methods(false) +
+ ActiveRecord::FinderMethods.public_instance_methods(false) - [:raise_record_not_found_exception!] +
+ ActiveRecord::SpawnMethods.public_instance_methods(false) - [:spawn, :merge!] +
+ ActiveRecord::QueryMethods.public_instance_methods(false).reject { |method|
+ method.to_s.end_with?("=", "!", "value", "values", "clause")
+ } - [:reverse_order, :arel, :extensions, :construct_join_dependency] + [
+ :any?, :many?, :none?, :one?,
+ :first_or_create, :first_or_create!, :first_or_initialize,
+ :find_or_create_by, :find_or_create_by!, :find_or_initialize_by,
+ :create_or_find_by, :create_or_find_by!,
+ :destroy_all, :delete_all, :update_all, :touch_all, :delete_by, :destroy_by
+ ]
def test_delegate_querying_methods
klass = Class.new(ActiveRecord::Base) do
self.table_name = "posts"
end
+ assert_equal QUERYING_METHODS.sort, ActiveRecord::Querying::QUERYING_METHODS.sort
+
QUERYING_METHODS.each do |method|
assert_respond_to klass.all, method
assert_respond_to klass, method
diff --git a/activerecord/test/cases/relation/delete_all_test.rb b/activerecord/test/cases/relation/delete_all_test.rb
index 446d7621ea..d1c13fa1b5 100644
--- a/activerecord/test/cases/relation/delete_all_test.rb
+++ b/activerecord/test/cases/relation/delete_all_test.rb
@@ -80,25 +80,23 @@ class DeleteAllTest < ActiveRecord::TestCase
assert_equal pets.count, pets.delete_all
end
- unless current_adapter?(:OracleAdapter)
- def test_delete_all_with_order_and_limit_deletes_subset_only
- author = authors(:david)
- limited_posts = Post.where(author: author).order(:id).limit(1)
- assert_equal 1, limited_posts.size
- assert_equal 2, limited_posts.limit(2).size
- assert_equal 1, limited_posts.delete_all
- assert_raise(ActiveRecord::RecordNotFound) { posts(:welcome) }
- assert posts(:thinking)
- end
+ def test_delete_all_with_order_and_limit_deletes_subset_only
+ author = authors(:david)
+ limited_posts = Post.where(author: author).order(:id).limit(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { posts(:welcome) }
+ assert posts(:thinking)
+ end
- def test_delete_all_with_order_and_limit_and_offset_deletes_subset_only
- author = authors(:david)
- limited_posts = Post.where(author: author).order(:id).limit(1).offset(1)
- assert_equal 1, limited_posts.size
- assert_equal 2, limited_posts.limit(2).size
- assert_equal 1, limited_posts.delete_all
- assert_raise(ActiveRecord::RecordNotFound) { posts(:thinking) }
- assert posts(:welcome)
- end
+ def test_delete_all_with_order_and_limit_and_offset_deletes_subset_only
+ author = authors(:david)
+ limited_posts = Post.where(author: author).order(:id).limit(1).offset(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { posts(:thinking) }
+ assert posts(:welcome)
end
end
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index 224e4f39a8..5c5e760e34 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -135,6 +135,18 @@ class RelationMergingTest < ActiveRecord::TestCase
relation = Post.all.merge(Post.order(Arel.sql("title LIKE '%?'")))
assert_equal ["title LIKE '%?'"], relation.order_values
end
+
+ def test_merging_annotations_respects_merge_order
+ assert_sql(%r{/\* foo \*/ /\* bar \*/}) do
+ Post.annotate("foo").merge(Post.annotate("bar")).first
+ end
+ assert_sql(%r{/\* bar \*/ /\* foo \*/}) do
+ Post.annotate("bar").merge(Post.annotate("foo")).first
+ end
+ assert_sql(%r{/\* foo \*/ /\* bar \*/ /\* baz \*/ /\* qux \*/}) do
+ Post.annotate("foo").annotate("bar").merge(Post.annotate("baz").annotate("qux")).first
+ end
+ end
end
class MergingDifferentRelationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index f82ecd4449..96249b8d51 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -26,7 +26,7 @@ module ActiveRecord
assert relation.order!(:name).equal?(relation)
node = relation.order_values.first
assert_predicate node, :ascending?
- assert_equal :name, node.expr.name
+ assert_equal "name", node.expr.name
assert_equal "posts", node.expr.relation.name
end
@@ -89,7 +89,7 @@ module ActiveRecord
node = relation.order_values.first
assert_predicate node, :ascending?
- assert_equal :name, node.expr.name
+ assert_equal "name", node.expr.name
assert_equal "posts", node.expr.relation.name
end
diff --git a/activerecord/test/cases/relation/select_test.rb b/activerecord/test/cases/relation/select_test.rb
index dec8a6925d..586aaadd0a 100644
--- a/activerecord/test/cases/relation/select_test.rb
+++ b/activerecord/test/cases/relation/select_test.rb
@@ -11,5 +11,17 @@ module ActiveRecord
expected = Post.select(:title).to_sql
assert_equal expected, Post.select(nil).select(:title).to_sql
end
+
+ def test_reselect
+ expected = Post.select(:title).to_sql
+ assert_equal expected, Post.select(:title, :body).reselect(:title).to_sql
+ end
+
+ def test_reselect_with_default_scope_select
+ expected = Post.select(:title).to_sql
+ actual = PostWithDefaultSelect.reselect(:title).to_sql
+
+ assert_equal expected, actual
+ end
end
end
diff --git a/activerecord/test/cases/relation/update_all_test.rb b/activerecord/test/cases/relation/update_all_test.rb
index bb6912148c..e45531b4a9 100644
--- a/activerecord/test/cases/relation/update_all_test.rb
+++ b/activerecord/test/cases/relation/update_all_test.rb
@@ -138,14 +138,6 @@ class UpdateAllTest < ActiveRecord::TestCase
assert_equal new_time, developer.updated_at
end
- def test_touch_all_updates_locking_column
- person = people(:david)
-
- assert_difference -> { person.reload.lock_version }, +1 do
- Person.where(first_name: "David").touch_all
- end
- end
-
def test_update_on_relation
topic1 = TopicWithCallbacks.create! title: "arel", author_name: nil
topic2 = TopicWithCallbacks.create! title: "activerecord", author_name: nil
@@ -186,6 +178,101 @@ class UpdateAllTest < ActiveRecord::TestCase
end
end
+ def test_update_all_cares_about_optimistic_locking
+ david = people(:david)
+
+ travel 5.seconds do
+ now = Time.now.utc
+ assert_not_equal now, david.updated_at
+
+ people = Person.where(id: people(:michael, :david, :susan))
+ expected = people.pluck(:lock_version)
+ expected.map! { |version| version + 1 }
+ people.update_all(updated_at: now)
+
+ assert_equal [now] * 3, people.pluck(:updated_at)
+ assert_equal expected, people.pluck(:lock_version)
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ david.touch(time: now)
+ end
+ end
+ end
+
+ def test_update_counters_cares_about_optimistic_locking
+ david = people(:david)
+
+ travel 5.seconds do
+ now = Time.now.utc
+ assert_not_equal now, david.updated_at
+
+ people = Person.where(id: people(:michael, :david, :susan))
+ expected = people.pluck(:lock_version)
+ expected.map! { |version| version + 1 }
+ people.update_counters(touch: [time: now])
+
+ assert_equal [now] * 3, people.pluck(:updated_at)
+ assert_equal expected, people.pluck(:lock_version)
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ david.touch(time: now)
+ end
+ end
+ end
+
+ def test_touch_all_cares_about_optimistic_locking
+ david = people(:david)
+
+ travel 5.seconds do
+ now = Time.now.utc
+ assert_not_equal now, david.updated_at
+
+ people = Person.where(id: people(:michael, :david, :susan))
+ expected = people.pluck(:lock_version)
+ expected.map! { |version| version + 1 }
+ people.touch_all(time: now)
+
+ assert_equal [now] * 3, people.pluck(:updated_at)
+ assert_equal expected, people.pluck(:lock_version)
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ david.touch(time: now)
+ end
+ end
+ end
+
+ def test_klass_level_update_all
+ travel 5.seconds do
+ now = Time.now.utc
+
+ Person.all.each do |person|
+ assert_not_equal now, person.updated_at
+ end
+
+ Person.update_all(updated_at: now)
+
+ Person.all.each do |person|
+ assert_equal now, person.updated_at
+ end
+ end
+ end
+
+ def test_klass_level_touch_all
+ travel 5.seconds do
+ now = Time.now.utc
+
+ Person.all.each do |person|
+ assert_not_equal now, person.updated_at
+ end
+
+ Person.touch_all(time: now)
+
+ Person.all.each do |person|
+ assert_equal now, person.updated_at
+ end
+ end
+ end
+
# Oracle UPDATE does not support ORDER BY
unless current_adapter?(:OracleAdapter)
def test_update_all_ignores_order_without_limit_from_association
diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb
index 0b06cec40b..b26a1a1d80 100644
--- a/activerecord/test/cases/relation/where_clause_test.rb
+++ b/activerecord/test/cases/relation/where_clause_test.rb
@@ -106,7 +106,7 @@ class ActiveRecord::Relation
Arel::Nodes::Not.new(random_object)
])
- assert_equal expected, original.invert
+ assert_equal expected, original.invert(:nor)
end
test "except removes binary predicates referencing a given column" do
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index d49ed092b2..6c1e3e7fec 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -14,11 +14,19 @@ require "models/price_estimate"
require "models/topic"
require "models/treasure"
require "models/vertex"
+require "support/stubs/strong_parameters"
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
fixtures :posts, :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
+ david = authors(:david)
+ assert_equal [david], Author.where(name: "David", id: [1, 2])
+ end
+ end
+
def test_where_copies_bind_params
author = authors(:david)
posts = author.posts.where("posts.id != 1")
@@ -50,8 +58,13 @@ module ActiveRecord
assert_equal [chef], chefs.to_a
end
- def test_where_with_casted_value_is_nil
- assert_equal 4, Topic.where(last_read: "").count
+ def test_where_with_invalid_value
+ topics(:first).update!(parent_id: 0, written_on: nil, bonus_time: nil, last_read: nil)
+ assert_empty Topic.where(parent_id: Object.new)
+ assert_empty Topic.where(parent_id: "not-a-number")
+ assert_empty Topic.where(written_on: "")
+ assert_empty Topic.where(bonus_time: "")
+ assert_empty Topic.where(last_read: "")
end
def test_rewhere_on_root
@@ -109,13 +122,58 @@ module ActiveRecord
assert_equal expected.to_sql, actual.to_sql
end
- def test_polymorphic_shallow_where_not
- treasure = treasures(:sapphire)
+ def test_where_not_polymorphic_association
+ sapphire = treasures(:sapphire)
- expected = [price_estimates(:diamond), price_estimates(:honda)]
- actual = PriceEstimate.where.not(estimate_of: treasure)
+ all = [treasures(:diamond), sapphire, cars(:honda), sapphire]
+ assert_equal all, PriceEstimate.all.sort_by(&:id).map(&:estimate_of)
- assert_equal expected.sort_by(&:id), actual.sort_by(&:id)
+ actual = PriceEstimate.where.not(estimate_of: sapphire)
+ only = PriceEstimate.where(estimate_of: sapphire)
+
+ expected = all - [sapphire]
+ assert_equal expected, actual.sort_by(&:id).map(&:estimate_of)
+ assert_equal all - expected, only.sort_by(&:id).map(&:estimate_of)
+ end
+
+ def test_where_not_polymorphic_id_and_type_as_nand
+ sapphire = treasures(:sapphire)
+
+ all = [treasures(:diamond), sapphire, cars(:honda), sapphire]
+ assert_equal all, PriceEstimate.all.sort_by(&:id).map(&:estimate_of)
+
+ actual = PriceEstimate.where.yield_self do |where_chain|
+ where_chain.stub(:not_behaves_as_nor?, false) do
+ where_chain.not(estimate_of_type: sapphire.class.polymorphic_name, estimate_of_id: sapphire.id)
+ end
+ end
+ only = PriceEstimate.where(estimate_of_type: sapphire.class.polymorphic_name, estimate_of_id: sapphire.id)
+
+ expected = all - [sapphire]
+ assert_equal expected, actual.sort_by(&:id).map(&:estimate_of)
+ assert_equal all - expected, only.sort_by(&:id).map(&:estimate_of)
+ end
+
+ def test_where_not_polymorphic_id_and_type_as_nor_is_deprecated
+ sapphire = treasures(:sapphire)
+
+ all = [treasures(:diamond), sapphire, cars(:honda), sapphire]
+ assert_equal all, PriceEstimate.all.sort_by(&:id).map(&:estimate_of)
+
+ message = <<~MSG.squish
+ NOT conditions will no longer behave as NOR in Rails 6.1.
+ To continue using NOR conditions, NOT each conditions manually
+ (`.where.not(:estimate_of_type => ...).where.not(:estimate_of_id => ...)`).
+ MSG
+ actual = assert_deprecated(message) do
+ PriceEstimate.where.not(estimate_of_type: sapphire.class.polymorphic_name, estimate_of_id: sapphire.id)
+ end
+ only = PriceEstimate.where(estimate_of_type: sapphire.class.polymorphic_name, estimate_of_id: sapphire.id)
+
+ expected = all - [sapphire]
+ # NOT (estimate_of_type = 'Treasure' OR estimate_of_id = sapphire.id) matches only `cars(:honda)` unfortunately.
+ assert_not_equal expected, actual.sort_by(&:id).map(&:estimate_of)
+ assert_equal all - expected, only.sort_by(&:id).map(&:estimate_of)
end
def test_polymorphic_nested_array_where
@@ -334,27 +392,8 @@ module ActiveRecord
end
def test_where_with_strong_parameters
- protected_params = Class.new do
- attr_reader :permitted
- alias :permitted? :permitted
-
- def initialize(parameters)
- @parameters = parameters
- @permitted = false
- end
-
- def to_h
- @parameters
- end
-
- def permit!
- @permitted = true
- self
- end
- end
-
author = authors(:david)
- params = protected_params.new(name: author.name)
+ params = ProtectedParams.new(name: author.name)
assert_raises(ActiveModel::ForbiddenAttributesError) { Author.where(params) }
assert_equal author, Author.where(params.permit!).first
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 68161f6a84..3f370e5ede 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -101,6 +101,9 @@ module ActiveRecord
relation.merge!(relation)
assert_predicate relation, :empty_scope?
+
+ assert_not_predicate NullPost.all, :empty_scope?
+ assert_not_predicate FirstPost.all, :empty_scope?
end
def test_bad_constants_raise_errors
@@ -289,6 +292,7 @@ module ActiveRecord
klass.create!(description: "foo")
assert_equal ["foo"], klass.select(:description).from(klass.all).map(&:desc)
+ assert_equal ["foo"], klass.reselect(:description).from(klass.all).map(&:desc)
end
def test_relation_merging_with_merged_joins_as_strings
@@ -307,6 +311,58 @@ module ActiveRecord
assert_equal 3, ratings.count
end
+ def test_relation_with_annotation_includes_comment_in_to_sql
+ post_with_annotation = Post.where(id: 1).annotate("foo")
+ assert_match %r{= 1 /\* foo \*/}, post_with_annotation.to_sql
+ end
+
+ def test_relation_with_annotation_includes_comment_in_sql
+ post_with_annotation = Post.where(id: 1).annotate("foo")
+ assert_sql(%r{/\* foo \*/}) do
+ assert post_with_annotation.first, "record should be found"
+ end
+ end
+
+ def test_relation_with_annotation_chains_sql_comments
+ post_with_annotation = Post.where(id: 1).annotate("foo").annotate("bar")
+ assert_sql(%r{/\* foo \*/ /\* bar \*/}) do
+ assert post_with_annotation.first, "record should be found"
+ end
+ end
+
+ def test_relation_with_annotation_filters_sql_comment_delimiters
+ post_with_annotation = Post.where(id: 1).annotate("**//foo//**")
+ assert_match %r{= 1 /\* foo \*/}, post_with_annotation.to_sql
+ end
+
+ def test_relation_with_annotation_includes_comment_in_count_query
+ post_with_annotation = Post.annotate("foo")
+ all_count = Post.all.to_a.count
+ assert_sql(%r{/\* foo \*/}) do
+ assert_equal all_count, post_with_annotation.count
+ end
+ end
+
+ def test_relation_without_annotation_does_not_include_an_empty_comment
+ log = capture_sql do
+ Post.where(id: 1).first
+ end
+
+ assert_not_predicate log, :empty?
+ assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
+ end
+
+ def test_relation_with_optimizer_hints_filters_sql_comment_delimiters
+ post_with_hint = Post.where(id: 1).optimizer_hints("**//BADHINT//**")
+ assert_match %r{BADHINT}, post_with_hint.to_sql
+ assert_no_match %r{\*/BADHINT}, post_with_hint.to_sql
+ assert_no_match %r{\*//BADHINT}, post_with_hint.to_sql
+ assert_no_match %r{BADHINT/\*}, post_with_hint.to_sql
+ assert_no_match %r{BADHINT//\*}, post_with_hint.to_sql
+ post_with_hint = Post.where(id: 1).optimizer_hints("/*+ BADHINT */")
+ assert_match %r{/\*\+ BADHINT \*/}, post_with_hint.to_sql
+ end
+
class EnsureRoundTripTypeCasting < ActiveRecord::Type::Value
def type
:string
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 0ab0459c38..2417775ef1 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -14,6 +14,7 @@ require "models/person"
require "models/computer"
require "models/reply"
require "models/company"
+require "models/contract"
require "models/bird"
require "models/car"
require "models/engine"
@@ -181,15 +182,64 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_select_with_from_includes_original_table_name
+ relation = Comment.joins(:post).select(:id).order(:id)
+ subquery = Comment.from("#{Comment.table_name} /*! USE INDEX (PRIMARY) */").joins(:post).select(:id).order(:id)
+ assert_equal relation.map(&:id), subquery.map(&:id)
+ end
+
+ def test_pluck_with_from_includes_original_table_name
+ relation = Comment.joins(:post).order(:id)
+ subquery = Comment.from("#{Comment.table_name} /*! USE INDEX (PRIMARY) */").joins(:post).order(:id)
+ assert_equal relation.pluck(:id), subquery.pluck(:id)
+ end
+
+ def test_select_with_from_includes_quoted_original_table_name
+ relation = Comment.joins(:post).select(:id).order(:id)
+ subquery = Comment.from("#{Comment.quoted_table_name} /*! USE INDEX (PRIMARY) */").joins(:post).select(:id).order(:id)
+ assert_equal relation.map(&:id), subquery.map(&:id)
+ end
+
+ def test_pluck_with_from_includes_quoted_original_table_name
+ relation = Comment.joins(:post).order(:id)
+ subquery = Comment.from("#{Comment.quoted_table_name} /*! USE INDEX (PRIMARY) */").joins(:post).order(:id)
+ assert_equal relation.pluck(:id), subquery.pluck(:id)
+ end
+
+ def test_select_with_subquery_in_from_uses_original_table_name
+ relation = Comment.joins(:post).select(:id).order(:id)
+ # Avoid subquery flattening by adding distinct to work with SQLite < 3.20.0.
+ subquery = Comment.from(Comment.all.distinct, Comment.quoted_table_name).joins(:post).select(:id).order(:id)
+ assert_equal relation.map(&:id), subquery.map(&:id)
+ end
+
+ def test_pluck_with_subquery_in_from_uses_original_table_name
+ relation = Comment.joins(:post).order(:id)
+ subquery = Comment.from(Comment.all, Comment.quoted_table_name).joins(:post).order(:id)
+ assert_equal relation.pluck(:id), subquery.pluck(:id)
+ end
+
def test_select_with_subquery_in_from_does_not_use_original_table_name
relation = Comment.group(:type).select("COUNT(post_id) AS post_count, type")
- subquery = Comment.from(relation).select("type", "post_count")
+ subquery = Comment.from(relation, "grouped_#{Comment.table_name}").select("type", "post_count")
assert_equal(relation.map(&:post_count).sort, subquery.map(&:post_count).sort)
end
def test_group_with_subquery_in_from_does_not_use_original_table_name
relation = Comment.group(:type).select("COUNT(post_id) AS post_count,type")
- subquery = Comment.from(relation).group("type").average("post_count")
+ subquery = Comment.from(relation, "grouped_#{Comment.table_name}").group("type").average("post_count")
+ assert_equal(relation.map(&:post_count).sort, subquery.values.sort)
+ end
+
+ def test_select_with_subquery_string_in_from_does_not_use_original_table_name
+ relation = Comment.group(:type).select("COUNT(post_id) AS post_count, type")
+ subquery = Comment.from("(#{relation.to_sql}) #{Comment.table_name}_grouped").select("type", "post_count")
+ assert_equal(relation.map(&:post_count).sort, subquery.map(&:post_count).sort)
+ end
+
+ def test_group_with_subquery_string_in_from_does_not_use_original_table_name
+ relation = Comment.group(:type).select("COUNT(post_id) AS post_count,type")
+ subquery = Comment.from("(#{relation.to_sql}) #{Comment.table_name}_grouped").group("type").average("post_count")
assert_equal(relation.map(&:post_count).sort, subquery.values.sort)
end
@@ -290,8 +340,17 @@ class RelationTest < ActiveRecord::TestCase
Topic.order(Arel.sql("title NULLS FIRST")).reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order(Arel.sql("title NULLS FIRST")).reverse_order
+ end
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
Topic.order(Arel.sql("title nulls last")).reverse_order
end
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order(Arel.sql("title NULLS FIRST, author_name")).reverse_order
+ end
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order(Arel.sql("author_name, title nulls last")).reverse_order
+ end
end
def test_default_reverse_order_on_table_without_primary_key
@@ -543,6 +602,13 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_extracted_association
+ relation_authors = assert_queries(2) { Post.all.extract_associated(:author) }
+ root_authors = assert_queries(2) { Post.extract_associated(:author) }
+ assert_equal relation_authors, root_authors
+ assert_equal Post.all.collect(&:author), relation_authors
+ end
+
def test_find_with_included_associations
assert_queries(2) do
posts = Post.includes(:comments).order("posts.id")
@@ -919,12 +985,25 @@ class RelationTest < ActiveRecord::TestCase
assert_queries(1) { assert_equal 11, posts.load.size }
end
+ def test_size_with_eager_loading_and_custom_select_and_order
+ posts = Post.includes(:comments).order("comments.id").select(:type)
+ assert_queries(1) { assert_equal 11, posts.size }
+ assert_queries(1) { assert_equal 11, posts.load.size }
+ end
+
def test_size_with_eager_loading_and_custom_order_and_distinct
posts = Post.includes(:comments).order("comments.id").distinct
assert_queries(1) { assert_equal 11, posts.size }
assert_queries(1) { assert_equal 11, posts.load.size }
end
+ def test_size_with_eager_loading_and_manual_distinct_select_and_custom_order
+ accounts = Account.select("DISTINCT accounts.firm_id").order("accounts.firm_id")
+
+ assert_queries(1) { assert_equal 5, accounts.size }
+ assert_queries(1) { assert_equal 5, accounts.load.size }
+ end
+
def test_count_explicit_columns
Post.update_all(comments_count: nil)
posts = Post.all
@@ -1166,13 +1245,23 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "green", parrot.color
end
+ def test_first_or_create_with_after_initialize
+ Bird.create!(color: "yellow", name: "canary")
+ parrot = assert_deprecated do
+ Bird.where(color: "green").first_or_create do |bird|
+ bird.name = "parrot"
+ bird.enable_count = true
+ end
+ end
+ assert_equal 0, parrot.total_count
+ end
+
def test_first_or_create_with_block
- canary = Bird.create!(color: "yellow", name: "canary")
+ Bird.create!(color: "yellow", name: "canary")
parrot = Bird.where(color: "green").first_or_create do |bird|
bird.name = "parrot"
- assert_equal canary, Bird.find_by!(name: "canary")
+ assert_deprecated { assert_equal 0, Bird.count }
end
- assert_equal 1, parrot.total_count
assert_kind_of Bird, parrot
assert_predicate parrot, :persisted?
assert_equal "green", parrot.color
@@ -1213,13 +1302,23 @@ class RelationTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordInvalid) { Bird.where(color: "green").first_or_create! }
end
+ def test_first_or_create_bang_with_after_initialize
+ Bird.create!(color: "yellow", name: "canary")
+ parrot = assert_deprecated do
+ Bird.where(color: "green").first_or_create! do |bird|
+ bird.name = "parrot"
+ bird.enable_count = true
+ end
+ end
+ assert_equal 0, parrot.total_count
+ end
+
def test_first_or_create_bang_with_valid_block
- canary = Bird.create!(color: "yellow", name: "canary")
+ Bird.create!(color: "yellow", name: "canary")
parrot = Bird.where(color: "green").first_or_create! do |bird|
bird.name = "parrot"
- assert_equal canary, Bird.find_by!(name: "canary")
+ assert_deprecated { assert_equal 0, Bird.count }
end
- assert_equal 1, parrot.total_count
assert_kind_of Bird, parrot
assert_predicate parrot, :persisted?
assert_equal "green", parrot.color
@@ -1268,13 +1367,23 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "green", parrot.color
end
+ def test_first_or_initialize_with_after_initialize
+ Bird.create!(color: "yellow", name: "canary")
+ parrot = assert_deprecated do
+ Bird.where(color: "green").first_or_initialize do |bird|
+ bird.name = "parrot"
+ bird.enable_count = true
+ end
+ end
+ assert_equal 0, parrot.total_count
+ end
+
def test_first_or_initialize_with_block
- canary = Bird.create!(color: "yellow", name: "canary")
+ Bird.create!(color: "yellow", name: "canary")
parrot = Bird.where(color: "green").first_or_initialize do |bird|
bird.name = "parrot"
- assert_equal canary, Bird.find_by!(name: "canary")
+ assert_deprecated { assert_equal 0, Bird.count }
end
- assert_equal 1, parrot.total_count
assert_kind_of Bird, parrot
assert_not_predicate parrot, :persisted?
assert_predicate parrot, :valid?
@@ -1406,10 +1515,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [posts(:welcome)], relation.to_a
author_posts = relation.except(:order, :limit)
- assert_equal Post.where(author_id: 1).to_a, author_posts.to_a
+ assert_equal Post.where(author_id: 1).sort_by(&:id), author_posts.sort_by(&:id)
+ assert_equal author_posts.sort_by(&:id), relation.scoping { Post.except(:order, :limit).sort_by(&:id) }
all_posts = relation.except(:where, :order, :limit)
- assert_equal Post.all, all_posts
+ assert_equal Post.all.sort_by(&:id), all_posts.sort_by(&:id)
+ assert_equal all_posts.sort_by(&:id), relation.scoping { Post.except(:where, :order, :limit).sort_by(&:id) }
end
def test_only
@@ -1417,10 +1528,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [posts(:welcome)], relation.to_a
author_posts = relation.only(:where)
- assert_equal Post.where(author_id: 1).to_a, author_posts.to_a
+ assert_equal Post.where(author_id: 1).sort_by(&:id), author_posts.sort_by(&:id)
+ assert_equal author_posts.sort_by(&:id), relation.scoping { Post.only(:where).sort_by(&:id) }
- all_posts = relation.only(:limit)
- assert_equal Post.limit(1).to_a, all_posts.to_a
+ all_posts = relation.only(:order)
+ assert_equal Post.order("id ASC").to_a, all_posts.to_a
+ assert_equal all_posts.to_a, relation.scoping { Post.only(:order).to_a }
end
def test_anonymous_extension
@@ -1646,6 +1759,24 @@ class RelationTest < ActiveRecord::TestCase
assert_predicate topics, :loaded?
end
+ def test_delete_by
+ david = authors(:david)
+
+ assert_difference("Post.count", -3) { david.posts.delete_by(body: "hello") }
+
+ deleted = Author.delete_by(id: david.id)
+ assert_equal 1, deleted
+ end
+
+ def test_destroy_by
+ david = authors(:david)
+
+ assert_difference("Post.count", -3) { david.posts.destroy_by(body: "hello") }
+
+ destroyed = Author.destroy_by(id: david.id)
+ assert_equal [david], destroyed
+ end
+
test "find_by with hash conditions returns the first matching record" do
assert_equal posts(:eager_other), Post.order(:id).find_by(author_id: 2)
end
@@ -1815,6 +1946,19 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [1, 1, 1], posts.map(&:author_address_id)
end
+ test "joins with select custom attribute" do
+ contract = Company.create!(name: "test").contracts.create!
+ company = Company.joins(:contracts).select(:id, :metadata).find(contract.company_id)
+ assert_equal contract.metadata, company.metadata
+ end
+
+ test "joins with order by custom attribute" do
+ companies = Company.create!([{ name: "test1" }, { name: "test2" }])
+ companies.each { |company| company.contracts.create! }
+ assert_equal companies, Company.joins(:contracts).order(:metadata)
+ assert_equal companies.reverse, Company.joins(:contracts).order(metadata: :desc)
+ end
+
test "delegations do not leak to other classes" do
Topic.all.by_lifo
assert Topic.all.class.method_defined?(:by_lifo)
@@ -1833,6 +1977,30 @@ class RelationTest < ActiveRecord::TestCase
assert_equal p2.first.comments, comments
end
+ def test_unscope_with_merge
+ p0 = Post.where(author_id: 0)
+ p1 = Post.where(author_id: 1, comments_count: 1)
+
+ assert_equal [posts(:authorless)], p0
+ assert_equal [posts(:thinking)], p1
+
+ comments = Comment.merge(p0).unscope(where: :author_id).where(post: p1)
+
+ assert_not_equal p0.first.comments, comments
+ assert_equal p1.first.comments, comments
+ end
+
+ def test_unscope_with_unknown_column
+ comment = comments(:greetings)
+ comment.update!(comments: 1)
+
+ comments = Comment.where(comments: 1).unscope(where: :unknown_column)
+ assert_equal [comment], comments
+
+ comments = Comment.where(comments: 1).unscope(where: { comments: :unknown_column })
+ assert_equal [comment], comments
+ end
+
def test_unscope_specific_where_value
posts = Post.where(title: "Welcome to the weblog", body: "Such a lovely day")
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 18b27bd6d1..6c884b4f45 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -148,6 +148,19 @@ class SanitizeTest < ActiveRecord::TestCase
assert_equal "foo in (#{quoted_nil})", bind("foo in (?)", [])
end
+ def test_bind_range
+ quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})
+ assert_equal "0", bind("?", 0..0)
+ assert_equal "1,2,3", bind("?", 1..3)
+ assert_equal quoted_abc, bind("?", "a"..."d")
+ end
+
+ def test_bind_empty_range
+ quoted_nil = ActiveRecord::Base.connection.quote(nil)
+ assert_equal quoted_nil, bind("?", 0...0)
+ assert_equal quoted_nil, bind("?", "a"..."a")
+ end
+
def test_bind_empty_string
quoted_empty = ActiveRecord::Base.connection.quote("")
assert_equal quoted_empty, bind("?", "")
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 6281712df6..e7bdab58c6 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -408,18 +408,18 @@ class DefaultScopingTest < ActiveRecord::TestCase
end
def test_joins_not_affected_by_scope_other_than_default_or_unscoped
- without_scope_on_post = Comment.joins(:post).to_a
+ without_scope_on_post = Comment.joins(:post).sort_by(&:id)
with_scope_on_post = nil
Post.where(id: [1, 5, 6]).scoping do
- with_scope_on_post = Comment.joins(:post).to_a
+ with_scope_on_post = Comment.joins(:post).sort_by(&:id)
end
- assert_equal with_scope_on_post, without_scope_on_post
+ assert_equal without_scope_on_post, with_scope_on_post
end
def test_unscoped_with_joins_should_not_have_default_scope
- assert_equal SpecialPostWithDefaultScope.unscoped { Comment.joins(:special_post_with_default_scope).to_a },
- Comment.joins(:post).to_a
+ assert_equal Comment.joins(:post).sort_by(&:id),
+ SpecialPostWithDefaultScope.unscoped { Comment.joins(:special_post_with_default_scope).sort_by(&:id) }
end
def test_sti_association_with_unscoped_not_affected_by_default_scope
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 27f9df295f..3488442cab 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -50,7 +50,7 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_calling_merge_at_first_in_scope
Topic.class_eval do
- scope :calling_merge_at_first_in_scope, Proc.new { merge(Topic.replied) }
+ scope :calling_merge_at_first_in_scope, Proc.new { merge(Topic.unscoped.replied) }
end
assert_equal Topic.calling_merge_at_first_in_scope.to_a, Topic.replied.to_a
end
@@ -447,9 +447,10 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).to_a.uniq
end
- def test_chaining_doesnt_leak_conditions_to_another_scopes
- expected = Topic.where(approved: false).where(id: Topic.children.select(:parent_id))
- assert_equal expected.to_a, Topic.rejected.has_children.to_a
+ def test_class_method_in_scope
+ assert_deprecated do
+ assert_equal [topics(:second)], topics(:first).approved_replies.ordered
+ end
end
def test_nested_scoping
@@ -601,4 +602,14 @@ class NamedScopingTest < ActiveRecord::TestCase
Topic.create!
assert_predicate Topic, :one?
end
+
+ def test_scope_with_annotation
+ Topic.class_eval do
+ scope :including_annotate_in_scope, Proc.new { annotate("from-scope") }
+ end
+
+ assert_sql(%r{/\* from-scope \*/}) do
+ assert Topic.including_annotate_in_scope.to_a, Topic.all.to_a
+ end
+ end
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index b1f2ffe29c..50b514d464 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -130,6 +130,44 @@ class RelationScopingTest < ActiveRecord::TestCase
end
end
+ def test_scoped_find_with_annotation
+ Developer.annotate("scoped").scoping do
+ developer = nil
+ assert_sql(%r{/\* scoped \*/}) do
+ developer = Developer.where("name = 'David'").first
+ end
+ assert_equal "David", developer.name
+ end
+ end
+
+ def test_find_with_annotation_unscoped
+ Developer.annotate("scoped").unscoped do
+ developer = nil
+ log = capture_sql do
+ developer = Developer.where("name = 'David'").first
+ end
+
+ assert_not_predicate log, :empty?
+ assert_predicate log.select { |query| query.match?(%r{/\* scoped \*/}) }, :empty?
+
+ assert_equal "David", developer.name
+ end
+ end
+
+ def test_find_with_annotation_unscope
+ developer = nil
+ log = capture_sql do
+ developer = Developer.annotate("unscope").
+ where("name = 'David'").
+ unscope(:annotate).first
+ end
+
+ assert_not_predicate log, :empty?
+ assert_predicate log.select { |query| query.match?(%r{/\* unscope \*/}) }, :empty?
+
+ assert_equal "David", developer.name
+ end
+
def test_scoped_find_include
# with the include, will retrieve only developers for the given project
scoped_developers = Developer.includes(:projects).scoping do
@@ -373,7 +411,19 @@ class HasManyScopingTest < ActiveRecord::TestCase
def test_nested_scope_finder
Comment.where("1=0").scoping do
- assert_equal 0, @welcome.comments.count
+ assert_equal 2, @welcome.comments.count
+ assert_equal "a comment...", @welcome.comments.what_are_you
+ end
+
+ Comment.where("1=1").scoping do
+ assert_equal 2, @welcome.comments.count
+ assert_equal "a comment...", @welcome.comments.what_are_you
+ end
+ end
+
+ def test_none_scoping
+ Comment.none.scoping do
+ assert_equal 2, @welcome.comments.count
assert_equal "a comment...", @welcome.comments.what_are_you
end
@@ -414,7 +464,19 @@ class HasAndBelongsToManyScopingTest < ActiveRecord::TestCase
def test_nested_scope_finder
Category.where("1=0").scoping do
- assert_equal 0, @welcome.categories.count
+ assert_equal 2, @welcome.categories.count
+ assert_equal "a category...", @welcome.categories.what_are_you
+ end
+
+ Category.where("1=1").scoping do
+ assert_equal 2, @welcome.categories.count
+ assert_equal "a category...", @welcome.categories.what_are_you
+ end
+ end
+
+ def test_none_scoping
+ Category.none.scoping do
+ assert_equal 2, @welcome.categories.count
assert_equal "a category...", @welcome.categories.what_are_you
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index fa136fe8da..ecf81b2042 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -1,28 +1,29 @@
# frozen_string_literal: true
require "cases/helper"
-require "models/topic"
-require "models/reply"
require "models/person"
require "models/traffic_light"
require "models/post"
-require "bcrypt"
class SerializedAttributeTest < ActiveRecord::TestCase
fixtures :topics, :posts
MyObject = Struct.new :attribute1, :attribute2
- # NOTE: Use a duplicate of Topic so attribute
- # changes don't bleed into other tests
- Topic = ::Topic.dup
+ class Topic < ActiveRecord::Base
+ serialize :content
+ end
+
+ class ImportantTopic < Topic
+ serialize :important, Hash
+ end
teardown do
Topic.serialize("content")
end
def test_serialize_does_not_eagerly_load_columns
- reset_column_information_of(Topic)
+ Topic.reset_column_information
assert_no_queries do
Topic.serialize(:content)
end
@@ -53,10 +54,10 @@ class SerializedAttributeTest < ActiveRecord::TestCase
def test_serialized_attributes_from_database_on_subclass
Topic.serialize :content, Hash
- t = Reply.new(content: { foo: :bar })
+ t = ImportantTopic.new(content: { foo: :bar })
assert_equal({ foo: :bar }, t.content)
t.save!
- t = Reply.last
+ t = ImportantTopic.last
assert_equal({ foo: :bar }, t.content)
end
@@ -371,14 +372,13 @@ class SerializedAttributeTest < ActiveRecord::TestCase
end
def test_serialized_attribute_works_under_concurrent_initial_access
- model = ::Topic.dup
+ model = Class.new(Topic)
- topic = model.last
+ topic = model.create!
topic.update group: "1"
model.serialize :group, JSON
-
- reset_column_information_of(model)
+ model.reset_column_information
# This isn't strictly necessary for the test, but a little bit of
# knowledge of internals allows us to make failures far more likely.
@@ -398,12 +398,4 @@ class SerializedAttributeTest < ActiveRecord::TestCase
# raw string ("1"), or raise an exception.
assert_equal [1] * threads.size, threads.map(&:value)
end
-
- private
-
- def reset_column_information_of(topic_class)
- topic_class.reset_column_information
- # reset original topic to undefine attribute methods
- ::Topic.reset_column_information
- end
end
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 4457cfbd37..91c0e959f4 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -79,6 +79,74 @@ class StoreTest < ActiveRecord::TestCase
assert_not_predicate @john, :settings_changed?
end
+ test "updating the store will mark accessor as changed" do
+ @john.color = "red"
+ assert @john.color_changed?
+ end
+
+ test "new record and no accessors changes" do
+ user = Admin::User.new
+ assert_not user.color_changed?
+ assert_nil user.color_was
+ assert_nil user.color_change
+
+ user.color = "red"
+ assert user.color_changed?
+ assert_nil user.color_was
+ assert_equal "red", user.color_change[1]
+ end
+
+ test "updating the store won't mark accessor as changed if the whole store was updated" do
+ @john.settings = { color: @john.color, some: "thing" }
+ assert @john.settings_changed?
+ assert_not @john.color_changed?
+ end
+
+ test "updating the store populates the accessor changed array correctly" do
+ @john.color = "red"
+ assert_equal "black", @john.color_was
+ assert_equal "black", @john.color_change[0]
+ assert_equal "red", @john.color_change[1]
+ end
+
+ test "updating the store won't mark accessor as changed if the value isn't changed" do
+ @john.color = @john.color
+ assert_not @john.color_changed?
+ end
+
+ test "nullifying the store mark accessor as changed" do
+ color = @john.color
+ @john.settings = nil
+ assert @john.color_changed?
+ assert_equal color, @john.color_was
+ assert_equal [color, nil], @john.color_change
+ end
+
+ test "dirty methods for suffixed accessors" do
+ @john.configs[:two_factor_auth] = true
+ assert @john.two_factor_auth_configs_changed?
+ assert_nil @john.two_factor_auth_configs_was
+ assert_equal [nil, true], @john.two_factor_auth_configs_change
+ end
+
+ test "dirty methods for prefixed accessors" do
+ @john.spouse[:name] = "Lena"
+ assert @john.partner_name_changed?
+ assert_equal "Dallas", @john.partner_name_was
+ assert_equal ["Dallas", "Lena"], @john.partner_name_change
+ end
+
+ test "saved changes tracking for accessors" do
+ @john.spouse[:name] = "Lena"
+ assert @john.partner_name_changed?
+
+ @john.save!
+ assert_not @john.partner_name_change
+ assert @john.saved_change_to_partner_name?
+ assert_equal ["Dallas", "Lena"], @john.saved_change_to_partner_name
+ assert_equal "Dallas", @john.partner_name_before_last_save
+ end
+
test "object initialization with not nullable column" do
assert_equal true, @john.remember_login
end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 3fd1813d64..ffe94eee0f 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -2,6 +2,7 @@
require "cases/helper"
require "active_record/tasks/database_tasks"
+require "models/author"
module ActiveRecord
module DatabaseTasksSetupper
@@ -49,6 +50,8 @@ module ActiveRecord
protected_environments = ActiveRecord::Base.protected_environments
current_env = ActiveRecord::Base.connection.migration_context.current_environment
+ InternalMetadata[:environment] = current_env
+
assert_called_on_instance_of(
ActiveRecord::MigrationContext,
:current_version,
@@ -72,6 +75,9 @@ module ActiveRecord
def test_raises_an_error_when_called_with_protected_environment_which_name_is_a_symbol
protected_environments = ActiveRecord::Base.protected_environments
current_env = ActiveRecord::Base.connection.migration_context.current_environment
+
+ InternalMetadata[:environment] = current_env
+
assert_called_on_instance_of(
ActiveRecord::MigrationContext,
:current_version,
@@ -754,7 +760,7 @@ module ActiveRecord
end
class DatabaseTasksMigrateTest < DatabaseTasksMigrationTestCase
- def test_migrate_set_and_unset_verbose_and_version_env_vars
+ def test_can_migrate_from_pending_migration_error_action_dispatch
verbose, version = ENV["VERBOSE"], ENV["VERSION"]
ENV["VERSION"] = "2"
ENV["VERBOSE"] = "false"
@@ -766,7 +772,9 @@ module ActiveRecord
ENV.delete("VERBOSE")
# re-run up migration
- assert_includes capture_migration_output, "migrating"
+ assert_includes(capture(:stdout) do
+ ActiveSupport::ActionableError.dispatch ActiveRecord::PendingMigrationError, "Run pending migrations"
+ end, "migrating")
ensure
ENV["VERBOSE"], ENV["VERSION"] = verbose, version
end
@@ -944,6 +952,176 @@ module ActiveRecord
end
end
+ unless in_memory_db?
+ class DatabaseTasksTruncateAllTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ fixtures :authors, :author_addresses
+
+ def setup
+ SchemaMigration.create_table
+ SchemaMigration.create!(version: SchemaMigration.table_name)
+ InternalMetadata.create_table
+ InternalMetadata.create!(key: InternalMetadata.table_name)
+ end
+
+ def teardown
+ SchemaMigration.delete_all
+ InternalMetadata.delete_all
+ ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
+ end
+
+ def test_truncate_tables
+ assert_operator SchemaMigration.count, :>, 0
+ assert_operator InternalMetadata.count, :>, 0
+ assert_operator Author.count, :>, 0
+ assert_operator AuthorAddress.count, :>, 0
+
+ old_configurations = ActiveRecord::Base.configurations
+ configurations = { development: ActiveRecord::Base.configurations["arunit"] }
+ ActiveRecord::Base.configurations = configurations
+
+ ActiveRecord::Tasks::DatabaseTasks.stub(:root, nil) do
+ ActiveRecord::Tasks::DatabaseTasks.truncate_all(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+
+ assert_operator SchemaMigration.count, :>, 0
+ assert_operator InternalMetadata.count, :>, 0
+ assert_equal 0, Author.count
+ assert_equal 0, AuthorAddress.count
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
+ end
+ end
+
+ class DatabaseTasksTruncateAllWithPrefixTest < DatabaseTasksTruncateAllTest
+ setup do
+ ActiveRecord::Base.table_name_prefix = "p_"
+
+ SchemaMigration.reset_table_name
+ InternalMetadata.reset_table_name
+ end
+
+ teardown do
+ ActiveRecord::Base.table_name_prefix = nil
+
+ SchemaMigration.reset_table_name
+ InternalMetadata.reset_table_name
+ end
+ end
+
+ class DatabaseTasksTruncateAllWithSuffixTest < DatabaseTasksTruncateAllTest
+ setup do
+ ActiveRecord::Base.table_name_suffix = "_s"
+
+ SchemaMigration.reset_table_name
+ InternalMetadata.reset_table_name
+ end
+
+ teardown do
+ ActiveRecord::Base.table_name_suffix = nil
+
+ SchemaMigration.reset_table_name
+ InternalMetadata.reset_table_name
+ end
+ end
+ end
+
+ class DatabaseTasksTruncateAllWithMultipleDatabasesTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {
+ "development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
+ "test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
+ "production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } }
+ }
+ end
+
+ def test_truncate_all_databases_for_environment
+ with_stubbed_configurations do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :truncate_tables,
+ [
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.truncate_all(
+ ActiveSupport::StringInquirer.new("test")
+ )
+ end
+ end
+ end
+
+ def test_truncate_all_databases_with_url_for_environment
+ with_stubbed_configurations do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :truncate_tables,
+ [
+ ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"],
+ ["adapter" => "abstract", "database" => "secondary-prod-db", "host" => "secondary-prod-db-host"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.truncate_all(
+ ActiveSupport::StringInquirer.new("production")
+ )
+ end
+ end
+ end
+
+ def test_truncate_all_development_databases_when_env_is_not_specified
+ with_stubbed_configurations do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :truncate_tables,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.truncate_all(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ end
+
+ def test_truncate_all_development_databases_when_env_is_development
+ old_env = ENV["RAILS_ENV"]
+ ENV["RAILS_ENV"] = "development"
+
+ with_stubbed_configurations do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :truncate_tables,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.truncate_all(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ ensure
+ ENV["RAILS_ENV"] = old_env
+ end
+
+ private
+ def with_stubbed_configurations
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ yield
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
+ end
+ end
+
class DatabaseTasksCharsetTest < ActiveRecord::TestCase
include DatabaseTasksSetupper
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 5b25432dc0..81f7226718 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -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/time_precision_test.rb b/activerecord/test/cases/time_precision_test.rb
index 086500de38..1abd857216 100644
--- a/activerecord/test/cases/time_precision_test.rb
+++ b/activerecord/test/cases/time_precision_test.rb
@@ -45,6 +45,26 @@ if subsecond_precision_supported?
assert_equal 123456000, foo.finish.nsec
end
+ unless current_adapter?(:Mysql2Adapter)
+ def test_no_time_precision_isnt_truncated_on_assignment
+ @connection.create_table(:foos, force: true)
+ @connection.add_column :foos, :start, :time
+ @connection.add_column :foos, :finish, :time, precision: 6
+
+ time = ::Time.now.change(nsec: 123)
+ foo = Foo.new(start: time, finish: time)
+
+ assert_equal 123, foo.start.nsec
+ assert_equal 0, foo.finish.nsec
+
+ foo.save!
+ foo.reload
+
+ assert_equal 0, foo.start.nsec
+ assert_equal 0, foo.finish.nsec
+ end
+ end
+
def test_passing_precision_to_time_does_not_set_limit
@connection.create_table(:foos, force: true) do |t|
t.time :start, precision: 3
@@ -55,7 +75,7 @@ if subsecond_precision_supported?
end
def test_invalid_time_precision_raises_error
- assert_raises ActiveRecord::ActiveRecordError do
+ assert_raises ArgumentError do
@connection.create_table(:foos, force: true) do |t|
t.time :start, precision: 7
t.time :finish, precision: 7
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 75ecd6fc40..232e018e03 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -40,17 +40,25 @@ class TimestampTest < ActiveRecord::TestCase
assert_not_equal @previously_updated_at, @developer.updated_at
assert_equal previous_salary + 10000, @developer.salary
- assert @developer.salary_changed?, "developer salary should have changed"
- assert @developer.changed?, "developer should be marked as changed"
+ assert_predicate @developer, :salary_changed?, "developer salary should have changed"
+ assert_predicate @developer, :changed?, "developer should be marked as changed"
+ assert_equal ["salary"], @developer.changed
+ assert_predicate @developer, :saved_changes?
+ assert_equal ["updated_at", "updated_on"], @developer.saved_changes.keys.sort
+
@developer.reload
assert_equal previous_salary, @developer.salary
end
def test_touching_a_record_with_default_scope_that_excludes_it_updates_its_timestamp
developer = @developer.becomes(DeveloperCalledJamis)
-
developer.touch
+
assert_not_equal @previously_updated_at, developer.updated_at
+ assert_not_predicate developer, :changed?
+ assert_predicate developer, :saved_changes?
+ assert_equal ["updated_at", "updated_on"], developer.saved_changes.keys.sort
+
developer.reload
assert_not_equal @previously_updated_at, developer.updated_at
end
diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb
index cd3d5ed7d1..f1a9cf2d05 100644
--- a/activerecord/test/cases/touch_later_test.rb
+++ b/activerecord/test/cases/touch_later_test.rb
@@ -10,7 +10,7 @@ require "models/tree"
class TouchLaterTest < ActiveRecord::TestCase
fixtures :nodes, :trees
- def test_touch_laster_raise_if_non_persisted
+ def test_touch_later_raise_if_non_persisted
invoice = Invoice.new
Invoice.transaction do
assert_not_predicate invoice, :persisted?
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index aa6b7915a2..100bd6a925 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -36,8 +36,11 @@ 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) }
after_create_commit { |record| record.do_after_commit(:create) }
after_update_commit { |record| record.do_after_commit(:update) }
after_destroy_commit { |record| record.do_after_commit(:destroy) }
@@ -110,6 +113,43 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal [:after_commit], @first.history
end
+ def test_dont_call_any_callbacks_after_transaction_commits_for_invalid_record
+ @first.after_commit_block { |r| r.history << :after_commit }
+ @first.after_rollback_block { |r| r.history << :after_rollback }
+
+ def @first.valid?(*)
+ false
+ end
+
+ assert_not @first.save
+ assert_equal [], @first.history
+ end
+
+ def test_dont_call_any_callbacks_after_explicit_transaction_commits_for_invalid_record
+ @first.after_commit_block { |r| r.history << :after_commit }
+ @first.after_rollback_block { |r| r.history << :after_rollback }
+
+ def @first.valid?(*)
+ false
+ end
+
+ @first.transaction do
+ assert_not @first.save
+ end
+ assert_equal [], @first.history
+ end
+
+ def test_only_call_after_commit_on_save_after_transaction_commits_for_saving_record
+ record = TopicWithCallbacks.new(title: "New topic", written_on: Date.today)
+ record.after_commit_block(:save) { |r| r.history << :after_save }
+
+ record.save!
+ assert_equal [:after_save], record.history
+
+ record.update!(title: "Another topic")
+ assert_equal [:after_save, :after_save], record.history
+ end
+
def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record
add_transaction_execution_blocks @first
@@ -511,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
@@ -524,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
@@ -536,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
@@ -547,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
@@ -586,7 +672,7 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase
@topic.content = "foo"
@topic.save!
end
- @topic.class.connection.add_transaction_record(@topic)
+ @topic.send(:add_to_transaction)
end
assert_equal [:before_commit, :after_commit], @topic.history
end
@@ -596,7 +682,7 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase
@topic.transaction(requires_new: true) do
@topic.content = "foo"
@topic.save!
- @topic.class.connection.add_transaction_record(@topic)
+ @topic.send(:add_to_transaction)
end
end
assert_equal [:before_commit, :after_commit], @topic.history
@@ -617,7 +703,7 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase
@topic.content = "foo"
@topic.save!
end
- @topic.class.connection.add_transaction_record(@topic)
+ @topic.send(:add_to_transaction)
raise ActiveRecord::Rollback
end
assert_equal [:rollback], @topic.history
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 1009dd0f99..6795996cca 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -18,6 +18,65 @@ class TransactionTest < ActiveRecord::TestCase
@first, @second = Topic.find(1, 2).sort_by(&:id)
end
+ def test_rollback_dirty_changes
+ topic = topics(:fifth)
+
+ ActiveRecord::Base.transaction do
+ topic.update(title: "Ruby on Rails")
+ raise ActiveRecord::Rollback
+ end
+
+ title_change = ["The Fifth Topic of the day", "Ruby on Rails"]
+ assert_equal title_change, topic.changes["title"]
+ end
+
+ def test_rollback_dirty_changes_multiple_saves
+ topic = topics(:fifth)
+
+ ActiveRecord::Base.transaction do
+ topic.update(title: "Ruby on Rails")
+ topic.update(title: "Another Title")
+ raise ActiveRecord::Rollback
+ end
+
+ title_change = ["The Fifth Topic of the day", "Another Title"]
+ assert_equal title_change, topic.changes["title"]
+ end
+
+ def test_rollback_dirty_changes_then_retry_save
+ topic = topics(:fifth)
+
+ ActiveRecord::Base.transaction do
+ topic.update(title: "Ruby on Rails")
+ raise ActiveRecord::Rollback
+ end
+
+ title_change = ["The Fifth Topic of the day", "Ruby on Rails"]
+ assert_equal title_change, topic.changes["title"]
+
+ assert topic.save
+
+ assert_equal title_change, topic.saved_changes["title"]
+ assert_equal topic.title, topic.reload.title
+ end
+
+ def test_rollback_dirty_changes_then_retry_save_on_new_record
+ topic = Topic.new(title: "Ruby on Rails")
+
+ ActiveRecord::Base.transaction do
+ topic.save
+ raise ActiveRecord::Rollback
+ end
+
+ title_change = [nil, "Ruby on Rails"]
+ assert_equal title_change, topic.changes["title"]
+
+ assert topic.save
+
+ assert_equal title_change, topic.saved_changes["title"]
+ assert_equal topic.title, topic.reload.title
+ end
+
def test_persisted_in_a_model_with_custom_primary_key_after_failed_save
movie = Movie.create
assert_not_predicate movie, :persisted?
@@ -26,28 +85,31 @@ class TransactionTest < ActiveRecord::TestCase
def test_raise_after_destroy
assert_not_predicate @first, :frozen?
- assert_raises(RuntimeError) {
- Topic.transaction do
- @first.destroy
- assert_predicate @first, :frozen?
- raise
+ assert_not_called(@first, :rolledback!) do
+ assert_raises(RuntimeError) do
+ Topic.transaction do
+ @first.destroy
+ assert_predicate @first, :frozen?
+ raise
+ end
end
- }
+ end
- assert @first.reload
assert_not_predicate @first, :frozen?
end
def test_successful
- Topic.transaction do
- @first.approved = true
- @second.approved = false
- @first.save
- @second.save
+ assert_not_called(@first, :committed!) do
+ Topic.transaction do
+ @first.approved = true
+ @second.approved = false
+ @first.save
+ @second.save
+ end
end
- assert Topic.find(1).approved?, "First should have been approved"
- assert_not Topic.find(2).approved?, "Second should have been unapproved"
+ assert_predicate Topic.find(1), :approved?, "First should have been approved"
+ assert_not_predicate Topic.find(2), :approved?, "Second should have been unapproved"
end
def transaction_with_return
@@ -62,7 +124,7 @@ class TransactionTest < ActiveRecord::TestCase
def test_add_to_null_transaction
topic = Topic.new
- topic.add_to_transaction
+ topic.send(:add_to_transaction)
end
def test_successful_with_return
@@ -76,11 +138,13 @@ class TransactionTest < ActiveRecord::TestCase
end
end
- transaction_with_return
+ assert_not_called(@first, :committed!) do
+ transaction_with_return
+ end
assert committed
- assert Topic.find(1).approved?, "First should have been approved"
- assert_not Topic.find(2).approved?, "Second should have been unapproved"
+ assert_predicate Topic.find(1), :approved?, "First should have been approved"
+ assert_not_predicate Topic.find(2), :approved?, "Second should have been unapproved"
ensure
Topic.connection.class_eval do
remove_method :commit_db_transaction
@@ -99,9 +163,11 @@ class TransactionTest < ActiveRecord::TestCase
end
end
- Topic.transaction do
- @first.approved = true
- @first.save!
+ assert_not_called(@first, :committed!) do
+ Topic.transaction do
+ @first.approved = true
+ @first.save!
+ end
end
assert_equal 0, num
@@ -113,19 +179,21 @@ class TransactionTest < ActiveRecord::TestCase
end
def test_successful_with_instance_method
- @first.transaction do
- @first.approved = true
- @second.approved = false
- @first.save
- @second.save
+ assert_not_called(@first, :committed!) do
+ @first.transaction do
+ @first.approved = true
+ @second.approved = false
+ @first.save
+ @second.save
+ end
end
- assert Topic.find(1).approved?, "First should have been approved"
- assert_not Topic.find(2).approved?, "Second should have been unapproved"
+ assert_predicate Topic.find(1), :approved?, "First should have been approved"
+ assert_not_predicate Topic.find(2), :approved?, "Second should have been unapproved"
end
def test_failing_on_exception
- begin
+ assert_not_called(@first, :rolledback!) do
Topic.transaction do
@first.approved = true
@second.approved = false
@@ -137,11 +205,11 @@ class TransactionTest < ActiveRecord::TestCase
# caught it
end
- assert @first.approved?, "First should still be changed in the objects"
- assert_not @second.approved?, "Second should still be changed in the objects"
+ assert_predicate @first, :approved?, "First should still be changed in the objects"
+ assert_not_predicate @second, :approved?, "Second should still be changed in the objects"
- assert_not Topic.find(1).approved?, "First shouldn't have been approved"
- assert Topic.find(2).approved?, "Second should still be approved"
+ assert_not_predicate Topic.find(1), :approved?, "First shouldn't have been approved"
+ assert_predicate Topic.find(2), :approved?, "Second should still be approved"
end
def test_raising_exception_in_callback_rollbacks_in_save
@@ -150,8 +218,10 @@ class TransactionTest < ActiveRecord::TestCase
end
@first.approved = true
- e = assert_raises(RuntimeError) { @first.save }
- assert_equal "Make the transaction rollback", e.message
+ assert_not_called(@first, :rolledback!) do
+ e = assert_raises(RuntimeError) { @first.save }
+ assert_equal "Make the transaction rollback", e.message
+ end
assert_not_predicate Topic.find(1), :approved?
end
@@ -159,13 +229,15 @@ class TransactionTest < ActiveRecord::TestCase
def @first.before_save_for_transaction
raise ActiveRecord::Rollback
end
- assert_not @first.approved
+ assert_not_predicate @first, :approved?
- Topic.transaction do
- @first.approved = true
- @first.save!
+ assert_not_called(@first, :rolledback!) do
+ Topic.transaction do
+ @first.approved = true
+ @first.save!
+ end
end
- assert_not Topic.find(@first.id).approved?, "Should not commit the approved flag"
+ assert_not_predicate Topic.find(@first.id), :approved?, "Should not commit the approved flag"
end
def test_raising_exception_in_nested_transaction_restore_state_in_save
@@ -175,11 +247,13 @@ class TransactionTest < ActiveRecord::TestCase
raise "Make the transaction rollback"
end
- assert_raises(RuntimeError) do
- Topic.transaction { topic.save }
+ assert_not_called(topic, :rolledback!) do
+ assert_raises(RuntimeError) do
+ Topic.transaction { topic.save }
+ end
end
- assert topic.new_record?, "#{topic.inspect} should be new record"
+ assert_predicate topic, :new_record?, "#{topic.inspect} should be new record"
end
def test_transaction_state_is_cleared_when_record_is_persisted
diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb
index f0a0e7f805..49746996bc 100644
--- a/activerecord/test/cases/unconnected_test.rb
+++ b/activerecord/test/cases/unconnected_test.rb
@@ -11,6 +11,12 @@ class TestUnconnectedAdapter < ActiveRecord::TestCase
def setup
@underlying = ActiveRecord::Base.connection
@specification = ActiveRecord::Base.remove_connection
+
+ # Clear out connection info from other pids (like a fork parent) too
+ pool_map = ActiveRecord::Base.connection_handler.instance_variable_get(:@owner_to_pool)
+ (pool_map.keys - [Process.pid]).each do |other_pid|
+ pool_map.delete(other_pid)
+ end
end
teardown do
diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
index 703c24b340..993c201f03 100644
--- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -4,16 +4,20 @@ require "cases/helper"
require "models/topic"
class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
+ class Backend < I18n::Backend::Simple
+ include I18n::Backend::Fallbacks
+ end
+
def setup
Topic.clear_validators!
@topic = Topic.new
- I18n.backend = I18n::Backend::Simple.new
+ I18n.backend = Backend.new
end
def reset_i18n_load_path
@old_load_path, @old_backend = I18n.load_path.dup, I18n.backend
I18n.load_path.clear
- I18n.backend = I18n::Backend::Simple.new
+ I18n.backend = Backend.new
yield
ensure
I18n.load_path.replace @old_load_path
@@ -83,4 +87,16 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, value: "title")
end
end
+
+ test "activerecord attributes scope falls back to parent locale before it falls back to the :errors namespace" do
+ reset_i18n_load_path do
+ I18n.backend.store_translations "en", activerecord: { errors: { models: { topic: { attributes: { title: { taken: "custom en message" } } } } } }
+ I18n.backend.store_translations "en-US", errors: { messages: { taken: "generic en-US fallback" } }
+
+ I18n.with_locale "en-US" do
+ assert_equal "custom en message", @topic.errors.generate_message(:title, :taken, value: "title")
+ assert_equal "generic en-US fallback", @topic.errors.generate_message(:heading, :taken, value: "heading")
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index b7c52ea18c..2645776ab7 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -40,11 +40,11 @@ class I18nValidationTest < ActiveRecord::TestCase
COMMON_CASES = [
# [ case, validation_options, generate_message_options]
[ "given no options", {}, {}],
- [ "given custom message", { message: "custom" }, { message: "custom" }],
- [ "given if condition", { if: lambda { true } }, {}],
- [ "given unless condition", { unless: lambda { false } }, {}],
- [ "given option that is not reserved", { format: "jpg" }, { format: "jpg" }],
- [ "given on condition", { on: [:create, :update] }, {}]
+ [ "given custom message", { message: "custom" }, { message: "custom" }],
+ [ "given if condition", { if: lambda { true } }, {}],
+ [ "given unless condition", { unless: lambda { false } }, {}],
+ [ "given option that is not reserved", { format: "jpg" }, { format: "jpg" }],
+ [ "given on condition", { on: [:create, :update] }, {}]
]
COMMON_CASES.each do |name, validation_options, generate_message_options|
@@ -53,6 +53,7 @@ class I18nValidationTest < ActiveRecord::TestCase
@topic.title = unique_topic.title
assert_called_with(@topic.errors, :generate_message, [:title, :taken, generate_message_options.merge(value: "unique!")]) do
@topic.valid?
+ @topic.errors.messages
end
end
end
@@ -62,6 +63,7 @@ class I18nValidationTest < ActiveRecord::TestCase
Topic.validates_associated :replies, validation_options
assert_called_with(replied_topic.errors, :generate_message, [:replies, :invalid, generate_message_options.merge(value: replied_topic.replies)]) do
replied_topic.save
+ replied_topic.errors.messages
end
end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 8f6f47e5fb..76163e3093 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -314,6 +314,51 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t3.save, "Should save t3 as unique"
end
+ if current_adapter?(:Mysql2Adapter)
+ def test_deprecate_validate_uniqueness_mismatched_collation
+ Topic.validates_uniqueness_of(:author_email_address)
+
+ topic1 = Topic.new(author_email_address: "david@loudthinking.com")
+ topic2 = Topic.new(author_email_address: "David@loudthinking.com")
+
+ assert_equal 1, Topic.where(author_email_address: "david@loudthinking.com").count
+
+ assert_deprecated do
+ assert_not topic1.valid?
+ assert_not topic1.save
+ assert topic2.valid?
+ assert topic2.save
+ end
+
+ assert_equal 2, Topic.where(author_email_address: "david@loudthinking.com").count
+ assert_equal 2, Topic.where(author_email_address: "David@loudthinking.com").count
+ end
+ end
+
+ def test_validate_case_sensitive_uniqueness_by_default
+ Topic.validates_uniqueness_of(:author_email_address)
+
+ topic1 = Topic.new(author_email_address: "david@loudthinking.com")
+ topic2 = Topic.new(author_email_address: "David@loudthinking.com")
+
+ assert_equal 1, Topic.where(author_email_address: "david@loudthinking.com").count
+
+ ActiveSupport::Deprecation.silence do
+ assert_not topic1.valid?
+ assert_not topic1.save
+ assert topic2.valid?
+ assert topic2.save
+ end
+
+ if current_adapter?(:Mysql2Adapter)
+ assert_equal 2, Topic.where(author_email_address: "david@loudthinking.com").count
+ assert_equal 2, Topic.where(author_email_address: "David@loudthinking.com").count
+ else
+ assert_equal 1, Topic.where(author_email_address: "david@loudthinking.com").count
+ assert_equal 1, Topic.where(author_email_address: "David@loudthinking.com").count
+ end
+ end
+
def test_validate_case_sensitive_uniqueness
Topic.validates_uniqueness_of(:title, case_sensitive: true, allow_nil: true)
@@ -510,7 +555,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
abc.save!
end
assert_match(/\AUnknown primary key for table dashboards in model/, e.message)
- assert_match(/Can not validate uniqueness for persisted record without primary key.\z/, e.message)
+ assert_match(/Cannot validate uniqueness for persisted record without primary key.\z/, e.message)
end
def test_validate_uniqueness_ignores_itself_when_primary_key_changed
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 9a70934b7e..4f98a6b7fc 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -145,15 +145,17 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_acceptance_of_with_undefined_attribute_methods
- Topic.validates_acceptance_of(:approved)
- topic = Topic.new(approved: true)
- Topic.undefine_attribute_methods
+ klass = Class.new(Topic)
+ klass.validates_acceptance_of(:approved)
+ topic = klass.new(approved: true)
+ klass.undefine_attribute_methods
assert topic.approved
end
def test_validates_acceptance_of_as_database_column
- Topic.validates_acceptance_of(:approved)
- topic = Topic.create("approved" => true)
+ klass = Class.new(Topic)
+ klass.validates_acceptance_of(:approved)
+ topic = klass.create("approved" => true)
assert topic["approved"]
end
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 8b5a2fa0c8..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
@@ -220,3 +221,12 @@ class AuthorFavorite < ActiveRecord::Base
belongs_to :author
belongs_to :favorite_author, class_name: "Author"
end
+
+class AuthorFavoriteWithScope < ActiveRecord::Base
+ self.table_name = "author_favorites"
+
+ default_scope { order(id: :asc) }
+
+ belongs_to :author
+ belongs_to :favorite_author, class_name: "Author"
+end
diff --git a/activerecord/test/models/bird.rb b/activerecord/test/models/bird.rb
index c9f6759c7d..20af7c6122 100644
--- a/activerecord/test/models/bird.rb
+++ b/activerecord/test/models/bird.rb
@@ -17,8 +17,8 @@ class Bird < ActiveRecord::Base
throw(:abort)
end
- attr_accessor :total_count
+ attr_accessor :total_count, :enable_count
after_initialize do
- self.total_count = Bird.count
+ self.total_count = Bird.count if enable_count
end
end
diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb
index 2ccc00bed9..8c86879dc6 100644
--- a/activerecord/test/models/category.rb
+++ b/activerecord/test/models/category.rb
@@ -26,6 +26,7 @@ class Category < ActiveRecord::Base
has_many :categorizations
has_many :special_categorizations
has_many :post_comments, through: :posts, source: :comments
+ has_many :ordered_post_comments, -> { order(id: :desc) }, through: :posts, source: :comments
has_many :authors, through: :categorizations
has_many :authors_with_select, -> { select "authors.*, categorizations.post_id" }, through: :categorizations, source: :author
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index 13e72e9c50..bb49fb300c 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class Club < ActiveRecord::Base
- has_one :membership
+ has_one :membership, touch: true
has_many :memberships, inverse_of: false
has_many :members, through: :memberships
has_one :sponsor
@@ -12,6 +12,8 @@ class Club < ActiveRecord::Base
scope :general, -> { left_joins(:category).where(categories: { name: "General" }).unscope(:limit) }
+ accepts_nested_attributes_for :membership
+
private
def private_method
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 838f515aad..a0f48d23f1 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -13,6 +13,8 @@ class Company < AbstractCompany
has_many :contracts
has_many :developers, through: :contracts
+ attribute :metadata, :json
+
scope :of_first_firm, lambda {
joins(account: :firm).
where("firms.id" => 1)
diff --git a/activerecord/test/models/contract.rb b/activerecord/test/models/contract.rb
index 3f663375c4..89719775c4 100644
--- a/activerecord/test/models/contract.rb
+++ b/activerecord/test/models/contract.rb
@@ -5,7 +5,9 @@ class Contract < ActiveRecord::Base
belongs_to :developer, primary_key: :id
belongs_to :firm, foreign_key: "company_id"
- before_save :hi
+ attribute :metadata, :json
+
+ before_save :hi, :update_metadata
after_save :bye
attr_accessor :hi_count, :bye_count
@@ -19,6 +21,10 @@ class Contract < ActiveRecord::Base
@bye_count ||= 0
@bye_count += 1
end
+
+ def update_metadata
+ self.metadata = { company_id: company_id, developer_id: developer_id }
+ end
end
class NewContract < Contract
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index ec48094207..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
@@ -207,6 +207,7 @@ end
class MultiplePoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = "developers"
+ default_scope { }
default_scope -> { where(name: "Jamis") }
default_scope -> { where(salary: 50000) }
end
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index fd5083e597..8733398697 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -98,3 +98,19 @@ class FamousPirate < ActiveRecord::Base
has_many :famous_ships
validates_presence_of :catchphrase, on: :conference
end
+
+class SpacePirate < ActiveRecord::Base
+ self.table_name = "pirates"
+
+ belongs_to :parrot
+ belongs_to :parrot_with_annotation, -> { annotate("that tells jokes") }, class_name: :Parrot, foreign_key: :parrot_id
+ has_and_belongs_to_many :parrots, foreign_key: :pirate_id
+ has_and_belongs_to_many :parrots_with_annotation, -> { annotate("that are very colorful") }, class_name: :Parrot, foreign_key: :pirate_id
+ has_one :ship, foreign_key: :pirate_id
+ has_one :ship_with_annotation, -> { annotate("that is a rocket") }, class_name: :Ship, foreign_key: :pirate_id
+ has_many :birds, foreign_key: :pirate_id
+ has_many :birds_with_annotation, -> { annotate("that are also parrots") }, class_name: :Bird, foreign_key: :pirate_id
+ has_many :treasures, as: :looter
+ has_many :treasure_estimates, through: :treasures, source: :price_estimates
+ has_many :treasure_estimates_with_annotation, -> { annotate("yarrr") }, through: :treasures, source: :price_estimates
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index e32cc59399..50c0dddcf2 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -11,6 +11,10 @@ class Post < ActiveRecord::Base
def author
"lifo"
end
+
+ def greeting
+ super + " :)"
+ end
end
module NamedExtension2
@@ -31,6 +35,7 @@ class Post < ActiveRecord::Base
belongs_to :author_with_posts, -> { includes(:posts) }, class_name: "Author", foreign_key: :author_id
belongs_to :author_with_address, -> { includes(:author_address) }, class_name: "Author", foreign_key: :author_id
+ belongs_to :author_with_select, -> { select(:id) }, class_name: "Author", foreign_key: :author_id
def first_comment
super.body
@@ -38,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 }) }
@@ -77,6 +83,7 @@ class Post < ActiveRecord::Base
has_many :comments_with_extend_2, extend: [NamedExtension, NamedExtension2], class_name: "Comment", foreign_key: "post_id"
has_many :author_favorites, through: :author
+ has_many :author_favorites_with_scope, through: :author, class_name: "AuthorFavoriteWithScope", source: "author_favorites"
has_many :author_categorizations, through: :author, source: :categorizations
has_many :author_addresses, through: :author
has_many :author_address_extra_with_address,
@@ -201,6 +208,10 @@ end
class SubAbstractStiPost < AbstractStiPost; end
+class NullPost < Post
+ default_scope { none }
+end
+
class FirstPost < ActiveRecord::Base
self.inheritance_column = :disabled
self.table_name = "posts"
@@ -210,6 +221,12 @@ class FirstPost < ActiveRecord::Base
has_one :comment, foreign_key: :post_id
end
+class PostWithDefaultSelect < ActiveRecord::Base
+ self.table_name = "posts"
+
+ default_scope { select(:author_id) }
+end
+
class TaggedPost < Post
has_many :taggings, -> { rewhere(taggable_type: "TaggedPost") }, as: :taggable
has_many :tags, through: :taggings
@@ -307,8 +324,8 @@ class FakeKlass
"posts"
end
- def attribute_alias?(name)
- false
+ def attribute_aliases
+ {}
end
def sanitize_sql(sql)
@@ -327,6 +344,10 @@ class FakeKlass
# noop
end
+ def columns_hash
+ { "name" => nil }
+ end
+
def arel_table
Post.arel_table
end
diff --git a/activerecord/test/models/rating.rb b/activerecord/test/models/rating.rb
index cf06bc6931..49aa38285f 100644
--- a/activerecord/test/models/rating.rb
+++ b/activerecord/test/models/rating.rb
@@ -3,4 +3,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"
end
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index 0807bcf875..f6ab9c8a8f 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -7,6 +7,8 @@ class Reply < Topic
belongs_to :topic_with_primary_key, class_name: "Topic", primary_key: "title", foreign_key: "parent_title", counter_cache: "replies_count", touch: true
has_many :replies, class_name: "SillyReply", dependent: :destroy, foreign_key: "parent_id"
has_many :silly_unique_replies, dependent: :destroy, foreign_key: "parent_id"
+
+ scope :ordered, -> { Reply.order(:id) }
end
class SillyReply < Topic
@@ -32,29 +34,29 @@ class WrongReply < Reply
validate :check_author_name_is_secret, on: :special_case
def check_empty_title
- errors[:title] << "Empty" unless attribute_present?("title")
+ errors.add(:title, "Empty") unless attribute_present?("title")
end
def errors_on_empty_content
- errors[:content] << "Empty" unless attribute_present?("content")
+ errors.add(:content, "Empty") unless attribute_present?("content")
end
def check_content_mismatch
if attribute_present?("title") && attribute_present?("content") && content == "Mismatch"
- errors[:title] << "is Content Mismatch"
+ errors.add(:title, "is Content Mismatch")
end
end
def title_is_wrong_create
- errors[:title] << "is Wrong Create" if attribute_present?("title") && title == "Wrong Create"
+ errors.add(:title, "is Wrong Create") if attribute_present?("title") && title == "Wrong Create"
end
def check_wrong_update
- errors[:title] << "is Wrong Update" if attribute_present?("title") && title == "Wrong Update"
+ errors.add(:title, "is Wrong Update") if attribute_present?("title") && title == "Wrong Update"
end
def check_author_name_is_secret
- errors[:author_name] << "Invalid" unless author_name == "secret"
+ errors.add(:author_name, "Invalid") unless author_name == "secret"
end
end
diff --git a/activerecord/test/models/section.rb b/activerecord/test/models/section.rb
new file mode 100644
index 0000000000..f8b4cc7936
--- /dev/null
+++ b/activerecord/test/models/section.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class Section < ActiveRecord::Base
+ belongs_to :session, inverse_of: :sections, autosave: true
+ belongs_to :seminar, inverse_of: :sections, autosave: true
+end
diff --git a/activerecord/test/models/seminar.rb b/activerecord/test/models/seminar.rb
new file mode 100644
index 0000000000..c18aa86433
--- /dev/null
+++ b/activerecord/test/models/seminar.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class Seminar < ActiveRecord::Base
+ has_many :sections, inverse_of: :seminar, autosave: true, dependent: :destroy
+ has_many :sessions, through: :sections
+end
diff --git a/activerecord/test/models/session.rb b/activerecord/test/models/session.rb
new file mode 100644
index 0000000000..db66b5297e
--- /dev/null
+++ b/activerecord/test/models/session.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class Session < ActiveRecord::Base
+ has_many :sections, inverse_of: :session, autosave: true, dependent: :destroy
+ has_many :seminars, through: :sections
+end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index fdb461ed7f..77101090f2 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -10,9 +10,6 @@ class Topic < ActiveRecord::Base
scope :approved, -> { where(approved: true) }
scope :rejected, -> { where(approved: false) }
- scope :children, -> { where.not(parent_id: nil) }
- scope :has_children, -> { where(id: Topic.children.select(:parent_id)) }
-
scope :scope_with_lambda, lambda { all }
scope :by_lifo, -> { where(author_name: "lifo") }
@@ -102,7 +99,7 @@ class Topic < ActiveRecord::Base
end
def destroy_children
- self.class.where("parent_id = #{id}").delete_all
+ self.class.delete_by(parent_id: id)
end
def set_email_address
@@ -122,10 +119,6 @@ class Topic < ActiveRecord::Base
end
end
-class ImportantTopic < Topic
- serialize :important, Hash
-end
-
class DefaultRejectedTopic < Topic
default_scope -> { where(approved: false) }
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 86d5a67a13..41920b3719 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -8,10 +8,18 @@ ActiveRecord::Schema.define do
# #
# ------------------------------------------------------------------- #
+ case_sensitive_options =
+ if current_adapter?(:Mysql2Adapter)
+ { collation: "utf8mb4_bin" }
+ else
+ {}
+ end
+
create_table :accounts, force: true do |t|
t.references :firm, index: false
t.string :firm_name
t.integer :credit_limit
+ t.integer "a" * max_identifier_length
end
create_table :admin_accounts, force: true do |t|
@@ -94,18 +102,23 @@ ActiveRecord::Schema.define do
end
create_table :books, id: :integer, force: true do |t|
+ default_zero = { default: 0 }
t.references :author
t.string :format
t.column :name, :string
- t.column :status, :integer, default: 0
- t.column :read_status, :integer, default: 0
+ t.column :status, :integer, **default_zero
+ t.column :read_status, :integer, **default_zero
t.column :nullable_status, :integer
- t.column :language, :integer, default: 0
- t.column :author_visibility, :integer, default: 0
- t.column :illustrator_visibility, :integer, default: 0
- t.column :font_size, :integer, default: 0
- t.column :difficulty, :integer, default: 0
+ t.column :language, :integer, **default_zero
+ t.column :author_visibility, :integer, **default_zero
+ t.column :illustrator_visibility, :integer, **default_zero
+ t.column :font_size, :integer, **default_zero
+ t.column :difficulty, :integer, **default_zero
t.column :cover, :string, default: "hard"
+ t.string :isbn
+ t.datetime :published_on
+ t.index [:author_id, :name], unique: true
+ t.index :isbn, where: "published_on IS NOT NULL", unique: true
end
create_table :booleans, force: true do |t|
@@ -247,6 +260,7 @@ ActiveRecord::Schema.define do
create_table :contracts, force: true do |t|
t.references :developer, index: false
t.references :company, index: false
+ t.string :metadata
end
create_table :customers, force: true do |t|
@@ -264,7 +278,7 @@ ActiveRecord::Schema.define do
end
create_table :dashboards, force: true, id: false do |t|
- t.string :dashboard_id
+ t.string :dashboard_id, **case_sensitive_options
t.string :name
end
@@ -328,7 +342,7 @@ ActiveRecord::Schema.define do
end
create_table :essays, force: true do |t|
- t.string :name
+ t.string :name, **case_sensitive_options
t.string :writer_id
t.string :writer_type
t.string :category_id
@@ -336,7 +350,7 @@ ActiveRecord::Schema.define do
end
create_table :events, force: true do |t|
- t.string :title, limit: 5
+ t.string :title, limit: 5, **case_sensitive_options
end
create_table :eyes, force: true do |t|
@@ -378,7 +392,7 @@ ActiveRecord::Schema.define do
end
create_table :guids, force: true do |t|
- t.column :key, :string
+ t.column :key, :string, **case_sensitive_options
end
create_table :guitars, force: true do |t|
@@ -386,8 +400,8 @@ ActiveRecord::Schema.define do
end
create_table :inept_wizards, force: true do |t|
- t.column :name, :string, null: false
- t.column :city, :string, null: false
+ t.column :name, :string, null: false, **case_sensitive_options
+ t.column :city, :string, null: false, **case_sensitive_options
t.column :type, :string
end
@@ -510,6 +524,8 @@ ActiveRecord::Schema.define do
t.integer :club_id, :member_id
t.boolean :favourite, default: false
t.integer :type
+ t.datetime :created_at
+ t.datetime :updated_at
end
create_table :member_types, force: true do |t|
@@ -778,6 +794,24 @@ ActiveRecord::Schema.define do
t.integer :lock_version, default: 0
end
+ disable_referential_integrity do
+ create_table :seminars, force: :cascade do |t|
+ t.string :name
+ end
+
+ create_table :sessions, force: :cascade do |t|
+ t.date :start_date
+ t.date :end_date
+ t.string :name
+ end
+
+ create_table :sections, force: :cascade do |t|
+ t.string :short_name
+ t.belongs_to :session, foreign_key: true
+ t.belongs_to :seminar, foreign_key: true
+ end
+ end
+
create_table :shape_expressions, force: true do |t|
t.string :paint_type
t.integer :paint_id
@@ -874,8 +908,8 @@ ActiveRecord::Schema.define do
end
create_table :topics, force: true do |t|
- t.string :title, limit: 250
- t.string :author_name
+ t.string :title, limit: 250, **case_sensitive_options
+ t.string :author_name, **case_sensitive_options
t.string :author_email_address
if subsecond_precision_supported?
t.datetime :written_on, precision: 6
@@ -887,10 +921,10 @@ ActiveRecord::Schema.define do
# use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in
# Oracle SELECT WHERE clause which causes many unit test failures
if current_adapter?(:OracleAdapter)
- t.string :content, limit: 4000
+ t.string :content, limit: 4000, **case_sensitive_options
t.string :important, limit: 4000
else
- t.text :content
+ t.text :content, **case_sensitive_options
t.text :important
end
t.boolean :approved, default: true
diff --git a/activerecord/test/support/stubs/strong_parameters.rb b/activerecord/test/support/stubs/strong_parameters.rb
index 84f93a28b9..da8f9892f9 100644
--- a/activerecord/test/support/stubs/strong_parameters.rb
+++ b/activerecord/test/support/stubs/strong_parameters.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
-class Parameters
+require "active_support/core_ext/hash/indifferent_access"
+
+class ProtectedParams
+ delegate :keys, :key?, :has_key?, :empty?, to: :@parameters
+
def initialize(parameters = {})
@parameters = parameters.with_indifferent_access
@permitted = false
@@ -15,7 +19,22 @@ class Parameters
self
end
+ def [](key)
+ @parameters[key]
+ end
+
def to_h
@parameters.to_h
end
+ alias to_unsafe_h to_h
+
+ def stringify_keys
+ dup
+ end
+
+ def dup
+ super.tap do |duplicate|
+ duplicate.instance_variable_set :@permitted, @permitted
+ end
+ end
end