aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/test/cases')
-rw-r--r--activerecord/test/cases/adapter_test.rb243
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb56
-rw-r--r--activerecord/test/cases/adapters/mysql2/annotate_test.rb37
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/charset_collation_test.rb10
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb17
-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.rb149
-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/active_schema_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/annotate_test.rb37
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb22
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb17
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb14
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb42
-rw-r--r--activerecord/test/cases/adapters/postgresql/create_unlogged_tables_test.rb74
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/extension_migration_test.rb13
-rw-r--r--activerecord/test/cases/adapters/postgresql/foreign_table_test.rb8
-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/infinity_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/optimizer_hints_test.rb52
-rw-r--r--activerecord/test/cases/adapters/postgresql/partitions_test.rb22
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb66
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb5
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb720
-rw-r--r--activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/rename_table_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb34
-rw-r--r--activerecord/test/cases/adapters/postgresql/transaction_test.rb1
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb28
-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/quoting_test.rb40
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb126
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb18
-rw-r--r--activerecord/test/cases/aggregations_test.rb2
-rw-r--r--activerecord/test/cases/ar_schema_test.rb87
-rw-r--r--activerecord/test/cases/arel/attributes/attribute_test.rb123
-rw-r--r--activerecord/test/cases/arel/attributes/math_test.rb20
-rw-r--r--activerecord/test/cases/arel/collectors/sql_string_test.rb16
-rw-r--r--activerecord/test/cases/arel/delete_manager_test.rb1
-rw-r--r--activerecord/test/cases/arel/insert_manager_test.rb19
-rw-r--r--activerecord/test/cases/arel/nodes/and_test.rb9
-rw-r--r--activerecord/test/cases/arel/nodes/case_test.rb10
-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.rb29
-rw-r--r--activerecord/test/cases/arel/support/fake_record.rb4
-rw-r--r--activerecord/test/cases/arel/visitors/depth_first_test.rb9
-rw-r--r--activerecord/test/cases/arel/visitors/dot_test.rb3
-rw-r--r--activerecord/test/cases/arel/visitors/ibm_db_test.rb39
-rw-r--r--activerecord/test/cases/arel/visitors/informix_test.rb39
-rw-r--r--activerecord/test/cases/arel/visitors/mssql_test.rb39
-rw-r--r--activerecord/test/cases/arel/visitors/mysql_test.rb49
-rw-r--r--activerecord/test/cases/arel/visitors/oracle12_test.rb39
-rw-r--r--activerecord/test/cases/arel/visitors/oracle_test.rb39
-rw-r--r--activerecord/test/cases/arel/visitors/postgres_test.rb45
-rw-r--r--activerecord/test/cases/arel/visitors/sqlite_test.rb45
-rw-r--r--activerecord/test/cases/arel/visitors/to_sql_test.rb67
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb126
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb23
-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.rb2
-rw-r--r--activerecord/test/cases/associations/eager_test.rb64
-rw-r--r--activerecord/test/cases/associations/extension_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb52
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb338
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb117
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb72
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb7
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb5
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb10
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb19
-rw-r--r--activerecord/test/cases/associations/required_test.rb50
-rw-r--r--activerecord/test/cases/associations_test.rb101
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb94
-rw-r--r--activerecord/test/cases/attributes_test.rb25
-rw-r--r--activerecord/test/cases/autosave_association_test.rb101
-rw-r--r--activerecord/test/cases/base_test.rb121
-rw-r--r--activerecord/test/cases/batches_test.rb34
-rw-r--r--activerecord/test/cases/binary_test.rb2
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb117
-rw-r--r--activerecord/test/cases/boolean_test.rb9
-rw-r--r--activerecord/test/cases/cache_key_test.rb82
-rw-r--r--activerecord/test/cases/calculations_test.rb106
-rw-r--r--activerecord/test/cases/callbacks_test.rb8
-rw-r--r--activerecord/test/cases/collection_cache_key_test.rb18
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb39
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb391
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb25
-rw-r--r--activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/schema_cache_test.rb81
-rw-r--r--activerecord/test/cases/connection_management_test.rb2
-rw-r--r--activerecord/test/cases/connection_pool_test.rb121
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb8
-rw-r--r--activerecord/test/cases/core_test.rb20
-rw-r--r--activerecord/test/cases/counter_cache_test.rb2
-rw-r--r--activerecord/test/cases/database_configurations_test.rb117
-rw-r--r--activerecord/test/cases/database_selector_test.rb166
-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.rb9
-rw-r--r--activerecord/test/cases/dirty_test.rb28
-rw-r--r--activerecord/test/cases/enum_test.rb57
-rw-r--r--activerecord/test/cases/errors_test.rb8
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb2
-rw-r--r--activerecord/test/cases/filter_attributes_test.rb132
-rw-r--r--activerecord/test/cases/finder_respond_to_test.rb6
-rw-r--r--activerecord/test/cases/finder_test.rb134
-rw-r--r--activerecord/test/cases/fixtures_test.rb112
-rw-r--r--activerecord/test/cases/forbidden_attributes_protection_test.rb38
-rw-r--r--activerecord/test/cases/habtm_destroy_order_test.rb28
-rw-r--r--activerecord/test/cases/helper.rb25
-rw-r--r--activerecord/test/cases/hot_compatibility_test.rb2
-rw-r--r--activerecord/test/cases/inheritance_test.rb7
-rw-r--r--activerecord/test/cases/insert_all_test.rb276
-rw-r--r--activerecord/test/cases/instrumentation_test.rb12
-rw-r--r--activerecord/test/cases/integration_test.rb89
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb18
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb15
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb6
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb8
-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.rb29
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb114
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb195
-rw-r--r--activerecord/test/cases/migration/index_test.rb13
-rw-r--r--activerecord/test/cases/migration/pending_migrations_test.rb4
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb61
-rw-r--r--activerecord/test/cases/migration_test.rb165
-rw-r--r--activerecord/test/cases/migrator_test.rb1
-rw-r--r--activerecord/test/cases/multiple_db_test.rb12
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb24
-rw-r--r--activerecord/test/cases/null_relation_test.rb15
-rw-r--r--activerecord/test/cases/numeric_data_test.rb34
-rw-r--r--activerecord/test/cases/persistence_test.rb194
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb28
-rw-r--r--activerecord/test/cases/primary_keys_test.rb2
-rw-r--r--activerecord/test/cases/query_cache_test.rb191
-rw-r--r--activerecord/test/cases/quoting_test.rb24
-rw-r--r--activerecord/test/cases/reaper_test.rb6
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb52
-rw-r--r--activerecord/test/cases/relation/delete_all_test.rb102
-rw-r--r--activerecord/test/cases/relation/merging_test.rb26
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb4
-rw-r--r--activerecord/test/cases/relation/or_test.rb5
-rw-r--r--activerecord/test/cases/relation/select_test.rb14
-rw-r--r--activerecord/test/cases/relation/update_all_test.rb324
-rw-r--r--activerecord/test/cases/relation/where_clause_test.rb4
-rw-r--r--activerecord/test/cases/relation/where_test.rb41
-rw-r--r--activerecord/test/cases/relation_test.rb58
-rw-r--r--activerecord/test/cases/relations_test.rb432
-rw-r--r--activerecord/test/cases/result_test.rb14
-rw-r--r--activerecord/test/cases/sanitize_test.rb19
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb46
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb26
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb32
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb77
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb21
-rw-r--r--activerecord/test/cases/statement_cache_test.rb6
-rw-r--r--activerecord/test/cases/statement_invalid_test.rb42
-rw-r--r--activerecord/test/cases/store_test.rb68
-rw-r--r--activerecord/test/cases/suppressor_test.rb2
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb429
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb26
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb25
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb8
-rw-r--r--activerecord/test/cases/test_case.rb6
-rw-r--r--activerecord/test/cases/time_precision_test.rb22
-rw-r--r--activerecord/test/cases/touch_later_test.rb4
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb23
-rw-r--r--activerecord/test/cases/transaction_isolation_test.rb6
-rw-r--r--activerecord/test/cases/transactions_test.rb172
-rw-r--r--activerecord/test/cases/type/time_test.rb22
-rw-r--r--activerecord/test/cases/type/type_map_test.rb6
-rw-r--r--activerecord/test/cases/unconnected_test.rb14
-rw-r--r--activerecord/test/cases/validations/absence_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations/i18n_generate_message_validation_test.rb20
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations/presence_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb47
-rw-r--r--activerecord/test/cases/validations_test.rb25
-rw-r--r--activerecord/test/cases/view_test.rb12
192 files changed, 7835 insertions, 2168 deletions
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 79642f5871..ba04859bf0 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "cases/helper"
+require "support/connection_helper"
require "models/book"
require "models/post"
require "models/author"
@@ -10,6 +11,7 @@ module ActiveRecord
class AdapterTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
+ @connection.materialize_transactions
end
##
@@ -107,6 +109,11 @@ module ActiveRecord
end
end
+ def test_exec_query_returns_an_empty_result
+ result = @connection.exec_query "INSERT INTO subscribers(nick) VALUES('me')"
+ assert_instance_of(ActiveRecord::Result, result)
+ end
+
if current_adapter?(:Mysql2Adapter)
def test_charset
assert_not_nil @connection.charset
@@ -125,19 +132,17 @@ module ActiveRecord
end
def test_not_specifying_database_name_for_cross_database_selects
- begin
- assert_nothing_raised do
- ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations["arunit"].except(:database))
-
- config = ARTest.connection_config
- ActiveRecord::Base.connection.execute(
- "SELECT #{config['arunit']['database']}.pirates.*, #{config['arunit2']['database']}.courses.* " \
- "FROM #{config['arunit']['database']}.pirates, #{config['arunit2']['database']}.courses"
- )
- end
- ensure
- ActiveRecord::Base.establish_connection :arunit
+ assert_nothing_raised do
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations["arunit"].except(:database))
+
+ config = ARTest.connection_config
+ ActiveRecord::Base.connection.execute(
+ "SELECT #{config['arunit']['database']}.pirates.*, #{config['arunit2']['database']}.courses.* " \
+ "FROM #{config['arunit']['database']}.pirates, #{config['arunit2']['database']}.courses"
+ )
end
+ ensure
+ ActiveRecord::Base.establish_connection :arunit
end
end
@@ -158,6 +163,65 @@ module ActiveRecord
end
end
+ def test_preventing_writes_predicate
+ assert_not_predicate @connection, :preventing_writes?
+
+ @connection.while_preventing_writes do
+ assert_predicate @connection, :preventing_writes?
+ end
+
+ assert_not_predicate @connection, :preventing_writes?
+ end
+
+ def test_errors_when_an_insert_query_is_called_while_preventing_writes
+ assert_no_queries do
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @connection.while_preventing_writes do
+ @connection.transaction do
+ @connection.insert("INSERT INTO subscribers(nick) VALUES ('138853948594')", nil, false)
+ end
+ end
+ end
+ end
+ end
+
+ def test_errors_when_an_update_query_is_called_while_preventing_writes
+ @connection.insert("INSERT INTO subscribers(nick) VALUES ('138853948594')")
+
+ assert_no_queries do
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @connection.while_preventing_writes do
+ @connection.transaction do
+ @connection.update("UPDATE subscribers SET nick = '9989' WHERE nick = '138853948594'")
+ end
+ end
+ end
+ end
+ end
+
+ def test_errors_when_a_delete_query_is_called_while_preventing_writes
+ @connection.insert("INSERT INTO subscribers(nick) VALUES ('138853948594')")
+
+ assert_no_queries do
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @connection.while_preventing_writes do
+ @connection.transaction do
+ @connection.delete("DELETE FROM subscribers WHERE nick = '138853948594'")
+ end
+ end
+ end
+ end
+ end
+
+ def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes
+ @connection.insert("INSERT INTO subscribers(nick) VALUES ('138853948594')")
+
+ @connection.while_preventing_writes do
+ result = @connection.select_all("SELECT subscribers.* FROM subscribers WHERE nick = '138853948594'")
+ assert_equal 1, result.length
+ end
+ end
+
def test_uniqueness_violations_are_translated_to_specific_exception
@connection.execute "INSERT INTO subscribers(nick) VALUES('me')"
error = assert_raises(ActiveRecord::RecordNotUnique) do
@@ -225,7 +289,7 @@ module ActiveRecord
post = Post.create!(title: "foo", body: "bar")
expected = @connection.select_all("SELECT * FROM posts WHERE id = #{post.id}")
result = @connection.select_all("SELECT * FROM posts WHERE id = #{Arel::Nodes::BindParam.new(nil).to_sql}", nil, [[nil, post.id]])
- assert_equal expected.to_hash, result.to_hash
+ assert_equal expected.to_a, result.to_a
end
def test_insert_update_delete_with_legacy_binds
@@ -284,26 +348,48 @@ module ActiveRecord
assert_equal "special_db_type", @connection.type_to_sql(:special_db_type)
end
- unless current_adapter?(:PostgreSQLAdapter)
- def test_log_invalid_encoding
- error = assert_raises RuntimeError do
- @connection.send :log, "SELECT 'ы' FROM DUAL" do
- raise "ы".dup.force_encoding(Encoding::ASCII_8BIT)
- end
- end
-
- assert_equal "ы", error.message
- 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
+
+ def test_column_name_length_is_deprecated
+ assert_deprecated { @connection.column_name_length }
+ end
+
+ def test_table_name_length_is_deprecated
+ assert_deprecated { @connection.table_name_length }
+ end
+
+ def test_columns_per_table_is_deprecated
+ assert_deprecated { @connection.columns_per_table }
+ end
+
+ def test_indexes_per_table_is_deprecated
+ assert_deprecated { @connection.indexes_per_table }
+ end
+
+ def test_columns_per_multicolumn_index_is_deprecated
+ assert_deprecated { @connection.columns_per_multicolumn_index }
+ end
+
+ def test_sql_query_length_is_deprecated
+ assert_deprecated { @connection.sql_query_length }
+ end
+
+ def test_joins_per_query_is_deprecated
+ assert_deprecated { @connection.joins_per_query }
+ end
end
class AdapterForeignKeyTest < ActiveRecord::TestCase
self.use_transactional_tests = false
+ fixtures :fk_test_has_pk
+
def setup
@connection = ActiveRecord::Base.connection
end
@@ -322,7 +408,7 @@ module ActiveRecord
assert_not_nil error.cause
end
- def test_foreign_key_violations_are_translated_to_specific_exception
+ def test_foreign_key_violations_on_insert_are_translated_to_specific_exception
error = assert_raises(ActiveRecord::InvalidForeignKey) do
insert_into_fk_test_has_fk
end
@@ -330,6 +416,16 @@ module ActiveRecord
assert_not_nil error.cause
end
+ def test_foreign_key_violations_on_delete_are_translated_to_specific_exception
+ insert_into_fk_test_has_fk fk_id: 1
+
+ error = assert_raises(ActiveRecord::InvalidForeignKey) do
+ @connection.execute "DELETE FROM fk_test_has_pk WHERE pk_id = 1"
+ end
+
+ assert_not_nil error.cause
+ end
+
def test_disable_referential_integrity
assert_nothing_raised do
@connection.disable_referential_integrity do
@@ -342,14 +438,13 @@ module ActiveRecord
end
private
-
- def insert_into_fk_test_has_fk
+ def insert_into_fk_test_has_fk(fk_id: 0)
# Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
if @connection.prefetch_primary_key?
id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
- @connection.execute "INSERT INTO fk_test_has_fk (id,fk_id) VALUES (#{id_value},0)"
+ @connection.execute "INSERT INTO fk_test_has_fk (id,fk_id) VALUES (#{id_value},#{fk_id})"
else
- @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
+ @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (#{fk_id})"
end
end
end
@@ -357,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?
@@ -382,9 +479,59 @@ 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
+ 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
+ @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
+ 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
+ @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"
@@ -406,3 +553,27 @@ module ActiveRecord
end
end
end
+
+if ActiveRecord::Base.connection.supports_advisory_locks?
+ class AdvisoryLocksEnabledTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
+ def test_advisory_locks_enabled?
+ assert ActiveRecord::Base.connection.advisory_locks_enabled?
+
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(
+ orig_connection.merge(advisory_locks: false)
+ )
+
+ assert_not ActiveRecord::Base.connection.advisory_locks_enabled?
+
+ ActiveRecord::Base.establish_connection(
+ orig_connection.merge(advisory_locks: true)
+ )
+
+ assert ActiveRecord::Base.connection.advisory_locks_enabled?
+ 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 6fc9df5083..88c2ac5d0a 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -7,6 +7,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
include ConnectionHelper
def setup
+ 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
@@ -68,18 +69,18 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def (ActiveRecord::Base.connection).data_source_exists?(*); false; end
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
- expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`))"
+ expected = /\ACREATE TABLE `people` \(#{type} INDEX `index_people_on_last_name` \(`last_name`\)\)/
actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, type: type
end
- assert_equal expected, actual
+ assert_match expected, actual
end
- expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)))"
+ expected = /\ACREATE TABLE `people` \( INDEX `index_people_on_last_name` USING btree \(`last_name`\(10\)\)\)/
actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, length: 10, using: :btree
end
- assert_equal expected, actual
+ assert_match expected, actual
end
def test_index_in_bulk_change
@@ -106,7 +107,13 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end
def test_create_mysql_database_with_encoding
- assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
+ if row_format_dynamic_by_default?
+ assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8mb4`", create_database(:matt)
+ else
+ error = assert_raises(RuntimeError) { create_database(:matt) }
+ expected = "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
+ assert_equal expected, error.message
+ end
assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, charset: "latin1")
assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT COLLATE `utf8mb4_bin`", create_database(:matt_aimonetti, collation: "utf8mb4_bin")
end
@@ -130,29 +137,25 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def test_add_timestamps
with_real_execute do
- begin
- ActiveRecord::Base.connection.create_table :delete_me
- ActiveRecord::Base.connection.add_timestamps :delete_me, null: true
- assert column_present?("delete_me", "updated_at", "datetime")
- assert column_present?("delete_me", "created_at", "datetime")
- ensure
- ActiveRecord::Base.connection.drop_table :delete_me rescue nil
- end
+ ActiveRecord::Base.connection.create_table :delete_me
+ ActiveRecord::Base.connection.add_timestamps :delete_me, null: true
+ assert column_exists?("delete_me", "updated_at", "datetime")
+ assert column_exists?("delete_me", "created_at", "datetime")
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
end
end
def test_remove_timestamps
with_real_execute do
- begin
- ActiveRecord::Base.connection.create_table :delete_me do |t|
- t.timestamps null: true
- end
- ActiveRecord::Base.connection.remove_timestamps :delete_me, null: true
- assert_not column_present?("delete_me", "updated_at", "datetime")
- assert_not column_present?("delete_me", "created_at", "datetime")
- ensure
- ActiveRecord::Base.connection.drop_table :delete_me rescue nil
+ ActiveRecord::Base.connection.create_table :delete_me do |t|
+ t.timestamps null: true
end
+ ActiveRecord::Base.connection.remove_timestamps :delete_me, null: true
+ assert_not column_exists?("delete_me", "updated_at", "datetime")
+ assert_not column_exists?("delete_me", "created_at", "datetime")
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
end
end
@@ -163,12 +166,12 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
[:temp],
returns: false
) do
- expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) AS SELECT id, name, zip FROM a_really_complicated_query"
+ expected = /\ACREATE TEMPORARY TABLE `temp` \( INDEX `index_temp_on_zip` \(`zip`\)\)(?: ROW_FORMAT=DYNAMIC)? AS SELECT id, name, zip FROM a_really_complicated_query/
actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
t.index :zip
end
- assert_equal expected, actual
+ assert_match expected, actual
end
end
@@ -191,9 +194,4 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def method_missing(method_symbol, *arguments)
ActiveRecord::Base.connection.send(method_symbol, *arguments)
end
-
- def column_present?(table_name, column_name, type)
- results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'")
- results.first && results.first["Type"] == type
- end
end
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/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
index aa870349be..c32475c683 100644
--- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -9,8 +9,8 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
repair_validations(CollationTest)
def test_columns_include_collation_different_from_table
- assert_equal "utf8_bin", CollationTest.columns_hash["string_cs_column"].collation
- assert_equal "utf8_general_ci", CollationTest.columns_hash["string_ci_column"].collation
+ assert_equal "utf8mb4_bin", CollationTest.columns_hash["string_cs_column"].collation
+ assert_equal "utf8mb4_general_ci", CollationTest.columns_hash["string_ci_column"].collation
end
def test_case_sensitive
diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
index d0c57de65d..0bdbefdfb9 100644
--- a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
@@ -32,20 +32,20 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase
end
test "add column with charset and collation" do
- @connection.add_column :charset_collations, :title, :string, charset: "utf8", collation: "utf8_bin"
+ @connection.add_column :charset_collations, :title, :string, charset: "utf8mb4", collation: "utf8mb4_bin"
column = @connection.columns(:charset_collations).find { |c| c.name == "title" }
assert_equal :string, column.type
- assert_equal "utf8_bin", column.collation
+ assert_equal "utf8mb4_bin", column.collation
end
test "change column with charset and collation" do
- @connection.add_column :charset_collations, :description, :string, charset: "utf8", collation: "utf8_unicode_ci"
- @connection.change_column :charset_collations, :description, :text, charset: "utf8", collation: "utf8_general_ci"
+ @connection.add_column :charset_collations, :description, :string, charset: "utf8mb4", collation: "utf8mb4_unicode_ci"
+ @connection.change_column :charset_collations, :description, :text, charset: "utf8mb4", collation: "utf8mb4_general_ci"
column = @connection.columns(:charset_collations).find { |c| c.name == "description" }
assert_equal :text, column.type
- assert_equal "utf8_general_ci", column.collation
+ assert_equal "utf8mb4_general_ci", column.collation
end
test "schema dump includes collation" do
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 726f58d58e..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")
@@ -104,8 +93,8 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_mysql_connection_collation_is_configured
- assert_equal "utf8_unicode_ci", @connection.show_variable("collation_connection")
- assert_equal "utf8_general_ci", ARUnit2Model.connection.show_variable("collation_connection")
+ assert_equal "utf8mb4_unicode_ci", @connection.show_variable("collation_connection")
+ assert_equal "utf8mb4_general_ci", ARUnit2Model.connection.show_variable("collation_connection")
end
def test_mysql_default_in_strict_mode
@@ -170,6 +159,8 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_logs_name_show_variable
+ ActiveRecord::Base.connection.materialize_transactions
+ @subscriber.logged.clear
@connection.show_variable "foo"
assert_equal "SCHEMA", @subscriber.logged[0][1]
end
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 0719baaa23..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,152 @@ 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
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @conn.while_preventing_writes do
+ @conn.insert("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
+ end
+ end
+ end
+
+ def test_errors_when_an_update_query_is_called_while_preventing_writes
+ @conn.insert("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
+
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @conn.while_preventing_writes do
+ @conn.update("UPDATE `engines` SET `engines`.`car_id` = '9989' WHERE `engines`.`car_id` = '138853948594'")
+ end
+ end
+ end
+
+ def test_errors_when_a_delete_query_is_called_while_preventing_writes
+ @conn.execute("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
+
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @conn.while_preventing_writes do
+ @conn.execute("DELETE FROM `engines` where `engines`.`car_id` = '138853948594'")
+ end
+ end
+ end
+
+ def test_errors_when_a_replace_query_is_called_while_preventing_writes
+ @conn.execute("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
+
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @conn.while_preventing_writes do
+ @conn.execute("REPLACE INTO `engines` SET `engines`.`car_id` = '249823948'")
+ end
+ end
+ end
+
+ def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes
+ @conn.execute("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
+
+ @conn.while_preventing_writes do
+ assert_equal 1, @conn.execute("SELECT `engines`.* FROM `engines` WHERE `engines`.`car_id` = '138853948594'").entries.count
+ end
+ end
+
+ def test_doesnt_error_when_a_show_query_is_called_while_preventing_writes
+ @conn.while_preventing_writes do
+ assert_equal 2, @conn.execute("SHOW FULL FIELDS FROM `engines`").entries.count
+ end
+ end
+
+ def test_doesnt_error_when_a_set_query_is_called_while_preventing_writes
+ @conn.while_preventing_writes do
+ 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
private
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/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 308ad1d854..62efaf3bfe 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -4,6 +4,8 @@ require "cases/helper"
class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
def setup
+ ActiveRecord::Base.connection.materialize_transactions
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
def execute(sql, name = nil) sql end
end
@@ -27,7 +29,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
def test_add_index
# add_index calls index_name_exists? which can't work since execute is stubbed
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) { |*| false }
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.define_method(:index_name_exists?) { |*| false }
expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
assert_equal expected, add_index(:people, :last_name, unique: true, where: "state = 'active'")
@@ -72,12 +74,12 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
add_index(:people, :last_name, algorithm: :copy)
end
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_exists?
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.remove_method :index_name_exists?
end
def test_remove_index
# remove_index calls index_name_for_remove which can't work since execute is stubbed
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_for_remove) do |*|
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.define_method(:index_name_for_remove) do |*|
"index_people_on_last_name"
end
@@ -88,7 +90,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
add_index(:people, :last_name, algorithm: :copy)
end
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_for_remove
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.remove_method :index_name_for_remove
end
def test_remove_index_when_name_is_specified
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/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 42618c2ec3..2e7a4b498f 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -17,7 +17,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
enable_extension!("hstore", @connection)
@connection.transaction do
- @connection.create_table("pg_arrays") do |t|
+ @connection.create_table "pg_arrays", force: true do |t|
t.string "tags", array: true, limit: 255
t.integer "ratings", array: true
t.datetime :datetimes, array: true
@@ -112,6 +112,18 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
assert_predicate column, :array?
end
+ def test_change_column_from_non_array_to_array
+ @connection.add_column :pg_arrays, :snippets, :string
+ @connection.change_column :pg_arrays, :snippets, :text, array: true, default: [], using: "string_to_array(\"snippets\", ',')"
+
+ PgArray.reset_column_information
+ column = PgArray.columns_hash["snippets"]
+
+ assert_equal :text, column.type
+ assert_equal [], PgArray.column_defaults["snippets"]
+ assert_predicate column, :array?
+ end
+
def test_change_column_cant_make_non_array_column_to_array
@connection.add_column :pg_arrays, :a_string, :string
assert_raises ActiveRecord::StatementInvalid do
@@ -226,14 +238,6 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
assert_equal(PgArray.last.tags, tag_values)
end
- def test_insert_fixtures
- tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"]
- assert_deprecated do
- @connection.insert_fixtures([{ "tags" => tag_values }], "pg_arrays")
- end
- assert_equal(PgArray.last.tags, tag_values)
- end
-
def test_attribute_for_inspect_for_array_field
record = PgArray.new { |a| a.ratings = (1..10).to_a }
assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]", record.attribute_for_inspect(:ratings))
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 64bb6906cd..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
@@ -49,7 +49,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
end
def test_type_cast_binary_value
- data = "\u001F\x8B".dup.force_encoding("BINARY")
+ data = (+"\u001F\x8B").force_encoding("BINARY")
assert_equal(data, @type.deserialize(data))
end
diff --git a/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb b/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb
index 305e033642..79e9efcf06 100644
--- a/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb
@@ -7,22 +7,21 @@ class PostgresqlCaseInsensitiveTest < ActiveRecord::PostgreSQLTestCase
def test_case_insensitiveness
connection = ActiveRecord::Base.connection
- table = Default.arel_table
- column = Default.columns_hash["char1"]
- comparison = connection.case_insensitive_comparison table, :char1, column, nil
+ attr = Default.arel_attribute(:char1)
+ comparison = connection.case_insensitive_comparison(attr, nil)
assert_match(/lower/i, comparison.to_sql)
- column = Default.columns_hash["char2"]
- comparison = connection.case_insensitive_comparison table, :char2, column, nil
+ attr = Default.arel_attribute(:char2)
+ comparison = connection.case_insensitive_comparison(attr, nil)
assert_match(/lower/i, comparison.to_sql)
- column = Default.columns_hash["char3"]
- comparison = connection.case_insensitive_comparison table, :char3, column, nil
+ attr = Default.arel_attribute(:char3)
+ comparison = connection.case_insensitive_comparison(attr, nil)
assert_match(/lower/i, comparison.to_sql)
- column = Default.columns_hash["multiline_default"]
- comparison = connection.case_insensitive_comparison table, :multiline_default, column, nil
+ attr = Default.arel_attribute(:multiline_default)
+ comparison = connection.case_insensitive_comparison(attr, nil)
assert_match(/lower/i, comparison.to_sql)
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
index b0ce2694a3..683066cdb3 100644
--- a/activerecord/test/cases/adapters/postgresql/composite_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -15,13 +15,13 @@ module PostgresqlCompositeBehavior
@connection = ActiveRecord::Base.connection
@connection.transaction do
- @connection.execute <<-SQL
- CREATE TYPE full_address AS
- (
- city VARCHAR(90),
- street VARCHAR(90)
- );
- SQL
+ @connection.execute <<~SQL
+ CREATE TYPE full_address AS
+ (
+ city VARCHAR(90),
+ street VARCHAR(90)
+ );
+ SQL
@connection.create_table("postgresql_composites") do |t|
t.column :address, :full_address
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 54b0dde7dc..210758f462 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -15,8 +15,9 @@ module ActiveRecord
def setup
super
@subscriber = SQLSubscriber.new
- @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber)
@connection = ActiveRecord::Base.connection
+ @connection.materialize_transactions
+ @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber)
end
def teardown
@@ -24,14 +25,6 @@ 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_not_nil @connection.encoding
@@ -145,34 +138,15 @@ module ActiveRecord
end
end
- # Must have PostgreSQL >= 9.2, or with_manual_interventions set to
- # true for this test to run.
- #
- # When prompted, restart the PostgreSQL server with the
- # "-m fast" option or kill the individual connection assuming
- # you know the incantation to do that.
- # To restart PostgreSQL 9.1 on macOS, installed via MacPorts, ...
- # sudo su postgres -c "pg_ctl restart -D /opt/local/var/db/postgresql91/defaultdb/ -m fast"
def test_reconnection_after_actual_disconnection_with_verify
original_connection_pid = @connection.query("select pg_backend_pid()")
# Sanity check.
assert_predicate @connection, :active?
- if @connection.send(:postgresql_version) >= 90200
- secondary_connection = ActiveRecord::Base.connection_pool.checkout
- secondary_connection.query("select pg_terminate_backend(#{original_connection_pid.first.first})")
- ActiveRecord::Base.connection_pool.checkin(secondary_connection)
- elsif ARTest.config["with_manual_interventions"]
- puts "Kill the connection now (e.g. by restarting the PostgreSQL " \
- 'server with the "-m fast" option) and then press enter.'
- $stdin.gets
- else
- # We're not capable of terminating the backend ourselves, and
- # we're not allowed to seek assistance; bail out without
- # actually testing anything.
- return
- end
+ secondary_connection = ActiveRecord::Base.connection_pool.checkout
+ secondary_connection.query("select pg_terminate_backend(#{original_connection_pid.first.first})")
+ ActiveRecord::Base.connection_pool.checkin(secondary_connection)
@connection.verify!
@@ -229,7 +203,7 @@ module ActiveRecord
def test_get_and_release_advisory_lock
lock_id = 5295901941911233559
- list_advisory_locks = <<-SQL
+ list_advisory_locks = <<~SQL
SELECT locktype,
(classid::bigint << 32) | objid::bigint AS lock_id
FROM pg_locks
@@ -260,6 +234,10 @@ module ActiveRecord
end
end
+ def test_supports_ranges_is_deprecated
+ assert_deprecated { @connection.supports_ranges? }
+ end
+
private
def with_warning_suppression
diff --git a/activerecord/test/cases/adapters/postgresql/create_unlogged_tables_test.rb b/activerecord/test/cases/adapters/postgresql/create_unlogged_tables_test.rb
new file mode 100644
index 0000000000..a02bae1453
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/create_unlogged_tables_test.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "support/schema_dumping_helper"
+
+class UnloggedTablesTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+
+ TABLE_NAME = "things"
+ LOGGED_FIELD = "relpersistence"
+ LOGGED_QUERY = "SELECT #{LOGGED_FIELD} FROM pg_class WHERE relname = '#{TABLE_NAME}'"
+ LOGGED = "p"
+ UNLOGGED = "u"
+ TEMPORARY = "t"
+
+ class Thing < ActiveRecord::Base
+ self.table_name = TABLE_NAME
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = false
+ end
+
+ teardown do
+ @connection.drop_table TABLE_NAME, if_exists: true
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = false
+ end
+
+ def test_logged_by_default
+ @connection.create_table(TABLE_NAME) do |t|
+ end
+ assert_equal @connection.execute(LOGGED_QUERY).first[LOGGED_FIELD], LOGGED
+ end
+
+ def test_unlogged_in_test_environment_when_unlogged_setting_enabled
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
+
+ @connection.create_table(TABLE_NAME) do |t|
+ end
+ assert_equal @connection.execute(LOGGED_QUERY).first[LOGGED_FIELD], UNLOGGED
+ end
+
+ def test_not_included_in_schema_dump
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
+
+ @connection.create_table(TABLE_NAME) do |t|
+ end
+ assert_no_match(/unlogged/i, dump_table_schema(TABLE_NAME))
+ end
+
+ def test_not_changed_in_change_table
+ @connection.create_table(TABLE_NAME) do |t|
+ end
+
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
+
+ @connection.change_table(TABLE_NAME) do |t|
+ t.column :name, :string
+ end
+ assert_equal @connection.execute(LOGGED_QUERY).first[LOGGED_FIELD], LOGGED
+ end
+
+ def test_gracefully_handles_temporary_tables
+ @connection.create_table(TABLE_NAME, temporary: true) do |t|
+ end
+
+ # Temporary tables are already unlogged, though this query results in a
+ # different result ("t" vs. "u"). This test is really just checking that we
+ # didn't try to run `CREATE TEMPORARY UNLOGGED TABLE`, which would result in
+ # a PostgreSQL error.
+ assert_equal @connection.execute(LOGGED_QUERY).first[LOGGED_FIELD], TEMPORARY
+ 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/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb
index 6789ff63e7..416a2b141b 100644
--- a/activerecord/test/cases/adapters/postgresql/enum_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -13,7 +13,7 @@ class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase
def setup
@connection = ActiveRecord::Base.connection
@connection.transaction do
- @connection.execute <<-SQL
+ @connection.execute <<~SQL
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
SQL
@connection.create_table("postgresql_enums") do |t|
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/foreign_table_test.rb b/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb
index 4fa315ad23..69339c8a31 100644
--- a/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb
@@ -22,18 +22,18 @@ if ActiveRecord::Base.connection.supports_foreign_tables?
enable_extension!("postgres_fdw", @connection)
foreign_db_config = ARTest.connection_config["arunit2"]
- @connection.execute <<-SQL
+ @connection.execute <<~SQL
CREATE SERVER foreign_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (dbname '#{foreign_db_config["database"]}')
SQL
- @connection.execute <<-SQL
+ @connection.execute <<~SQL
CREATE USER MAPPING FOR CURRENT_USER
SERVER foreign_server
SQL
- @connection.execute <<-SQL
+ @connection.execute <<~SQL
CREATE FOREIGN TABLE foreign_professors (
id int,
name character varying NOT NULL
@@ -45,7 +45,7 @@ if ActiveRecord::Base.connection.supports_foreign_tables?
def teardown
disable_extension!("postgres_fdw", @connection)
- @connection.execute <<-SQL
+ @connection.execute <<~SQL
DROP SERVER IF EXISTS foreign_server CASCADE
SQL
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/infinity_test.rb b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
index 5e56ce8427..b1bf06d9e9 100644
--- a/activerecord/test/cases/adapters/postgresql/infinity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
@@ -71,17 +71,15 @@ class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase
end
test "assigning 'infinity' on a datetime column with TZ aware attributes" do
- begin
- in_time_zone "Pacific Time (US & Canada)" do
- record = PostgresqlInfinity.create!(datetime: "infinity")
- assert_equal Float::INFINITY, record.datetime
- assert_equal record.datetime, record.reload.datetime
- end
- ensure
- # setting time_zone_aware_attributes causes the types to change.
- # There is no way to do this automatically since it can be set on a superclass
- PostgresqlInfinity.reset_column_information
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = PostgresqlInfinity.create!(datetime: "infinity")
+ assert_equal Float::INFINITY, record.datetime
+ assert_equal record.datetime, record.reload.datetime
end
+ ensure
+ # setting time_zone_aware_attributes causes the types to change.
+ # There is no way to do this automatically since it can be set on a superclass
+ PostgresqlInfinity.reset_column_information
end
test "where clause with infinite range on a datetime column" do
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index 61e75e772d..1aa0348879 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -37,7 +37,7 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
def test_default
assert_equal BigDecimal("150.55"), PostgresqlMoney.column_defaults["depth"]
assert_equal BigDecimal("150.55"), PostgresqlMoney.new.depth
- assert_equal "$150.55", PostgresqlMoney.new.depth_before_type_cast
+ assert_equal "150.55", PostgresqlMoney.new.depth_before_type_cast
end
def test_money_values
@@ -52,10 +52,10 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
def test_money_type_cast
type = PostgresqlMoney.type_for_attribute("wealth")
- assert_equal(12345678.12, type.cast("$12,345,678.12".dup))
- assert_equal(12345678.12, type.cast("$12.345.678,12".dup))
- assert_equal(-1.15, type.cast("-$1.15".dup))
- assert_equal(-2.25, type.cast("($2.25)".dup))
+ assert_equal(12345678.12, type.cast(+"$12,345,678.12"))
+ assert_equal(12345678.12, type.cast(+"$12.345.678,12"))
+ assert_equal(-1.15, type.cast(+"-$1.15"))
+ assert_equal(-2.25, type.cast(+"($2.25)"))
end
def test_schema_dumping
@@ -65,7 +65,7 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
end
def test_create_and_update_money
- money = PostgresqlMoney.create(wealth: "987.65".dup)
+ money = PostgresqlMoney.create(wealth: +"987.65")
assert_equal 987.65, money.wealth
new_value = BigDecimal("123.45")
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
new file mode 100644
index 0000000000..4015bc94f9
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/partitions_test.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+
+class PostgreSQLPartitionsTest < ActiveRecord::PostgreSQLTestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table "partitioned_events", if_exists: true
+ end
+
+ def test_partitions_table_exists
+ 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
+ end
+ assert @connection.table_exists?("partitioned_events")
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index cbb6cd42b5..fbd3cbf90f 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -376,6 +376,72 @@ module ActiveRecord
end
end
+ def test_errors_when_an_insert_query_is_called_while_preventing_writes
+ with_example_table do
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @connection.while_preventing_writes do
+ @connection.execute("INSERT INTO ex (data) VALUES ('138853948594')")
+ end
+ end
+ end
+ end
+
+ def test_errors_when_an_update_query_is_called_while_preventing_writes
+ with_example_table do
+ @connection.execute("INSERT INTO ex (data) VALUES ('138853948594')")
+
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @connection.while_preventing_writes do
+ @connection.execute("UPDATE ex SET data = '9989' WHERE data = '138853948594'")
+ end
+ end
+ end
+ end
+
+ def test_errors_when_a_delete_query_is_called_while_preventing_writes
+ with_example_table do
+ @connection.execute("INSERT INTO ex (data) VALUES ('138853948594')")
+
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @connection.while_preventing_writes do
+ @connection.execute("DELETE FROM ex where data = '138853948594'")
+ end
+ end
+ end
+ end
+
+ def test_doesnt_error_when_a_select_query_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("SELECT * FROM ex WHERE data = '138853948594'").entries.count
+ end
+ end
+ end
+
+ def test_doesnt_error_when_a_show_query_is_called_while_preventing_writes
+ @connection.while_preventing_writes do
+ assert_equal 1, @connection.execute("SHOW TIME ZONE").entries.count
+ end
+ end
+
+ def test_doesnt_error_when_a_set_query_is_called_while_preventing_writes
+ @connection.while_preventing_writes do
+ assert_equal [], @connection.execute("SET standard_conforming_strings = on").entries
+ 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/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index d50dc49276..d571355a9c 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -39,6 +39,11 @@ module ActiveRecord
type = OID::Bit.new
assert_nil @conn.quote(type.serialize(value))
end
+
+ def test_quote_table_name_with_spaces
+ value = "user posts"
+ assert_equal "\"user posts\"", @conn.quote_table_name(value)
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index 433598500d..068f1e8bea 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -3,418 +3,432 @@
require "cases/helper"
require "support/connection_helper"
-if ActiveRecord::Base.connection.respond_to?(:supports_ranges?) && ActiveRecord::Base.connection.supports_ranges?
- class PostgresqlRange < ActiveRecord::Base
- self.table_name = "postgresql_ranges"
- self.time_zone_aware_types += [:tsrange, :tstzrange]
- end
-
- class PostgresqlRangeTest < ActiveRecord::PostgreSQLTestCase
- self.use_transactional_tests = false
- include ConnectionHelper
- include InTimeZone
-
- def setup
- @connection = PostgresqlRange.connection
- begin
- @connection.transaction do
- @connection.execute <<_SQL
- CREATE TYPE floatrange AS RANGE (
- subtype = float8,
- subtype_diff = float8mi
- );
-_SQL
-
- @connection.create_table("postgresql_ranges") do |t|
- t.daterange :date_range
- t.numrange :num_range
- t.tsrange :ts_range
- t.tstzrange :tstz_range
- t.int4range :int4_range
- t.int8range :int8_range
- end
-
- @connection.add_column "postgresql_ranges", "float_range", "floatrange"
+class PostgresqlRange < ActiveRecord::Base
+ self.table_name = "postgresql_ranges"
+ self.time_zone_aware_types += [:tsrange, :tstzrange]
+end
+
+class PostgresqlRangeTest < ActiveRecord::PostgreSQLTestCase
+ self.use_transactional_tests = false
+ include ConnectionHelper
+ include InTimeZone
+
+ def setup
+ @connection = PostgresqlRange.connection
+ begin
+ @connection.transaction do
+ @connection.execute <<~SQL
+ CREATE TYPE floatrange AS RANGE (
+ subtype = float8,
+ subtype_diff = float8mi
+ );
+ SQL
+
+ @connection.create_table("postgresql_ranges") do |t|
+ t.daterange :date_range
+ t.numrange :num_range
+ t.tsrange :ts_range
+ t.tstzrange :tstz_range
+ t.int4range :int4_range
+ t.int8range :int8_range
end
- PostgresqlRange.reset_column_information
- rescue ActiveRecord::StatementInvalid
- skip "do not test on PG without range"
- end
- insert_range(id: 101,
- date_range: "[''2012-01-02'', ''2012-01-04'']",
- num_range: "[0.1, 0.2]",
- ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'']",
- tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']",
- int4_range: "[1, 10]",
- int8_range: "[10, 100]",
- float_range: "[0.5, 0.7]")
-
- insert_range(id: 102,
- date_range: "[''2012-01-02'', ''2012-01-04'')",
- num_range: "[0.1, 0.2)",
- ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'')",
- tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'')",
- int4_range: "[1, 10)",
- int8_range: "[10, 100)",
- float_range: "[0.5, 0.7)")
-
- insert_range(id: 103,
- date_range: "[''2012-01-02'',]",
- num_range: "[0.1,]",
- ts_range: "[''2010-01-01 14:30'',]",
- tstz_range: "[''2010-01-01 14:30:00+05'',]",
- int4_range: "[1,]",
- int8_range: "[10,]",
- float_range: "[0.5,]")
-
- insert_range(id: 104,
- date_range: "[,]",
- num_range: "[,]",
- ts_range: "[,]",
- tstz_range: "[,]",
- int4_range: "[,]",
- int8_range: "[,]",
- float_range: "[,]")
-
- insert_range(id: 105,
- date_range: "[''2012-01-02'', ''2012-01-02'')",
- num_range: "[0.1, 0.1)",
- ts_range: "[''2010-01-01 14:30'', ''2010-01-01 14:30'')",
- tstz_range: "[''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')",
- int4_range: "[1, 1)",
- int8_range: "[10, 10)",
- float_range: "[0.5, 0.5)")
-
- @new_range = PostgresqlRange.new
- @first_range = PostgresqlRange.find(101)
- @second_range = PostgresqlRange.find(102)
- @third_range = PostgresqlRange.find(103)
- @fourth_range = PostgresqlRange.find(104)
- @empty_range = PostgresqlRange.find(105)
- end
+ @connection.add_column "postgresql_ranges", "float_range", "floatrange"
+ end
+ PostgresqlRange.reset_column_information
+ rescue ActiveRecord::StatementInvalid
+ skip "do not test on PG without range"
+ end
+
+ insert_range(id: 101,
+ date_range: "[''2012-01-02'', ''2012-01-04'']",
+ num_range: "[0.1, 0.2]",
+ ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'']",
+ tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']",
+ int4_range: "[1, 10]",
+ int8_range: "[10, 100]",
+ float_range: "[0.5, 0.7]")
+
+ insert_range(id: 102,
+ date_range: "[''2012-01-02'', ''2012-01-04'')",
+ num_range: "[0.1, 0.2)",
+ ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'')",
+ tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'')",
+ int4_range: "[1, 10)",
+ int8_range: "[10, 100)",
+ float_range: "[0.5, 0.7)")
+
+ insert_range(id: 103,
+ date_range: "[''2012-01-02'',]",
+ num_range: "[0.1,]",
+ ts_range: "[''2010-01-01 14:30'',]",
+ tstz_range: "[''2010-01-01 14:30:00+05'',]",
+ int4_range: "[1,]",
+ int8_range: "[10,]",
+ float_range: "[0.5,]")
+
+ insert_range(id: 104,
+ date_range: "[,]",
+ num_range: "[,]",
+ ts_range: "[,]",
+ tstz_range: "[,]",
+ int4_range: "[,]",
+ int8_range: "[,]",
+ float_range: "[,]")
+
+ insert_range(id: 105,
+ date_range: "[''2012-01-02'', ''2012-01-02'')",
+ num_range: "[0.1, 0.1)",
+ ts_range: "[''2010-01-01 14:30'', ''2010-01-01 14:30'')",
+ tstz_range: "[''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')",
+ int4_range: "[1, 1)",
+ int8_range: "[10, 10)",
+ float_range: "[0.5, 0.5)")
+
+ @new_range = PostgresqlRange.new
+ @first_range = PostgresqlRange.find(101)
+ @second_range = PostgresqlRange.find(102)
+ @third_range = PostgresqlRange.find(103)
+ @fourth_range = PostgresqlRange.find(104)
+ @empty_range = PostgresqlRange.find(105)
+ end
- teardown do
- @connection.drop_table "postgresql_ranges", if_exists: true
- @connection.execute "DROP TYPE IF EXISTS floatrange"
- reset_connection
- end
+ teardown do
+ @connection.drop_table "postgresql_ranges", if_exists: true
+ @connection.execute "DROP TYPE IF EXISTS floatrange"
+ reset_connection
+ end
- def test_data_type_of_range_types
- assert_equal :daterange, @first_range.column_for_attribute(:date_range).type
- assert_equal :numrange, @first_range.column_for_attribute(:num_range).type
- assert_equal :tsrange, @first_range.column_for_attribute(:ts_range).type
- assert_equal :tstzrange, @first_range.column_for_attribute(:tstz_range).type
- assert_equal :int4range, @first_range.column_for_attribute(:int4_range).type
- assert_equal :int8range, @first_range.column_for_attribute(:int8_range).type
- end
+ def test_data_type_of_range_types
+ assert_equal :daterange, @first_range.column_for_attribute(:date_range).type
+ assert_equal :numrange, @first_range.column_for_attribute(:num_range).type
+ assert_equal :tsrange, @first_range.column_for_attribute(:ts_range).type
+ assert_equal :tstzrange, @first_range.column_for_attribute(:tstz_range).type
+ assert_equal :int4range, @first_range.column_for_attribute(:int4_range).type
+ assert_equal :int8range, @first_range.column_for_attribute(:int8_range).type
+ end
- def test_int4range_values
- assert_equal 1...11, @first_range.int4_range
- assert_equal 1...10, @second_range.int4_range
- assert_equal 1...Float::INFINITY, @third_range.int4_range
- assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range)
- assert_nil @empty_range.int4_range
- end
+ def test_int4range_values
+ assert_equal 1...11, @first_range.int4_range
+ assert_equal 1...10, @second_range.int4_range
+ assert_equal 1...Float::INFINITY, @third_range.int4_range
+ assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range)
+ assert_nil @empty_range.int4_range
+ end
- def test_int8range_values
- assert_equal 10...101, @first_range.int8_range
- assert_equal 10...100, @second_range.int8_range
- assert_equal 10...Float::INFINITY, @third_range.int8_range
- assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range)
- assert_nil @empty_range.int8_range
- end
+ def test_int8range_values
+ assert_equal 10...101, @first_range.int8_range
+ assert_equal 10...100, @second_range.int8_range
+ assert_equal 10...Float::INFINITY, @third_range.int8_range
+ assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range)
+ assert_nil @empty_range.int8_range
+ end
- def test_daterange_values
- assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 5), @first_range.date_range
- assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 4), @second_range.date_range
- assert_equal Date.new(2012, 1, 2)...Float::INFINITY, @third_range.date_range
- assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range)
- assert_nil @empty_range.date_range
- end
+ def test_daterange_values
+ assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 5), @first_range.date_range
+ assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 4), @second_range.date_range
+ assert_equal Date.new(2012, 1, 2)...Float::INFINITY, @third_range.date_range
+ assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range)
+ assert_nil @empty_range.date_range
+ end
- def test_numrange_values
- assert_equal BigDecimal("0.1")..BigDecimal("0.2"), @first_range.num_range
- assert_equal BigDecimal("0.1")...BigDecimal("0.2"), @second_range.num_range
- assert_equal BigDecimal("0.1")...BigDecimal("Infinity"), @third_range.num_range
- assert_equal BigDecimal("-Infinity")...BigDecimal("Infinity"), @fourth_range.num_range
- assert_nil @empty_range.num_range
- end
+ def test_numrange_values
+ assert_equal BigDecimal("0.1")..BigDecimal("0.2"), @first_range.num_range
+ assert_equal BigDecimal("0.1")...BigDecimal("0.2"), @second_range.num_range
+ assert_equal BigDecimal("0.1")...BigDecimal("Infinity"), @third_range.num_range
+ assert_equal BigDecimal("-Infinity")...BigDecimal("Infinity"), @fourth_range.num_range
+ assert_nil @empty_range.num_range
+ end
- def test_tsrange_values
- tz = ::ActiveRecord::Base.default_timezone
- assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range
- assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range
- assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range)
- assert_nil @empty_range.ts_range
- end
+ def test_tsrange_values
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range
+ assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range
+ assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range)
+ assert_nil @empty_range.ts_range
+ end
- def test_tstzrange_values
- assert_equal Time.parse("2010-01-01 09:30:00 UTC")..Time.parse("2011-01-01 17:30:00 UTC"), @first_range.tstz_range
- assert_equal Time.parse("2010-01-01 09:30:00 UTC")...Time.parse("2011-01-01 17:30:00 UTC"), @second_range.tstz_range
- assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range)
- assert_nil @empty_range.tstz_range
- end
+ def test_tstzrange_values
+ assert_equal Time.parse("2010-01-01 09:30:00 UTC")..Time.parse("2011-01-01 17:30:00 UTC"), @first_range.tstz_range
+ assert_equal Time.parse("2010-01-01 09:30:00 UTC")...Time.parse("2011-01-01 17:30:00 UTC"), @second_range.tstz_range
+ assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range)
+ assert_nil @empty_range.tstz_range
+ end
- def test_custom_range_values
- assert_equal 0.5..0.7, @first_range.float_range
- assert_equal 0.5...0.7, @second_range.float_range
- assert_equal 0.5...Float::INFINITY, @third_range.float_range
- assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.float_range)
- assert_nil @empty_range.float_range
- end
+ def test_custom_range_values
+ assert_equal 0.5..0.7, @first_range.float_range
+ assert_equal 0.5...0.7, @second_range.float_range
+ assert_equal 0.5...Float::INFINITY, @third_range.float_range
+ assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.float_range)
+ assert_nil @empty_range.float_range
+ end
- def test_timezone_awareness_tzrange
- tz = "Pacific Time (US & Canada)"
+ def test_timezone_awareness_tzrange
+ tz = "Pacific Time (US & Canada)"
- in_time_zone tz do
- PostgresqlRange.reset_column_information
- time_string = Time.current.to_s
- time = Time.zone.parse(time_string)
+ in_time_zone tz do
+ PostgresqlRange.reset_column_information
+ time_string = Time.current.to_s
+ time = Time.zone.parse(time_string)
- record = PostgresqlRange.new(tstz_range: time_string..time_string)
- assert_equal time..time, record.tstz_range
- assert_equal ActiveSupport::TimeZone[tz], record.tstz_range.begin.time_zone
+ record = PostgresqlRange.new(tstz_range: time_string..time_string)
+ assert_equal time..time, record.tstz_range
+ assert_equal ActiveSupport::TimeZone[tz], record.tstz_range.begin.time_zone
- record.save!
- record.reload
+ record.save!
+ record.reload
- assert_equal time..time, record.tstz_range
- assert_equal ActiveSupport::TimeZone[tz], record.tstz_range.begin.time_zone
- end
+ assert_equal time..time, record.tstz_range
+ assert_equal ActiveSupport::TimeZone[tz], record.tstz_range.begin.time_zone
end
+ end
- def test_create_tstzrange
- tstzrange = Time.parse("2010-01-01 14:30:00 +0100")...Time.parse("2011-02-02 14:30:00 CDT")
- round_trip(@new_range, :tstz_range, tstzrange)
- assert_equal @new_range.tstz_range, tstzrange
- assert_equal @new_range.tstz_range, Time.parse("2010-01-01 13:30:00 UTC")...Time.parse("2011-02-02 19:30:00 UTC")
- end
+ def test_create_tstzrange
+ tstzrange = Time.parse("2010-01-01 14:30:00 +0100")...Time.parse("2011-02-02 14:30:00 CDT")
+ round_trip(@new_range, :tstz_range, tstzrange)
+ assert_equal @new_range.tstz_range, tstzrange
+ assert_equal @new_range.tstz_range, Time.parse("2010-01-01 13:30:00 UTC")...Time.parse("2011-02-02 19:30:00 UTC")
+ end
- def test_update_tstzrange
- assert_equal_round_trip(@first_range, :tstz_range,
- Time.parse("2010-01-01 14:30:00 CDT")...Time.parse("2011-02-02 14:30:00 CET"))
- assert_nil_round_trip(@first_range, :tstz_range,
- Time.parse("2010-01-01 14:30:00 +0100")...Time.parse("2010-01-01 13:30:00 +0000"))
- end
+ def test_update_tstzrange
+ assert_equal_round_trip(@first_range, :tstz_range,
+ Time.parse("2010-01-01 14:30:00 CDT")...Time.parse("2011-02-02 14:30:00 CET"))
+ assert_nil_round_trip(@first_range, :tstz_range,
+ Time.parse("2010-01-01 14:30:00 +0100")...Time.parse("2010-01-01 13:30:00 +0000"))
+ end
- def test_create_tsrange
- tz = ::ActiveRecord::Base.default_timezone
- assert_equal_round_trip(@new_range, :ts_range,
- Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0))
- end
+ def test_create_tsrange
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal_round_trip(@new_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0))
+ end
- def test_update_tsrange
- tz = ::ActiveRecord::Base.default_timezone
- assert_equal_round_trip(@first_range, :ts_range,
- Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0))
- assert_nil_round_trip(@first_range, :ts_range,
- Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0))
- end
+ def test_update_tsrange
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal_round_trip(@first_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0))
+ assert_nil_round_trip(@first_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0))
+ end
- def test_timezone_awareness_tsrange
- tz = "Pacific Time (US & Canada)"
+ def test_timezone_awareness_tsrange
+ tz = "Pacific Time (US & Canada)"
- in_time_zone tz do
- PostgresqlRange.reset_column_information
- time_string = Time.current.to_s
- time = Time.zone.parse(time_string)
+ in_time_zone tz do
+ PostgresqlRange.reset_column_information
+ time_string = Time.current.to_s
+ time = Time.zone.parse(time_string)
- record = PostgresqlRange.new(ts_range: time_string..time_string)
- assert_equal time..time, record.ts_range
- assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+ record = PostgresqlRange.new(ts_range: time_string..time_string)
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
- record.save!
- record.reload
+ record.save!
+ record.reload
- assert_equal time..time, record.ts_range
- assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
- end
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
end
+ end
- def test_create_tstzrange_preserve_usec
- tstzrange = Time.parse("2010-01-01 14:30:00.670277 +0100")...Time.parse("2011-02-02 14:30:00.745125 CDT")
- round_trip(@new_range, :tstz_range, tstzrange)
- assert_equal @new_range.tstz_range, tstzrange
- assert_equal @new_range.tstz_range, Time.parse("2010-01-01 13:30:00.670277 UTC")...Time.parse("2011-02-02 19:30:00.745125 UTC")
- end
+ def test_create_tstzrange_preserve_usec
+ tstzrange = Time.parse("2010-01-01 14:30:00.670277 +0100")...Time.parse("2011-02-02 14:30:00.745125 CDT")
+ round_trip(@new_range, :tstz_range, tstzrange)
+ assert_equal @new_range.tstz_range, tstzrange
+ assert_equal @new_range.tstz_range, Time.parse("2010-01-01 13:30:00.670277 UTC")...Time.parse("2011-02-02 19:30:00.745125 UTC")
+ end
- def test_update_tstzrange_preserve_usec
- assert_equal_round_trip(@first_range, :tstz_range,
- Time.parse("2010-01-01 14:30:00.245124 CDT")...Time.parse("2011-02-02 14:30:00.451274 CET"))
- assert_nil_round_trip(@first_range, :tstz_range,
- Time.parse("2010-01-01 14:30:00.245124 +0100")...Time.parse("2010-01-01 13:30:00.245124 +0000"))
- end
+ def test_update_tstzrange_preserve_usec
+ assert_equal_round_trip(@first_range, :tstz_range,
+ Time.parse("2010-01-01 14:30:00.245124 CDT")...Time.parse("2011-02-02 14:30:00.451274 CET"))
+ assert_nil_round_trip(@first_range, :tstz_range,
+ Time.parse("2010-01-01 14:30:00.245124 +0100")...Time.parse("2010-01-01 13:30:00.245124 +0000"))
+ end
- def test_create_tsrange_preseve_usec
- tz = ::ActiveRecord::Base.default_timezone
- assert_equal_round_trip(@new_range, :ts_range,
- Time.send(tz, 2010, 1, 1, 14, 30, 0, 125435)...Time.send(tz, 2011, 2, 2, 14, 30, 0, 225435))
- end
+ def test_create_tsrange_preseve_usec
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal_round_trip(@new_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0, 125435)...Time.send(tz, 2011, 2, 2, 14, 30, 0, 225435))
+ end
- def test_update_tsrange_preserve_usec
- tz = ::ActiveRecord::Base.default_timezone
- assert_equal_round_trip(@first_range, :ts_range,
- Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432)...Time.send(tz, 2011, 2, 2, 14, 30, 0, 224242))
- assert_nil_round_trip(@first_range, :ts_range,
- Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432)...Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432))
- end
+ def test_update_tsrange_preserve_usec
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal_round_trip(@first_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432)...Time.send(tz, 2011, 2, 2, 14, 30, 0, 224242))
+ assert_nil_round_trip(@first_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432)...Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432))
+ end
- def test_timezone_awareness_tsrange_preserve_usec
- tz = "Pacific Time (US & Canada)"
+ def test_timezone_awareness_tsrange_preserve_usec
+ tz = "Pacific Time (US & Canada)"
- in_time_zone tz do
- PostgresqlRange.reset_column_information
- time_string = "2017-09-26 07:30:59.132451 -0700"
- time = Time.zone.parse(time_string)
- assert time.usec > 0
+ in_time_zone tz do
+ PostgresqlRange.reset_column_information
+ time_string = "2017-09-26 07:30:59.132451 -0700"
+ time = Time.zone.parse(time_string)
+ assert time.usec > 0
- record = PostgresqlRange.new(ts_range: time_string..time_string)
- assert_equal time..time, record.ts_range
- assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
- assert_equal time.usec, record.ts_range.begin.usec
+ record = PostgresqlRange.new(ts_range: time_string..time_string)
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+ assert_equal time.usec, record.ts_range.begin.usec
- record.save!
- record.reload
+ record.save!
+ record.reload
- assert_equal time..time, record.ts_range
- assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
- assert_equal time.usec, record.ts_range.begin.usec
- end
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+ assert_equal time.usec, record.ts_range.begin.usec
end
+ end
- def test_create_numrange
- assert_equal_round_trip(@new_range, :num_range,
- BigDecimal("0.5")...BigDecimal("1"))
- end
+ def test_create_numrange
+ assert_equal_round_trip(@new_range, :num_range,
+ BigDecimal("0.5")...BigDecimal("1"))
+ end
- def test_update_numrange
- assert_equal_round_trip(@first_range, :num_range,
- BigDecimal("0.5")...BigDecimal("1"))
- assert_nil_round_trip(@first_range, :num_range,
- BigDecimal("0.5")...BigDecimal("0.5"))
- end
+ def test_update_numrange
+ assert_equal_round_trip(@first_range, :num_range,
+ BigDecimal("0.5")...BigDecimal("1"))
+ assert_nil_round_trip(@first_range, :num_range,
+ BigDecimal("0.5")...BigDecimal("0.5"))
+ end
- def test_create_daterange
- assert_equal_round_trip(@new_range, :date_range,
- Range.new(Date.new(2012, 1, 1), Date.new(2013, 1, 1), true))
- end
+ def test_create_daterange
+ assert_equal_round_trip(@new_range, :date_range,
+ Range.new(Date.new(2012, 1, 1), Date.new(2013, 1, 1), true))
+ end
- def test_update_daterange
- assert_equal_round_trip(@first_range, :date_range,
- Date.new(2012, 2, 3)...Date.new(2012, 2, 10))
- assert_nil_round_trip(@first_range, :date_range,
- Date.new(2012, 2, 3)...Date.new(2012, 2, 3))
- end
+ def test_update_daterange
+ assert_equal_round_trip(@first_range, :date_range,
+ Date.new(2012, 2, 3)...Date.new(2012, 2, 10))
+ assert_nil_round_trip(@first_range, :date_range,
+ Date.new(2012, 2, 3)...Date.new(2012, 2, 3))
+ end
- def test_create_int4range
- assert_equal_round_trip(@new_range, :int4_range, Range.new(3, 50, true))
- end
+ def test_create_int4range
+ assert_equal_round_trip(@new_range, :int4_range, Range.new(3, 50, true))
+ end
- def test_update_int4range
- assert_equal_round_trip(@first_range, :int4_range, 6...10)
- assert_nil_round_trip(@first_range, :int4_range, 3...3)
- end
+ def test_update_int4range
+ assert_equal_round_trip(@first_range, :int4_range, 6...10)
+ assert_nil_round_trip(@first_range, :int4_range, 3...3)
+ end
- def test_create_int8range
- assert_equal_round_trip(@new_range, :int8_range, Range.new(30, 50, true))
- end
+ def test_create_int8range
+ assert_equal_round_trip(@new_range, :int8_range, Range.new(30, 50, true))
+ end
- def test_update_int8range
- assert_equal_round_trip(@first_range, :int8_range, 60000...10000000)
- assert_nil_round_trip(@first_range, :int8_range, 39999...39999)
- end
+ def test_update_int8range
+ assert_equal_round_trip(@first_range, :int8_range, 60000...10000000)
+ assert_nil_round_trip(@first_range, :int8_range, 39999...39999)
+ end
- def test_exclude_beginning_for_subtypes_without_succ_method_is_not_supported
- assert_raises(ArgumentError) { PostgresqlRange.create!(num_range: "(0.1, 0.2]") }
- assert_raises(ArgumentError) { PostgresqlRange.create!(float_range: "(0.5, 0.7]") }
- assert_raises(ArgumentError) { PostgresqlRange.create!(int4_range: "(1, 10]") }
- assert_raises(ArgumentError) { PostgresqlRange.create!(int8_range: "(10, 100]") }
- assert_raises(ArgumentError) { PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']") }
- assert_raises(ArgumentError) { PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']") }
- assert_raises(ArgumentError) { PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']") }
- end
+ def test_exclude_beginning_for_subtypes_without_succ_method_is_not_supported
+ assert_raises(ArgumentError) { PostgresqlRange.create!(num_range: "(0.1, 0.2]") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(float_range: "(0.5, 0.7]") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(int4_range: "(1, 10]") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(int8_range: "(10, 100]") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']") }
+ end
- def test_where_by_attribute_with_range
- range = 1..100
- record = PostgresqlRange.create!(int4_range: range)
- assert_equal record, PostgresqlRange.where(int4_range: range).take
- end
+ def test_where_by_attribute_with_range
+ range = 1..100
+ record = PostgresqlRange.create!(int4_range: range)
+ assert_equal record, PostgresqlRange.where(int4_range: range).take
+ end
- def test_where_by_attribute_with_range_in_array
- range = 1..100
- record = PostgresqlRange.create!(int4_range: range)
- assert_equal record, PostgresqlRange.where(int4_range: [range]).take
- end
+ def test_where_by_attribute_with_range_in_array
+ range = 1..100
+ record = PostgresqlRange.create!(int4_range: range)
+ assert_equal record, PostgresqlRange.where(int4_range: [range]).take
+ end
- def test_update_all_with_ranges
- PostgresqlRange.create!
+ def test_update_all_with_ranges
+ PostgresqlRange.create!
- PostgresqlRange.update_all(int8_range: 1..100)
+ PostgresqlRange.update_all(int8_range: 1..100)
- assert_equal 1...101, PostgresqlRange.first.int8_range
- end
+ assert_equal 1...101, PostgresqlRange.first.int8_range
+ end
- def test_ranges_correctly_escape_input
- range = "-1,2]'; DROP TABLE postgresql_ranges; --".."a"
- PostgresqlRange.update_all(int8_range: range)
+ def test_ranges_correctly_escape_input
+ range = "-1,2]'; DROP TABLE postgresql_ranges; --".."a"
+ PostgresqlRange.update_all(int8_range: range)
- assert_nothing_raised do
- PostgresqlRange.first
- end
+ assert_nothing_raised do
+ PostgresqlRange.first
end
+ end
- def test_infinity_values
- PostgresqlRange.create!(int4_range: 1..Float::INFINITY,
- int8_range: -Float::INFINITY..0,
- float_range: -Float::INFINITY..Float::INFINITY)
+ def test_infinity_values
+ PostgresqlRange.create!(int4_range: 1..Float::INFINITY,
+ int8_range: -Float::INFINITY..0,
+ float_range: -Float::INFINITY..Float::INFINITY)
- record = PostgresqlRange.first
+ record = PostgresqlRange.first
- assert_equal(1...Float::INFINITY, record.int4_range)
- assert_equal(-Float::INFINITY...1, record.int8_range)
- assert_equal(-Float::INFINITY...Float::INFINITY, record.float_range)
- end
+ assert_equal(1...Float::INFINITY, record.int4_range)
+ assert_equal(-Float::INFINITY...1, record.int8_range)
+ assert_equal(-Float::INFINITY...Float::INFINITY, record.float_range)
+ end
- private
- def assert_equal_round_trip(range, attribute, value)
- round_trip(range, attribute, value)
- assert_equal value, range.public_send(attribute)
- 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..")
+ )
- def assert_nil_round_trip(range, attribute, value)
- round_trip(range, attribute, value)
- assert_nil range.public_send(attribute)
- end
-
- def round_trip(range, attribute, value)
- range.public_send "#{attribute}=", value
- assert range.save
- assert range.reload
- end
+ record = PostgresqlRange.find(record.id)
- def insert_range(values)
- @connection.execute <<-SQL
- INSERT INTO postgresql_ranges (
- id,
- date_range,
- num_range,
- ts_range,
- tstz_range,
- int4_range,
- int8_range,
- float_range
- ) VALUES (
- #{values[:id]},
- '#{values[:date_range]}',
- '#{values[:num_range]}',
- '#{values[:ts_range]}',
- '#{values[:tstz_range]}',
- '#{values[:int4_range]}',
- '#{values[:int8_range]}',
- '#{values[:float_range]}'
- )
- SQL
- end
+ 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)
+ assert_equal value, range.public_send(attribute)
+ end
+
+ def assert_nil_round_trip(range, attribute, value)
+ round_trip(range, attribute, value)
+ assert_nil range.public_send(attribute)
+ end
+
+ def round_trip(range, attribute, value)
+ range.public_send "#{attribute}=", value
+ assert range.save
+ assert range.reload
+ end
+
+ def insert_range(values)
+ @connection.execute <<~SQL
+ INSERT INTO postgresql_ranges (
+ id,
+ date_range,
+ num_range,
+ ts_range,
+ tstz_range,
+ int4_range,
+ int8_range,
+ float_range
+ ) VALUES (
+ #{values[:id]},
+ '#{values[:date_range]}',
+ '#{values[:num_range]}',
+ '#{values[:ts_range]}',
+ '#{values[:tstz_range]}',
+ '#{values[:int4_range]}',
+ '#{values[:int8_range]}',
+ '#{values[:float_range]}'
+ )
+ SQL
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
index 0bcc214c24..ba477c63f4 100644
--- a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
@@ -101,7 +101,7 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase
@connection.extend ProgrammerMistake
assert_raises ArgumentError do
- @connection.disable_referential_integrity {}
+ @connection.disable_referential_integrity { }
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
index 100d247113..7eccaf4aa2 100644
--- a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
@@ -27,7 +27,7 @@ class PostgresqlRenameTableTest < ActiveRecord::PostgreSQLTestCase
private
def num_indices_named(name)
- @connection.execute(<<-SQL).values.length
+ @connection.execute(<<~SQL).values.length
SELECT 1 FROM "pg_index"
JOIN "pg_class" ON "pg_index"."indexrelid" = "pg_class"."oid"
WHERE "pg_class"."relname" = '#{name}'
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index a36d066c80..336cec30ca 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -108,23 +108,19 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
end
def test_create_schema
- begin
- @connection.create_schema "test_schema3"
- assert @connection.schema_names.include? "test_schema3"
- ensure
- @connection.drop_schema "test_schema3"
- end
+ @connection.create_schema "test_schema3"
+ assert @connection.schema_names.include? "test_schema3"
+ ensure
+ @connection.drop_schema "test_schema3"
end
def test_raise_create_schema_with_existing_schema
- begin
+ @connection.create_schema "test_schema3"
+ assert_raises(ActiveRecord::StatementInvalid) do
@connection.create_schema "test_schema3"
- assert_raises(ActiveRecord::StatementInvalid) do
- @connection.create_schema "test_schema3"
- end
- ensure
- @connection.drop_schema "test_schema3"
end
+ ensure
+ @connection.drop_schema "test_schema3"
end
def test_drop_schema
@@ -146,7 +142,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
def test_habtm_table_name_with_schema
ActiveRecord::Base.connection.drop_schema "music", if_exists: true
ActiveRecord::Base.connection.create_schema "music"
- ActiveRecord::Base.connection.execute <<-SQL
+ ActiveRecord::Base.connection.execute <<~SQL
CREATE TABLE music.albums (id serial primary key);
CREATE TABLE music.songs (id serial primary key);
CREATE TABLE music.albums_songs (album_id integer, song_id integer);
@@ -507,6 +503,7 @@ class SchemaIndexOpclassTest < ActiveRecord::PostgreSQLTestCase
@connection = ActiveRecord::Base.connection
@connection.create_table "trains" do |t|
t.string :name
+ t.string :position
t.text :description
end
end
@@ -530,6 +527,17 @@ class SchemaIndexOpclassTest < ActiveRecord::PostgreSQLTestCase
assert_match(/opclass: \{ description: :text_pattern_ops \}/, output)
end
+
+ def test_opclass_class_parsing_on_non_reserved_and_cannot_be_function_or_type_keyword
+ @connection.enable_extension("pg_trgm")
+ @connection.execute "CREATE INDEX trains_position ON trains USING gin(position gin_trgm_ops)"
+ @connection.execute "CREATE INDEX trains_name_and_position ON trains USING btree(name, position text_pattern_ops)"
+
+ output = dump_table_schema "trains"
+
+ assert_match(/opclass: :gin_trgm_ops/, output)
+ assert_match(/opclass: \{ position: :text_pattern_ops \}/, output)
+ end
end
class SchemaIndexNullsOrderTest < ActiveRecord::PostgreSQLTestCase
diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
index 984b2f5ea4..919ff3d158 100644
--- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
@@ -94,7 +94,6 @@ module ActiveRecord
end
test "raises LockWaitTimeout when lock wait timeout exceeded" do
- skip unless ActiveRecord::Base.connection.postgresql_version >= 90300
assert_raises(ActiveRecord::LockWaitTimeout) do
s = Sample.create!(value: 1)
latch1 = Concurrent::CountDownLatch.new
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 71d07e2f4c..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
@@ -198,10 +216,10 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
# Create custom PostgreSQL function to generate UUIDs
# to test dumping tables which columns have defaults with custom functions
- connection.execute <<-SQL
- CREATE OR REPLACE FUNCTION my_uuid_generator() RETURNS uuid
- AS $$ SELECT * FROM #{uuid_function} $$
- LANGUAGE SQL VOLATILE;
+ connection.execute <<~SQL
+ CREATE OR REPLACE FUNCTION my_uuid_generator() RETURNS uuid
+ AS $$ SELECT * FROM #{uuid_function} $$
+ LANGUAGE SQL VOLATILE;
SQL
# Create such a table with custom function as default value generator
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
new file mode 100644
index 0000000000..93a7dafebd
--- /dev/null
+++ b/activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb
@@ -0,0 +1,20 @@
+# 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/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index 1c85ff5674..9d26f32102 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -6,12 +6,8 @@ require "securerandom"
class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
def setup
+ super
@conn = ActiveRecord::Base.connection
- @initial_represent_boolean_as_integer = ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer
- end
-
- def teardown
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = @initial_represent_boolean_as_integer
end
def test_type_cast_binary_encoding_without_logger
@@ -22,18 +18,10 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
end
def test_type_cast_true
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = false
- assert_equal "t", @conn.type_cast(true)
-
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
assert_equal 1, @conn.type_cast(true)
end
def test_type_cast_false
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = false
- assert_equal "f", @conn.type_cast(false)
-
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
assert_equal 0, @conn.type_cast(false)
end
@@ -62,4 +50,30 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value))
end
+
+ def test_quoted_time_dst_utc
+ with_env_tz "America/New_York" do
+ with_timezone_config default: :utc do
+ t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30")
+
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getutc.to_s(:db).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ")
+
+ assert_equal expected, @conn.quoted_time(t)
+ end
+ end
+ end
+
+ def test_quoted_time_dst_local
+ with_env_tz "America/New_York" do
+ with_timezone_config default: :local do
+ t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30")
+
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getlocal.to_s(:db).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ")
+
+ assert_equal expected, @conn.quoted_time(t)
+ 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 d1d4d545a3..806cfbfc00 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -55,11 +55,11 @@ module ActiveRecord
owner = Owner.create!(name: "hello".encode("ascii-8bit"))
owner.reload
select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ", "
- result = Owner.connection.exec_query <<-esql
+ result = Owner.connection.exec_query <<~SQL
SELECT #{select}
FROM #{Owner.table_name}
WHERE #{Owner.primary_key} = #{owner.id}
- esql
+ SQL
assert_not(result.rows.first.include?("blob"), "should not store blobs")
ensure
@@ -87,7 +87,7 @@ module ActiveRecord
def test_connection_no_db
assert_raises(ArgumentError) do
- Base.sqlite3_connection {}
+ Base.sqlite3_connection { }
end
end
@@ -160,14 +160,14 @@ module ActiveRecord
end
def test_quote_binary_column_escapes_it
- DualEncoding.connection.execute(<<-eosql)
+ DualEncoding.connection.execute(<<~SQL)
CREATE TABLE IF NOT EXISTS dual_encodings (
id integer PRIMARY KEY AUTOINCREMENT,
name varchar(255),
data binary
)
- eosql
- str = "\x80".dup.force_encoding("ASCII-8BIT")
+ SQL
+ str = (+"\x80").force_encoding("ASCII-8BIT")
binary = DualEncoding.new name: "いただきます!", data: str
binary.save!
assert_equal str, binary.data
@@ -176,7 +176,7 @@ module ActiveRecord
end
def test_type_cast_should_not_mutate_encoding
- name = "hello".dup.force_encoding(Encoding::ASCII_8BIT)
+ name = (+"hello").force_encoding(Encoding::ASCII_8BIT)
Owner.create(name: name)
assert_equal Encoding::ASCII_8BIT, name.encoding
ensure
@@ -261,7 +261,7 @@ module ActiveRecord
end
def test_tables_logs_name
- sql = <<-SQL
+ sql = <<~SQL
SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence' AND type IN ('table')
SQL
assert_logged [[sql.squish, "SCHEMA", []]] do
@@ -271,7 +271,7 @@ module ActiveRecord
def test_table_exists_logs_name
with_example_table do
- sql = <<-SQL
+ sql = <<~SQL
SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence' AND name = 'ex' AND type IN ('table')
SQL
assert_logged [[sql.squish, "SCHEMA", []]] do
@@ -345,6 +345,42 @@ module ActiveRecord
end
end
+ if ActiveRecord::Base.connection.supports_expression_index?
+ def test_expression_index
+ with_example_table do
+ @conn.add_index "ex", "max(id, number)", name: "expression"
+ index = @conn.indexes("ex").find { |idx| idx.name == "expression" }
+ assert_equal "max(id, number)", index.columns
+ end
+ end
+
+ def test_expression_index_with_where
+ with_example_table do
+ @conn.add_index "ex", "id % 10, max(id, number)", name: "expression", where: "id > 1000"
+ index = @conn.indexes("ex").find { |idx| idx.name == "expression" }
+ assert_equal "id % 10, max(id, number)", index.columns
+ assert_equal "id > 1000", index.where
+ end
+ end
+
+ def test_complicated_expression
+ with_example_table do
+ @conn.execute "CREATE INDEX expression ON ex (id % 10, (CASE WHEN number > 0 THEN max(id, number) END))WHERE(id > 1000)"
+ index = @conn.indexes("ex").find { |idx| idx.name == "expression" }
+ assert_equal "id % 10, (CASE WHEN number > 0 THEN max(id, number) END)", index.columns
+ assert_equal "(id > 1000)", index.where
+ end
+ end
+
+ def test_not_everything_an_expression
+ with_example_table do
+ @conn.add_index "ex", "id, max(id, number)", name: "expression"
+ index = @conn.indexes("ex").find { |idx| idx.name == "expression" }
+ assert_equal "id, max(id, number)", index.columns
+ end
+ end
+ end
+
def test_primary_key
with_example_table do
assert_equal "id", @conn.primary_key("ex")
@@ -500,10 +536,6 @@ module ActiveRecord
end
end
- def test_deprecate_valid_alter_table_type
- assert_deprecated { @conn.valid_alter_table_type?(:string) }
- end
-
def test_db_is_not_readonly_when_readonly_option_is_false
conn = Base.sqlite3_connection database: ":memory:",
adapter: "sqlite3",
@@ -537,6 +569,72 @@ module ActiveRecord
end
end
+ def test_errors_when_an_insert_query_is_called_while_preventing_writes
+ with_example_table "id int, data string" do
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @conn.while_preventing_writes do
+ @conn.execute("INSERT INTO ex (data) VALUES ('138853948594')")
+ end
+ end
+ end
+ end
+
+ def test_errors_when_an_update_query_is_called_while_preventing_writes
+ with_example_table "id int, data string" do
+ @conn.execute("INSERT INTO ex (data) VALUES ('138853948594')")
+
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @conn.while_preventing_writes do
+ @conn.execute("UPDATE ex SET data = '9989' WHERE data = '138853948594'")
+ end
+ end
+ end
+ end
+
+ def test_errors_when_a_delete_query_is_called_while_preventing_writes
+ with_example_table "id int, data string" do
+ @conn.execute("INSERT INTO ex (data) VALUES ('138853948594')")
+
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @conn.while_preventing_writes do
+ @conn.execute("DELETE FROM ex where data = '138853948594'")
+ end
+ end
+ end
+ end
+
+ def test_errors_when_a_replace_query_is_called_while_preventing_writes
+ with_example_table "id int, data string" do
+ @conn.execute("INSERT INTO ex (data) VALUES ('138853948594')")
+
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @conn.while_preventing_writes do
+ @conn.execute("REPLACE INTO ex (data) VALUES ('249823948')")
+ end
+ end
+ end
+ end
+
+ def test_doesnt_error_when_a_select_query_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
+
+ 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)
@@ -549,7 +647,7 @@ module ActiveRecord
end
def with_example_table(definition = nil, table_name = "ex", &block)
- definition ||= <<-SQL
+ definition ||= <<~SQL
id integer PRIMARY KEY AUTOINCREMENT,
number integer
SQL
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
index d70486605f..cfc9853aba 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
@@ -8,17 +8,15 @@ module ActiveRecord
class SQLite3CreateFolder < ActiveRecord::SQLite3TestCase
def test_sqlite_creates_directory
Dir.mktmpdir do |dir|
- begin
- dir = Pathname.new(dir)
- @conn = Base.sqlite3_connection database: dir.join("db/foo.sqlite3"),
- adapter: "sqlite3",
- timeout: 100
+ dir = Pathname.new(dir)
+ @conn = Base.sqlite3_connection database: dir.join("db/foo.sqlite3"),
+ adapter: "sqlite3",
+ timeout: 100
- assert Dir.exist? dir.join("db")
- assert File.exist? dir.join("db/foo.sqlite3")
- ensure
- @conn.disconnect! if @conn
- end
+ assert Dir.exist? dir.join("db")
+ assert File.exist? dir.join("db/foo.sqlite3")
+ ensure
+ @conn.disconnect! if @conn
end
end
end
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index fbdf2ada4b..d270175af4 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -27,7 +27,7 @@ class AggregationsTest < ActiveRecord::TestCase
def test_immutable_value_objects
customers(:david).balance = Money.new(100)
- assert_raise(frozen_error_class) { customers(:david).balance.instance_eval { @amount = 20 } }
+ assert_raise(FrozenError) { customers(:david).balance.instance_eval { @amount = 20 } }
end
def test_inferred_mapping
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index f05dcac7dd..9027cc582a 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
@@ -116,8 +117,8 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
end
end
- assert_not @connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
- assert_not @connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
+ assert @connection.column_exists?(:has_timestamps, :created_at, null: false)
+ assert @connection.column_exists?(:has_timestamps, :updated_at, null: false)
end
def test_timestamps_without_null_set_null_to_false_on_change_table
@@ -129,8 +130,23 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
end
end
- assert_not @connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
- assert_not @connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
+ assert @connection.column_exists?(:has_timestamps, :created_at, null: false)
+ assert @connection.column_exists?(:has_timestamps, :updated_at, null: false)
+ end
+
+ if ActiveRecord::Base.connection.supports_bulk_alter?
+ def test_timestamps_without_null_set_null_to_false_on_change_table_with_bulk
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps
+
+ change_table :has_timestamps, bulk: true do |t|
+ t.timestamps default: Time.now
+ end
+ end
+
+ assert @connection.column_exists?(:has_timestamps, :created_at, null: false)
+ assert @connection.column_exists?(:has_timestamps, :updated_at, null: false)
+ end
end
def test_timestamps_without_null_set_null_to_false_on_add_timestamps
@@ -139,7 +155,58 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
add_timestamps :has_timestamps, default: Time.now
end
- assert_not @connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
- assert_not @connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
+ assert @connection.column_exists?(:has_timestamps, :created_at, null: false)
+ assert @connection.column_exists?(:has_timestamps, :updated_at, null: false)
+ end
+
+ if subsecond_precision_supported?
+ def test_timestamps_sets_presicion_on_create_table
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps do |t|
+ t.timestamps
+ end
+ end
+
+ assert @connection.column_exists?(:has_timestamps, :created_at, precision: 6, null: false)
+ assert @connection.column_exists?(:has_timestamps, :updated_at, precision: 6, null: false)
+ end
+
+ def test_timestamps_sets_presicion_on_change_table
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps
+
+ change_table :has_timestamps do |t|
+ t.timestamps default: Time.now
+ end
+ end
+
+ assert @connection.column_exists?(:has_timestamps, :created_at, precision: 6, null: false)
+ assert @connection.column_exists?(:has_timestamps, :updated_at, precision: 6, null: false)
+ end
+
+ if ActiveRecord::Base.connection.supports_bulk_alter?
+ def test_timestamps_sets_presicion_on_change_table_with_bulk
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps
+
+ change_table :has_timestamps, bulk: true do |t|
+ t.timestamps default: Time.now
+ end
+ end
+
+ assert @connection.column_exists?(:has_timestamps, :created_at, precision: 6, null: false)
+ assert @connection.column_exists?(:has_timestamps, :updated_at, precision: 6, null: false)
+ end
+ end
+
+ def test_timestamps_sets_presicion_on_add_timestamps
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps
+ add_timestamps :has_timestamps, default: Time.now
+ end
+
+ assert @connection.column_exists?(:has_timestamps, :created_at, precision: 6, null: false)
+ assert @connection.column_exists?(:has_timestamps, :updated_at, precision: 6, null: false)
+ end
end
end
diff --git a/activerecord/test/cases/arel/attributes/attribute_test.rb b/activerecord/test/cases/arel/attributes/attribute_test.rb
index 671e273543..c7bd0a053b 100644
--- a/activerecord/test/cases/arel/attributes/attribute_test.rb
+++ b/activerecord/test/cases/arel/attributes/attribute_test.rb
@@ -560,7 +560,7 @@ module Arel
end
end
- describe "with a range" do
+ describe "#between" do
it "can be constructed with a standard range" do
attribute = Attribute.new nil, nil
node = attribute.between(1..3)
@@ -628,7 +628,6 @@ module Arel
node.must_equal Nodes::NotIn.new(attribute, [])
end
-
it "can be constructed with a range ending at Infinity" do
attribute = Attribute.new nil, nil
node = attribute.between(0..::Float::INFINITY)
@@ -639,6 +638,18 @@ module Arel
)
end
+ if Gem::Version.new("2.6.0") <= Gem::Version.new(RUBY_VERSION)
+ it "can be constructed with a range implicitly ending at Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(eval("0..")) # Use eval for compatibility with Ruby < 2.6 parser
+
+ node.must_equal Nodes::GreaterThanOrEqual.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ )
+ end
+ end
+
it "can be constructed with a quoted range ending at Infinity" do
attribute = Attribute.new nil, nil
node = attribute.between(quoted_range(0, ::Float::INFINITY, false))
@@ -664,14 +675,6 @@ module Arel
)
])
end
-
- def quoted_range(begin_val, end_val, exclude)
- OpenStruct.new(
- begin: Nodes::Quoted.new(begin_val),
- end: Nodes::Quoted.new(end_val),
- exclude_end?: exclude,
- )
- end
end
describe "#in" do
@@ -753,21 +756,23 @@ module Arel
end
end
- describe "with a range" do
+ describe "#not_between" do
it "can be constructed with a standard range" do
attribute = Attribute.new nil, nil
node = attribute.not_between(1..3)
- node.must_equal Nodes::Grouping.new(Nodes::Or.new(
- Nodes::LessThan.new(
- attribute,
- Nodes::Casted.new(1, attribute)
- ),
- Nodes::GreaterThan.new(
- attribute,
- Nodes::Casted.new(3, attribute)
+ node.must_equal Nodes::Grouping.new(
+ Nodes::Or.new(
+ Nodes::LessThan.new(
+ attribute,
+ Nodes::Casted.new(1, attribute)
+ ),
+ Nodes::GreaterThan.new(
+ attribute,
+ Nodes::Casted.new(3, attribute)
+ )
)
- ))
+ )
end
it "can be constructed with a range starting from -Infinity" do
@@ -780,6 +785,16 @@ module Arel
)
end
+ it "can be constructed with a quoted range starting from -Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(quoted_range(-::Float::INFINITY, 3, false))
+
+ node.must_equal Nodes::GreaterThan.new(
+ attribute,
+ Nodes::Quoted.new(3)
+ )
+ end
+
it "can be constructed with an exclusive range starting from -Infinity" do
attribute = Attribute.new nil, nil
node = attribute.not_between(-::Float::INFINITY...3)
@@ -790,6 +805,16 @@ module Arel
)
end
+ it "can be constructed with a quoted exclusive range starting from -Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(quoted_range(-::Float::INFINITY, 3, true))
+
+ node.must_equal Nodes::GreaterThanOrEqual.new(
+ attribute,
+ Nodes::Quoted.new(3)
+ )
+ end
+
it "can be constructed with an infinite range" do
attribute = Attribute.new nil, nil
node = attribute.not_between(-::Float::INFINITY..::Float::INFINITY)
@@ -797,6 +822,13 @@ module Arel
node.must_equal Nodes::In.new(attribute, [])
end
+ it "can be constructed with a quoted infinite range" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(quoted_range(-::Float::INFINITY, ::Float::INFINITY, false))
+
+ node.must_equal Nodes::In.new(attribute, [])
+ end
+
it "can be constructed with a range ending at Infinity" do
attribute = Attribute.new nil, nil
node = attribute.not_between(0..::Float::INFINITY)
@@ -807,20 +839,44 @@ module Arel
)
end
+ if Gem::Version.new("2.6.0") <= Gem::Version.new(RUBY_VERSION)
+ it "can be constructed with a range implicitly ending at Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(eval("0..")) # Use eval for compatibility with Ruby < 2.6 parser
+
+ node.must_equal Nodes::LessThan.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ )
+ end
+ end
+
+ it "can be constructed with a quoted range ending at Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(quoted_range(0, ::Float::INFINITY, false))
+
+ node.must_equal Nodes::LessThan.new(
+ attribute,
+ Nodes::Quoted.new(0)
+ )
+ end
+
it "can be constructed with an exclusive range" do
attribute = Attribute.new nil, nil
node = attribute.not_between(0...3)
- node.must_equal Nodes::Grouping.new(Nodes::Or.new(
- Nodes::LessThan.new(
- attribute,
- Nodes::Casted.new(0, attribute)
- ),
- Nodes::GreaterThanOrEqual.new(
- attribute,
- Nodes::Casted.new(3, attribute)
+ node.must_equal Nodes::Grouping.new(
+ Nodes::Or.new(
+ Nodes::LessThan.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ ),
+ Nodes::GreaterThanOrEqual.new(
+ attribute,
+ Nodes::Casted.new(3, attribute)
+ )
)
- ))
+ )
end
end
@@ -1010,6 +1066,15 @@ module Arel
condition.to_sql.must_equal %("foo"."id" = (select 1))
end
end
+
+ private
+ def quoted_range(begin_val, end_val, exclude)
+ OpenStruct.new(
+ begin: Nodes::Quoted.new(begin_val),
+ end: Nodes::Quoted.new(end_val),
+ exclude_end?: exclude,
+ )
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/attributes/math_test.rb b/activerecord/test/cases/arel/attributes/math_test.rb
index f3aabe4f60..41eea217c0 100644
--- a/activerecord/test/cases/arel/attributes/math_test.rb
+++ b/activerecord/test/cases/arel/attributes/math_test.rb
@@ -6,35 +6,35 @@ module Arel
module Attributes
class MathTest < Arel::Spec
%i[* /].each do |math_operator|
- it "average should be compatiable with #{math_operator}" do
+ it "average should be compatible with #{math_operator}" do
table = Arel::Table.new :users
(table[:id].average.public_send(math_operator, 2)).to_sql.must_be_like %{
AVG("users"."id") #{math_operator} 2
}
end
- it "count should be compatiable with #{math_operator}" do
+ it "count should be compatible with #{math_operator}" do
table = Arel::Table.new :users
(table[:id].count.public_send(math_operator, 2)).to_sql.must_be_like %{
COUNT("users"."id") #{math_operator} 2
}
end
- it "maximum should be compatiable with #{math_operator}" do
+ it "maximum should be compatible with #{math_operator}" do
table = Arel::Table.new :users
(table[:id].maximum.public_send(math_operator, 2)).to_sql.must_be_like %{
MAX("users"."id") #{math_operator} 2
}
end
- it "minimum should be compatiable with #{math_operator}" do
+ it "minimum should be compatible with #{math_operator}" do
table = Arel::Table.new :users
(table[:id].minimum.public_send(math_operator, 2)).to_sql.must_be_like %{
MIN("users"."id") #{math_operator} 2
}
end
- it "attribute node should be compatiable with #{math_operator}" do
+ it "attribute node should be compatible with #{math_operator}" do
table = Arel::Table.new :users
(table[:id].public_send(math_operator, 2)).to_sql.must_be_like %{
"users"."id" #{math_operator} 2
@@ -43,35 +43,35 @@ module Arel
end
%i[+ - & | ^ << >>].each do |math_operator|
- it "average should be compatiable with #{math_operator}" do
+ it "average should be compatible with #{math_operator}" do
table = Arel::Table.new :users
(table[:id].average.public_send(math_operator, 2)).to_sql.must_be_like %{
(AVG("users"."id") #{math_operator} 2)
}
end
- it "count should be compatiable with #{math_operator}" do
+ it "count should be compatible with #{math_operator}" do
table = Arel::Table.new :users
(table[:id].count.public_send(math_operator, 2)).to_sql.must_be_like %{
(COUNT("users"."id") #{math_operator} 2)
}
end
- it "maximum should be compatiable with #{math_operator}" do
+ it "maximum should be compatible with #{math_operator}" do
table = Arel::Table.new :users
(table[:id].maximum.public_send(math_operator, 2)).to_sql.must_be_like %{
(MAX("users"."id") #{math_operator} 2)
}
end
- it "minimum should be compatiable with #{math_operator}" do
+ it "minimum should be compatible with #{math_operator}" do
table = Arel::Table.new :users
(table[:id].minimum.public_send(math_operator, 2)).to_sql.must_be_like %{
(MIN("users"."id") #{math_operator} 2)
}
end
- it "attribute node should be compatiable with #{math_operator}" do
+ it "attribute node should be compatible with #{math_operator}" do
table = Arel::Table.new :users
(table[:id].public_send(math_operator, 2)).to_sql.must_be_like %{
("users"."id" #{math_operator} 2)
diff --git a/activerecord/test/cases/arel/collectors/sql_string_test.rb b/activerecord/test/cases/arel/collectors/sql_string_test.rb
index 380573494d..443c7eb54b 100644
--- a/activerecord/test/cases/arel/collectors/sql_string_test.rb
+++ b/activerecord/test/cases/arel/collectors/sql_string_test.rb
@@ -19,27 +19,21 @@ module Arel
collect(node).value
end
- def ast_with_binds(bv)
+ def ast_with_binds
table = Table.new(:users)
manager = Arel::SelectManager.new table
- manager.where(table[:age].eq(bv))
- manager.where(table[:name].eq(bv))
+ manager.where(table[:age].eq(Nodes::BindParam.new("hello")))
+ manager.where(table[:name].eq(Nodes::BindParam.new("world")))
manager.ast
end
def test_compile
- bv = Nodes::BindParam.new(1)
- collector = collect ast_with_binds bv
-
- sql = collector.compile ["hello", "world"]
+ sql = compile(ast_with_binds)
assert_equal 'SELECT FROM "users" WHERE "users"."age" = ? AND "users"."name" = ?', sql
end
def test_returned_sql_uses_utf8_encoding
- bv = Nodes::BindParam.new(1)
- collector = collect ast_with_binds bv
-
- sql = collector.compile ["hello", "world"]
+ sql = compile(ast_with_binds)
assert_equal sql.encoding, Encoding::UTF_8
end
end
diff --git a/activerecord/test/cases/arel/delete_manager_test.rb b/activerecord/test/cases/arel/delete_manager_test.rb
index 17a5271039..0bad02f4d2 100644
--- a/activerecord/test/cases/arel/delete_manager_test.rb
+++ b/activerecord/test/cases/arel/delete_manager_test.rb
@@ -15,6 +15,7 @@ module Arel
dm = Arel::DeleteManager.new
dm.take 10
dm.from table
+ dm.key = table[:id]
assert_match(/LIMIT 10/, dm.to_sql)
end
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/and_test.rb b/activerecord/test/cases/arel/nodes/and_test.rb
index eff54abd91..d123ca9fd0 100644
--- a/activerecord/test/cases/arel/nodes/and_test.rb
+++ b/activerecord/test/cases/arel/nodes/and_test.rb
@@ -16,6 +16,15 @@ module Arel
assert_equal 2, array.uniq.size
end
end
+
+ describe "functions as node expression" do
+ it "allows aliasing" do
+ aliased = And.new(["foo", "bar"]).as("baz")
+
+ assert_kind_of As, aliased
+ assert_kind_of SqlLiteral, aliased.right
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/nodes/case_test.rb b/activerecord/test/cases/arel/nodes/case_test.rb
index 89861488df..946c2b0453 100644
--- a/activerecord/test/cases/arel/nodes/case_test.rb
+++ b/activerecord/test/cases/arel/nodes/case_test.rb
@@ -80,6 +80,16 @@ module Arel
assert_equal 2, array.uniq.size
end
end
+
+ describe "#as" do
+ it "allows aliasing" do
+ node = Case.new "foo"
+ as = node.as("bar")
+
+ assert_equal node, as.left
+ assert_kind_of Arel::Nodes::SqlLiteral, as.right
+ end
+ end
end
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 6b10d0b612..e6c49cd429 100644
--- a/activerecord/test/cases/arel/select_manager_test.rb
+++ b/activerecord/test/cases/arel/select_manager_test.rb
@@ -131,7 +131,7 @@ module Arel
right = table.alias
mgr = table.from
mgr.join(right).on("omg")
- mgr.to_sql.must_be_like %{ SELECT FROM "users" INNER JOIN "users" "users_2" ON omg }
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" INNER JOIN "users" "users_2" ON omg }
end
it "converts to sqlliterals with multiple items" do
@@ -139,7 +139,7 @@ module Arel
right = table.alias
mgr = table.from
mgr.join(right).on("omg", "123")
- mgr.to_sql.must_be_like %{ SELECT FROM "users" INNER JOIN "users" "users_2" ON omg AND 123 }
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" INNER JOIN "users" "users_2" ON omg AND 123 }
end
end
end
@@ -278,7 +278,7 @@ module Arel
@m2.where(table[:age].lt(99))
end
- it "should interect two managers" do
+ it "should intersect two managers" do
# FIXME should this intersect "managers" or "statements" ?
# FIXME this probably shouldn't return a node
node = @m1.intersect @m2
@@ -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..18e6c10c9d 100644
--- a/activerecord/test/cases/arel/support/fake_record.rb
+++ b/activerecord/test/cases/arel/support/fake_record.rb
@@ -58,6 +58,10 @@ module FakeRecord
"\"#{name}\""
end
+ def sanitize_as_sql_comment(comment)
+ comment
+ 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 3baccc7316..106be2311d 100644
--- a/activerecord/test/cases/arel/visitors/depth_first_test.rb
+++ b/activerecord/test/cases/arel/visitors/depth_first_test.rb
@@ -33,7 +33,7 @@ module Arel
Arel::Nodes::Ordering,
Arel::Nodes::StringJoin,
Arel::Nodes::UnqualifiedColumn,
- Arel::Nodes::Top,
+ Arel::Nodes::ValuesList,
Arel::Nodes::Limit,
Arel::Nodes::Else,
].each do |klass|
@@ -101,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,
@@ -117,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 98f3bab620..ade53c358e 100644
--- a/activerecord/test/cases/arel/visitors/dot_test.rb
+++ b/activerecord/test/cases/arel/visitors/dot_test.rb
@@ -37,7 +37,7 @@ module Arel
Arel::Nodes::Offset,
Arel::Nodes::Ordering,
Arel::Nodes::UnqualifiedColumn,
- Arel::Nodes::Top,
+ Arel::Nodes::ValuesList,
Arel::Nodes::Limit,
].each do |klass|
define_method("test_#{klass.name.gsub('::', '_')}") do
@@ -62,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/ibm_db_test.rb b/activerecord/test/cases/arel/visitors/ibm_db_test.rb
index 7163cb34d3..2ddbec3266 100644
--- a/activerecord/test/cases/arel/visitors/ibm_db_test.rb
+++ b/activerecord/test/cases/arel/visitors/ibm_db_test.rb
@@ -29,6 +29,45 @@ module Arel
sql = compile(stmt)
sql.must_be_like "UPDATE \"users\" WHERE \"users\".\"id\" IN (SELECT \"users\".\"id\" FROM \"users\" FETCH FIRST 1 ROWS ONLY)"
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ DECODE("users"."name", 'Aaron Patterson', 0, 1) = 0
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ DECODE("users"."first_name", "users"."last_name", 0, 1) = 0
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ DECODE("users"."first_name", "users"."last_name", 0, 1) = 1
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/informix_test.rb b/activerecord/test/cases/arel/visitors/informix_test.rb
index b0b031cca3..b6c2dd6ae7 100644
--- a/activerecord/test/cases/arel/visitors/informix_test.rb
+++ b/activerecord/test/cases/arel/visitors/informix_test.rb
@@ -54,6 +54,45 @@ module Arel
sql = compile(stmt)
sql.must_be_like 'SELECT FROM "posts" INNER JOIN "comments"'
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ CASE WHEN "users"."name" = 'Aaron Patterson' OR ("users"."name" IS NULL AND 'Aaron Patterson' IS NULL) THEN 0 ELSE 1 END = 0
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ CASE WHEN "users"."first_name" = "users"."last_name" OR ("users"."first_name" IS NULL AND "users"."last_name" IS NULL) THEN 0 ELSE 1 END = 0
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ CASE WHEN "users"."first_name" = "users"."last_name" OR ("users"."first_name" IS NULL AND "users"."last_name" IS NULL) THEN 0 ELSE 1 END = 1
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/mssql_test.rb b/activerecord/test/cases/arel/visitors/mssql_test.rb
index 340376c3d6..74f34b4dad 100644
--- a/activerecord/test/cases/arel/visitors/mssql_test.rb
+++ b/activerecord/test/cases/arel/visitors/mssql_test.rb
@@ -94,6 +94,45 @@ module Arel
sql = compile(stmt)
sql.must_be_like "SELECT COUNT(1) as count_id FROM (SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY ) as _row_num) as _t WHERE _row_num BETWEEN 1 AND 10) AS subquery"
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ EXISTS (VALUES ("users"."name") INTERSECT VALUES ('Aaron Patterson'))
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ EXISTS (VALUES ("users"."first_name") INTERSECT VALUES ("users"."last_name"))
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ NOT EXISTS (VALUES ("users"."first_name") INTERSECT VALUES ("users"."last_name"))
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/mysql_test.rb b/activerecord/test/cases/arel/visitors/mysql_test.rb
index 9d3bad8516..5f37587957 100644
--- a/activerecord/test/cases/arel/visitors/mysql_test.rb
+++ b/activerecord/test/cases/arel/visitors/mysql_test.rb
@@ -13,16 +13,6 @@ module Arel
@visitor.accept(node, Collectors::SQLString.new).value
end
- it "squashes parenthesis on multiple unions" do
- subnode = Nodes::Union.new Arel.sql("left"), Arel.sql("right")
- node = Nodes::Union.new subnode, Arel.sql("topright")
- assert_equal 1, compile(node).scan("(").length
-
- subnode = Nodes::Union.new Arel.sql("left"), Arel.sql("right")
- node = Nodes::Union.new Arel.sql("topleft"), subnode
- assert_equal 1, compile(node).scan("(").length
- end
-
###
# :'(
# http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214
@@ -75,6 +65,45 @@ module Arel
}
end
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ "users"."name" <=> 'Aaron Patterson'
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ "users"."first_name" <=> "users"."last_name"
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" <=> NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ NOT "users"."first_name" <=> "users"."last_name"
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ NOT "users"."name" <=> NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/oracle12_test.rb b/activerecord/test/cases/arel/visitors/oracle12_test.rb
index 83a2ee36ca..4ce5cab4db 100644
--- a/activerecord/test/cases/arel/visitors/oracle12_test.rb
+++ b/activerecord/test/cases/arel/visitors/oracle12_test.rb
@@ -56,6 +56,45 @@ module Arel
}
end
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ DECODE("users"."name", 'Aaron Patterson', 0, 1) = 0
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ DECODE("users"."first_name", "users"."last_name", 0, 1) = 0
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ DECODE("users"."first_name", "users"."last_name", 0, 1) = 1
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/oracle_test.rb b/activerecord/test/cases/arel/visitors/oracle_test.rb
index e1dfe40cf9..893edc7f74 100644
--- a/activerecord/test/cases/arel/visitors/oracle_test.rb
+++ b/activerecord/test/cases/arel/visitors/oracle_test.rb
@@ -192,6 +192,45 @@ module Arel
}
end
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ DECODE("users"."name", 'Aaron Patterson', 0, 1) = 0
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ DECODE("users"."first_name", "users"."last_name", 0, 1) = 0
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ DECODE("users"."first_name", "users"."last_name", 0, 1) = 1
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/postgres_test.rb b/activerecord/test/cases/arel/visitors/postgres_test.rb
index ba9cfcfc64..0f9efb20b4 100644
--- a/activerecord/test/cases/arel/visitors/postgres_test.rb
+++ b/activerecord/test/cases/arel/visitors/postgres_test.rb
@@ -215,7 +215,7 @@ module Arel
}
end
- it "should know how to generate paranthesis when supplied with many Dimensions" do
+ it "should know how to generate parenthesis when supplied with many Dimensions" do
dim1 = Arel::Nodes::GroupingElement.new(@table[:name])
dim2 = Arel::Nodes::GroupingElement.new([@table[:bool], @table[:created_at]])
node = Arel::Nodes::Cube.new([dim1, dim2])
@@ -241,7 +241,7 @@ module Arel
}
end
- it "should know how to generate paranthesis when supplied with many Dimensions" do
+ it "should know how to generate parenthesis when supplied with many Dimensions" do
group1 = Arel::Nodes::GroupingElement.new(@table[:name])
group2 = Arel::Nodes::GroupingElement.new([@table[:bool], @table[:created_at]])
node = Arel::Nodes::GroupingSet.new([group1, group2])
@@ -267,7 +267,7 @@ module Arel
}
end
- it "should know how to generate paranthesis when supplied with many Dimensions" do
+ it "should know how to generate parenthesis when supplied with many Dimensions" do
group1 = Arel::Nodes::GroupingElement.new(@table[:name])
group2 = Arel::Nodes::GroupingElement.new([@table[:bool], @table[:created_at]])
node = Arel::Nodes::RollUp.new([group1, group2])
@@ -276,6 +276,45 @@ module Arel
}
end
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ "users"."name" IS NOT DISTINCT FROM 'Aaron Patterson'
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ "users"."first_name" IS NOT DISTINCT FROM "users"."last_name"
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT DISTINCT FROM NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ "users"."first_name" IS DISTINCT FROM "users"."last_name"
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS DISTINCT FROM NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/sqlite_test.rb b/activerecord/test/cases/arel/visitors/sqlite_test.rb
index 6650b6ff3a..ee4e07a675 100644
--- a/activerecord/test/cases/arel/visitors/sqlite_test.rb
+++ b/activerecord/test/cases/arel/visitors/sqlite_test.rb
@@ -6,7 +6,11 @@ module Arel
module Visitors
class SqliteTest < Arel::Spec
before do
- @visitor = SQLite.new Table.engine.connection_pool
+ @visitor = SQLite.new Table.engine.connection
+ end
+
+ def compile(node)
+ @visitor.accept(node, Collectors::SQLString.new).value
end
it "defaults limit to -1" do
@@ -27,6 +31,45 @@ module Arel
node = Nodes::False.new()
assert_equal "0", @visitor.accept(node, Collectors::SQLString.new).value
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ "users"."name" IS 'Aaron Patterson'
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ "users"."first_name" IS "users"."last_name"
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ "users"."first_name" IS NOT "users"."last_name"
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/to_sql_test.rb b/activerecord/test/cases/arel/visitors/to_sql_test.rb
index ce836eded7..625e37f1c0 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
@@ -155,6 +155,43 @@ module Arel
end
end
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ CASE WHEN "users"."name" = 'Aaron Patterson' OR ("users"."name" IS NULL AND 'Aaron Patterson' IS NULL) THEN 0 ELSE 1 END = 0
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ CASE WHEN "users"."first_name" = "users"."last_name" OR ("users"."first_name" IS NULL AND "users"."last_name" IS NULL) THEN 0 ELSE 1 END = 0
+ }
+ end
+
+ it "should handle nil" do
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ CASE WHEN "users"."first_name" = "users"."last_name" OR ("users"."first_name" IS NULL AND "users"."last_name" IS NULL) THEN 0 ELSE 1 END = 1
+ }
+ end
+
+ it "should handle nil" do
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
+
it "should visit string subclass" do
[
Class.new(String).new(":'("),
@@ -221,7 +258,7 @@ module Arel
sql.must_be_like "foo AS bar"
end
- it "should visit_Bignum" do
+ it "should visit_Integer" do
compile 8787878092
end
@@ -427,7 +464,7 @@ module Arel
compile(node).must_equal %(("products"."price" - 7))
end
- it "should handle Concatination" do
+ it "should handle Concatenation" do
table = Table.new(:users)
node = table[:name].concat(table[:name])
compile(node).must_equal %("users"."name" || "users"."name")
@@ -480,6 +517,28 @@ module Arel
end
end
+ describe "Nodes::Union" do
+ it "squashes parenthesis on multiple unions" do
+ subnode = Nodes::Union.new Arel.sql("left"), Arel.sql("right")
+ node = Nodes::Union.new subnode, Arel.sql("topright")
+ assert_equal("( left UNION right UNION topright )", compile(node))
+ subnode = Nodes::Union.new Arel.sql("left"), Arel.sql("right")
+ node = Nodes::Union.new Arel.sql("topleft"), subnode
+ assert_equal("( topleft UNION left UNION right )", compile(node))
+ end
+ end
+
+ describe "Nodes::UnionAll" do
+ it "squashes parenthesis on multiple union alls" do
+ subnode = Nodes::UnionAll.new Arel.sql("left"), Arel.sql("right")
+ node = Nodes::UnionAll.new subnode, Arel.sql("topright")
+ assert_equal("( left UNION ALL right UNION ALL topright )", compile(node))
+ subnode = Nodes::UnionAll.new Arel.sql("left"), Arel.sql("right")
+ node = Nodes::UnionAll.new Arel.sql("topleft"), subnode
+ assert_equal("( topleft UNION ALL left UNION ALL right )", compile(node))
+ end
+ end
+
describe "Nodes::NotIn" do
it "should know how to visit" do
node = @attr.not_in [1, 2, 3]
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 0cc4ed7127..3525fa2ab8 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -32,16 +32,19 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
:posts, :tags, :taggings, :comments, :sponsors, :members
def test_belongs_to
- firm = Client.find(3).firm
- assert_not_nil firm
- assert_equal companies(:first_firm).name, firm.name
+ client = Client.find(3)
+ first_firm = companies(:first_firm)
+ assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) do
+ assert_equal first_firm, client.firm
+ assert_equal first_firm.name, client.firm.name
+ end
end
def test_assigning_belongs_to_on_destroyed_object
client = Client.create!(name: "Client")
client.destroy!
- assert_raise(frozen_error_class) { client.firm = nil }
- assert_raise(frozen_error_class) { client.firm = Firm.new(name: "Firm") }
+ assert_raise(FrozenError) { client.firm = nil }
+ assert_raise(FrozenError) { client.firm = Firm.new(name: "Firm") }
end
def test_eager_loading_wont_mutate_owner_record
@@ -60,7 +63,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
ActiveRecord::SQLCounter.clear_log
Client.find(3).firm
ensure
- assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query"
+ sql_log = ActiveRecord::SQLCounter.log
+ assert sql_log.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query: #{sql_log}"
end
def test_belongs_to_with_primary_key
@@ -367,6 +371,30 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal "ODEGY", odegy_account.reload_firm.name
end
+ def test_reload_the_belonging_object_with_query_cache
+ odegy_account_id = accounts(:odegy_account).id
+
+ connection = ActiveRecord::Base.connection
+ connection.enable_query_cache!
+ connection.clear_query_cache
+
+ # Populate the cache with a query
+ odegy_account = Account.find(odegy_account_id)
+
+ # Populate the cache with a second query
+ odegy_account.firm
+
+ assert_equal 2, connection.query_cache.size
+
+ # Clear the cache and fetch the firm again, populating the cache with a query
+ assert_queries(1) { odegy_account.reload_firm }
+
+ # This query is not cached anymore, so it should make a real SQL query
+ assert_queries(1) { Account.find(odegy_account_id) }
+ ensure
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+
def test_natural_assignment_to_nil
client = Client.find(3)
client.firm = nil
@@ -420,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
@@ -452,15 +485,39 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_belongs_to_counter_with_assigning_nil
- post = Post.find(1)
- comment = Comment.find(1)
+ topic = Topic.create!(title: "debate")
+ reply = Reply.create!(title: "blah!", content: "world around!", topic: topic)
+
+ assert_equal topic.id, reply.parent_id
+ assert_equal 1, topic.reload.replies.size
- assert_equal post.id, comment.post_id
- assert_equal 2, Post.find(post.id).comments.size
+ reply.topic = nil
+ reply.reload
- comment.post = nil
+ assert_equal topic.id, reply.parent_id
+ assert_equal 1, topic.reload.replies.size
- assert_equal 1, Post.find(post.id).comments.size
+ reply.topic = nil
+ reply.save!
+
+ assert_equal 0, topic.reload.replies.size
+ end
+
+ def test_belongs_to_counter_with_assigning_new_object
+ topic = Topic.create!(title: "debate")
+ reply = Reply.create!(title: "blah!", content: "world around!", topic: topic)
+
+ assert_equal topic.id, reply.parent_id
+ assert_equal 1, topic.reload.replies_count
+
+ topic2 = reply.build_topic(title: "debate2")
+ reply.save!
+
+ assert_not_equal topic.id, reply.parent_id
+ assert_equal topic2.id, reply.parent_id
+
+ assert_equal 0, topic.reload.replies_count
+ assert_equal 1, topic2.reload.replies_count
end
def test_belongs_to_with_primary_key_counter
@@ -485,11 +542,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 0, debate2.reload.replies_count
reply.topic_with_primary_key = debate2
+ reply.save!
assert_equal 0, debate.reload.replies_count
assert_equal 1, debate2.reload.replies_count
reply.topic_with_primary_key = nil
+ reply.save!
assert_equal 0, debate.reload.replies_count
assert_equal 0, debate2.reload.replies_count
@@ -516,11 +575,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 1, Topic.find(topic2.id).replies.size
reply1.topic = nil
+ reply1.save!
assert_equal 0, Topic.find(topic1.id).replies.size
assert_equal 0, Topic.find(topic2.id).replies.size
reply1.topic = topic1
+ reply1.save!
assert_equal 1, Topic.find(topic1.id).replies.size
assert_equal 0, Topic.find(topic2.id).replies.size
@@ -550,7 +611,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_belongs_to_counter_after_save
topic = Topic.create!(title: "monday night")
- topic.replies.create!(title: "re: monday night", content: "football")
+
+ assert_queries(2) do
+ topic.replies.create!(title: "re: monday night", content: "football")
+ end
+
assert_equal 1, Topic.find(topic.id)[:replies_count]
topic.save!
@@ -584,8 +649,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
debate.touch(time: time)
debate2.touch(time: time)
- reply.parent_title = "debate"
- reply.save!
+ assert_queries(3) do
+ reply.parent_title = "debate"
+ reply.save!
+ end
assert_operator debate.reload.updated_at, :>, time
assert_operator debate2.reload.updated_at, :>, time
@@ -593,7 +660,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
debate.touch(time: time)
debate2.touch(time: time)
- reply.topic_with_primary_key = debate2
+ assert_queries(3) do
+ reply.topic_with_primary_key = debate2
+ reply.save!
+ end
assert_operator debate.reload.updated_at, :>, time
assert_operator debate2.reload.updated_at, :>, time
@@ -657,7 +727,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
line_item = LineItem.create!
Invoice.create!(line_items: [line_item])
- assert_queries(0) { line_item.save }
+ assert_no_queries { line_item.save }
end
def test_belongs_to_with_touch_option_on_destroy
@@ -752,7 +822,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_dont_find_target_when_foreign_key_is_null
tagging = taggings(:thinking_general)
- assert_queries(0) { tagging.super_tag }
+ assert_no_queries { tagging.super_tag }
end
def test_dont_find_target_when_saving_foreign_key_after_stale_association_loaded
@@ -772,6 +842,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
reply = Reply.create(title: "re: zoom", content: "speedy quick!")
reply.topic = topic
+ reply.save!
assert_equal 1, topic.reload[:replies_count]
assert_equal 1, topic.replies.size
@@ -827,6 +898,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
silly = SillyReply.create(title: "gaga", content: "boo-boo")
silly.reply = reply
+ silly.save!
assert_equal 1, reply.reload[:replies_count]
assert_equal 1, reply.replies.size
@@ -1227,17 +1299,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_belongs_to_with_out_of_range_value_assigning
- model = Class.new(Comment) do
+ model = Class.new(Author) do
def self.name; "Temp"; end
- validates :post, presence: true
+ validates :author_address, presence: true
end
- comment = model.new
- comment.post_id = 9223372036854775808 # out of range in the bigint
+ author = model.new
+ author.author_address_id = 9223372036854775808 # out of range in the bigint
- assert_nil comment.post
- assert_not_predicate comment, :valid?
- assert_equal [{ error: :blank }], comment.errors.details[:post]
+ assert_nil author.author_address
+ assert_not_predicate author, :valid?
+ assert_equal [{ error: :blank }], author.errors.details[:author_address]
end
def test_polymorphic_with_custom_primary_key
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index e717621928..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
@@ -117,12 +117,11 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_has_many_sti_and_subclasses
- silly = SillyReply.new(title: "gaga", content: "boo-boo", parent_id: 1)
- silly.parent_id = 1
- assert silly.save
+ reply = Reply.new(title: "gaga", content: "boo-boo", parent_id: 1)
+ 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
@@ -161,6 +160,16 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
end
+ def test_preload_through_missing_records
+ post = Post.where.not(author_id: Author.select(:id)).preload(author: { comments: :post }).first!
+ assert_no_queries { assert_nil post.author }
+ end
+
+ def test_eager_association_loading_with_missing_first_record
+ posts = Post.where(id: 3).preload(author: { comments: :post }).to_a
+ assert_equal posts.size, 1
+ end
+
def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through
source = Vertex.all.merge!(includes: { sinks: { sinks: { sinks: :sinks } } }, order: "vertices.id").first
assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first }
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 c5b2b77bd4..525ad3197a 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -92,7 +92,7 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase
def test_include_query
res = ShapeExpression.all.merge!(includes: [ :shape, { paint: :non_poly } ]).to_a
assert_equal NUM_SHAPE_EXPRESSIONS, res.size
- assert_queries(0) do
+ assert_no_queries do
res.each do |se|
assert_not_nil se.paint.non_poly, "this is the association that was loading incorrectly before the change"
assert_not_nil se.shape, "just making sure other associations still work"
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 5b8d4722af..594d161fa3 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"
@@ -18,6 +19,7 @@ require "models/job"
require "models/subscriber"
require "models/subscription"
require "models/book"
+require "models/citation"
require "models/developer"
require "models/computer"
require "models/project"
@@ -29,9 +31,21 @@ require "models/sponsor"
require "models/mentor"
require "models/contract"
+class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
+ fixtures :citations
+
+ def test_preloading_too_many_ids
+ assert_equal Citation.count, Citation.preload(:reference_of).to_a.size
+ end
+
+ def test_eager_loading_too_may_ids
+ assert_equal Citation.count, Citation.eager_load(:citations).offset(0).size
+ end
+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
@@ -76,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
@@ -1246,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
@@ -1333,7 +1353,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_joins_with_includes_should_preload_via_joins
post = assert_queries(1) { Post.includes(:comments).joins(:comments).order("posts.id desc").to_a.first }
- assert_queries(0) do
+ assert_no_queries do
assert_not_equal 0, post.comments.to_a.count
end
end
@@ -1380,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
@@ -1571,8 +1604,9 @@ class EagerAssociationTest < ActiveRecord::TestCase
# CollectionProxy#reader is expensive, so the preloader avoids calling it.
test "preloading has_many_through association avoids calling association.reader" do
- ActiveRecord::Associations::HasManyAssociation.any_instance.expects(:reader).never
- Author.preload(:readonly_comments).first!
+ assert_not_called_on_instance_of(ActiveRecord::Associations::HasManyAssociation, :reader) do
+ Author.preload(:readonly_comments).first!
+ end
end
test "preloading through a polymorphic association doesn't require the association to exist" do
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 5eacb5a3d8..aef8f31112 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -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.define_extensions(model, :association_name) { }
end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index f414fbf64b..fe8bdd03ba 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -25,6 +25,8 @@ require "models/user"
require "models/member"
require "models/membership"
require "models/sponsor"
+require "models/lesson"
+require "models/student"
require "models/country"
require "models/treaty"
require "models/vertex"
@@ -275,7 +277,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_habtm_saving_multiple_relationships
new_project = Project.new("name" => "Grimetime")
amount_of_developers = 4
- developers = (0...amount_of_developers).collect { |i| Developer.create(name: "JME #{i}") }.reverse
+ developers = (0...amount_of_developers).reverse_each.map { |i| Developer.create(name: "JME #{i}") }
new_project.developer_ids = [developers[0].id, developers[1].id]
new_project.developers_with_callback_ids = [developers[2].id, developers[3].id]
@@ -310,7 +312,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_build
devel = Developer.find(1)
- proj = assert_no_queries(ignore_none: false) { devel.projects.build("name" => "Projekt") }
+
+ # Load schema information so we don't query below if running just this test.
+ Project.define_attribute_methods
+
+ proj = assert_no_queries { devel.projects.build("name" => "Projekt") }
assert_not_predicate devel.projects, :loaded?
assert_equal devel.projects.last, proj
@@ -325,7 +331,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_new_aliased_to_build
devel = Developer.find(1)
- proj = assert_no_queries(ignore_none: false) { devel.projects.new("name" => "Projekt") }
+
+ # Load schema information so we don't query below if running just this test.
+ Project.define_attribute_methods
+
+ proj = assert_no_queries { devel.projects.new("name" => "Projekt") }
assert_not_predicate devel.projects, :loaded?
assert_equal devel.projects.last, proj
@@ -546,7 +556,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
developer = project.developers.first
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_predicate project.developers, :loaded?
assert_includes project.developers, developer
end
@@ -741,7 +751,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
developer = developers(:david)
developer.projects.reload
- assert_queries(0) do
+ assert_no_queries do
developer.project_ids
developer.project_ids
end
@@ -772,6 +782,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal [projects(:active_record), projects(:action_controller)].map(&:id).sort, developer.project_ids.sort
end
+ def test_singular_ids_are_reloaded_after_collection_concat
+ student = Student.create(name: "Alberto Almagro")
+ student.lesson_ids
+
+ lesson = Lesson.create(name: "DSI")
+ student.lessons << lesson
+
+ assert_includes student.lesson_ids, lesson.id
+ end
+
def test_scoped_find_on_through_association_doesnt_return_read_only_records
tag = Post.find(1).tags.find_by_name("General")
@@ -781,7 +801,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_has_many_through_polymorphic_has_manys_works
- assert_equal [10, 20].to_set, pirates(:redbeard).treasure_estimates.map(&:price).to_set
+ assert_equal ["$10.00", "$20.00"].to_set, pirates(:redbeard).treasure_estimates.map(&:price).to_set
end
def test_symbols_as_keys
@@ -859,7 +879,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_has_and_belongs_to_many_associations_on_new_records_use_null_relations
projects = Developer.new.projects
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [], projects
assert_equal [], projects.where(title: "omg")
assert_equal [], projects.pluck(:title)
@@ -987,16 +1007,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_has_and_belongs_to_many_while_partial_writes_false
- begin
- original_partial_writes = ActiveRecord::Base.partial_writes
- ActiveRecord::Base.partial_writes = false
- developer = Developer.new(name: "Mehmet Emin İNAÇ")
- developer.projects << Project.new(name: "Bounty")
-
- assert developer.save
- ensure
- ActiveRecord::Base.partial_writes = original_partial_writes
- end
+ original_partial_writes = ActiveRecord::Base.partial_writes
+ ActiveRecord::Base.partial_writes = false
+ developer = Developer.new(name: "Mehmet Emin İNAÇ")
+ developer.projects << Project.new(name: "Bounty")
+
+ assert developer.save
+ ensure
+ ActiveRecord::Base.partial_writes = original_partial_writes
end
def test_has_and_belongs_to_many_with_belongs_to
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 0ca902385a..6a7efe2121 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -114,7 +114,7 @@ end
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :author_addresses, :comments,
- :posts, :readers, :taggings, :cars, :jobs, :tags,
+ :posts, :readers, :taggings, :cars, :tags,
:categorizations, :zines, :interests
def setup
@@ -265,7 +265,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
car = Car.create(name: "honda")
car.funky_bulbs.create!
assert_equal 1, car.funky_bulbs.count
- assert_nothing_raised { car.reload.funky_bulbs.delete_all }
+ assert_equal 1, car.reload.funky_bulbs.delete_all
assert_equal 0, car.funky_bulbs.count, "bulbs should have been deleted using :delete_all strategy"
end
@@ -295,6 +295,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal(expected_sql, loaded_sql)
end
+ def test_delete_all_on_association_clears_scope
+ author = Author.create!(name: "Gannon")
+ posts = author.posts
+ posts.create!(title: "test", body: "body")
+ posts.delete_all
+ assert_nil posts.first
+ end
+
def test_building_the_associated_object_with_implicit_sti_base_class
firm = DependentFirm.new
company = firm.companies.build
@@ -377,6 +385,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal invoice.id, line_item.invoice_id
end
+ class SpecialAuthor < ActiveRecord::Base
+ self.table_name = "authors"
+ has_many :books, class_name: "SpecialBook", foreign_key: :author_id
+ end
+
+ class SpecialBook < ActiveRecord::Base
+ self.table_name = "books"
+
+ belongs_to :author
+ enum read_status: { unread: 0, reading: 2, read: 3, forgotten: nil }
+ end
+
+ def test_association_enum_works_properly
+ author = SpecialAuthor.create!(name: "Test")
+ book = SpecialBook.create!(read_status: "reading")
+ author.books << book
+
+ assert_equal "reading", book.read_status
+ assert_not_equal 0, SpecialAuthor.joins(:books).where(books: { read_status: "reading" }).count
+ end
+
# When creating objects on the association, we must not do it within a scope (even though it
# would be convenient), because this would cause that scope to be applied to any callbacks etc.
def test_build_and_create_should_not_happen_within_scope
@@ -438,7 +467,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_finder_method_with_dirty_target
company = companies(:first_firm)
new_clients = []
- assert_no_queries(ignore_none: false) do
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ assert_no_queries do
new_clients << company.clients_of_firm.build(name: "Another Client")
new_clients << company.clients_of_firm.build(name: "Another Client II")
new_clients << company.clients_of_firm.build(name: "Another Client III")
@@ -458,7 +491,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_finder_bang_method_with_dirty_target
company = companies(:first_firm)
new_clients = []
- assert_no_queries(ignore_none: false) do
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ assert_no_queries do
new_clients << company.clients_of_firm.build(name: "Another Client")
new_clients << company.clients_of_firm.build(name: "Another Client II")
new_clients << company.clients_of_firm.build(name: "Another Client III")
@@ -801,6 +838,48 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_not_same original_object, collection.first, "Expected #first after #reload to return a new object"
end
+ def test_reload_with_query_cache
+ connection = ActiveRecord::Base.connection
+ connection.enable_query_cache!
+ connection.clear_query_cache
+
+ # Populate the cache with a query
+ firm = Firm.first
+ # Populate the cache with a second query
+ firm.clients.load
+
+ assert_equal 2, connection.query_cache.size
+
+ # Clear the cache and fetch the clients again, populating the cache with a query
+ assert_queries(1) { firm.clients.reload }
+ # This query is cached, so it shouldn't make a real SQL query
+ assert_queries(0) { firm.clients.load }
+
+ assert_equal 1, connection.query_cache.size
+ ensure
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+
+ def test_reloading_unloaded_associations_with_query_cache
+ connection = ActiveRecord::Base.connection
+ connection.enable_query_cache!
+ connection.clear_query_cache
+
+ firm = Firm.create!(name: "firm name")
+ client = firm.clients.create!(name: "client name")
+ firm.clients.to_a # add request to cache
+
+ connection.uncached do
+ client.update!(name: "new client name")
+ end
+
+ firm = Firm.find(firm.id)
+
+ assert_equal [client.name], firm.clients.reload.map(&:name)
+ ensure
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+
def test_find_all_with_include_and_conditions
assert_nothing_raised do
Developer.all.merge!(joins: :audit_logs, where: { "audit_logs.message" => nil, :name => "Smith" }).to_a
@@ -917,9 +996,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_predicate companies(:first_firm).clients_of_firm, :loaded?
- companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")])
+ result = companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")])
assert_equal 4, companies(:first_firm).clients_of_firm.size
assert_equal 4, companies(:first_firm).clients_of_firm.reload.size
+ assert_equal companies(:first_firm).clients_of_firm, result
end
def test_transactions_when_adding_to_persisted
@@ -935,8 +1015,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_transactions_when_adding_to_new_record
- assert_no_queries(ignore_none: false) do
- firm = Firm.new
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ firm = Firm.new
+ assert_no_queries do
firm.clients_of_firm.concat(Client.new("name" => "Natural Company"))
end
end
@@ -950,7 +1033,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_new_aliased_to_build
company = companies(:first_firm)
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.new("name" => "Another Client") }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_client = assert_no_queries { company.clients_of_firm.new("name" => "Another Client") }
assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
@@ -960,7 +1047,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build
company = companies(:first_firm)
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
@@ -1017,7 +1108,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_many
company = companies(:first_firm)
- new_clients = assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_clients = assert_no_queries { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
assert_equal 2, new_clients.size
end
@@ -1029,10 +1124,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_without_loading_association
first_topic = topics(:first)
- Reply.column_names
assert_equal 1, first_topic.replies.length
+ # Load schema information so we don't query below if running just this test.
+ Reply.define_attribute_methods
+
assert_no_queries do
first_topic.replies.build(title: "Not saved", content: "Superstars")
assert_equal 2, first_topic.replies.size
@@ -1043,7 +1140,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_via_block
company = companies(:first_firm)
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_client = assert_no_queries { company.clients_of_firm.build { |client| client.name = "Another Client" } }
assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
@@ -1053,7 +1154,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_many_via_block
company = companies(:first_firm)
- new_clients = assert_no_queries(ignore_none: false) do
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_clients = assert_no_queries do
company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) do |client|
client.name = "changed"
end
@@ -1066,8 +1171,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_create_without_loading_association
first_firm = companies(:first_firm)
- Firm.column_names
- Client.column_names
assert_equal 2, first_firm.clients_of_firm.size
first_firm.clients_of_firm.reset
@@ -1122,7 +1225,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_has_many_without_counter_cache_option
# Ship has a conventionally named `treasures_count` column, but the counter_cache
# option is not given on the association.
- ship = Ship.create(name: "Countless", treasures_count: 10)
+ ship = Ship.create!(name: "Countless", treasures_count: 10)
assert_not_predicate Ship.reflect_on_association(:treasures), :has_cached_counter?
@@ -1177,6 +1280,38 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, topic.reload.replies.size
end
+ def test_counter_cache_updates_in_memory_after_update_with_inverse_of_disabled
+ topic = Topic.create!(title: "Zoom-zoom-zoom")
+
+ assert_equal 0, topic.replies_count
+
+ reply1 = Reply.create!(title: "re: zoom", content: "speedy quick!")
+ reply2 = Reply.create!(title: "re: zoom 2", content: "OMG lol!")
+
+ assert_queries(4) do
+ topic.replies << [reply1, reply2]
+ end
+
+ assert_equal 2, topic.replies_count
+ assert_equal 2, topic.reload.replies_count
+ end
+
+ def test_counter_cache_updates_in_memory_after_update_with_inverse_of_enabled
+ category = Category.create!(name: "Counter Cache")
+
+ assert_nil category.categorizations_count
+
+ categorization1 = Categorization.create!
+ categorization2 = Categorization.create!
+
+ assert_queries(4) do
+ category.categorizations << [categorization1, categorization2]
+ end
+
+ assert_equal 2, category.categorizations_count
+ assert_equal 2, category.reload.categorizations_count
+ end
+
def test_pushing_association_updates_counter_cache
topic = Topic.order("id ASC").first
reply = Reply.create!
@@ -1214,7 +1349,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_empty_with_counter_cache
post = posts(:welcome)
- assert_queries(0) do
+ assert_no_queries do
assert_not_empty post.comments
end
end
@@ -1280,7 +1415,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, clients.count
assert_difference "Client.count", -(clients.count) do
- companies(:first_firm).dependent_clients_of_firm.delete_all
+ assert_equal clients.count, companies(:first_firm).dependent_clients_of_firm.delete_all
end
end
@@ -1312,8 +1447,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_transaction_when_deleting_new_record
- assert_no_queries(ignore_none: false) do
- firm = Firm.new
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ firm = Firm.new
+ assert_no_queries do
client = Client.new("name" => "New Client")
firm.clients_of_firm << client
firm.clients_of_firm.destroy(client)
@@ -1374,10 +1512,20 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_delete_all_with_option_delete_all
firm = companies(:first_firm)
client_id = firm.dependent_clients_of_firm.first.id
- firm.dependent_clients_of_firm.delete_all(:delete_all)
+ count = firm.dependent_clients_of_firm.count
+ assert_equal count, firm.dependent_clients_of_firm.delete_all(:delete_all)
assert_nil Client.find_by_id(client_id)
end
+ def test_delete_all_with_option_nullify
+ firm = companies(:first_firm)
+ client_id = firm.dependent_clients_of_firm.first.id
+ count = firm.dependent_clients_of_firm.count
+ assert_equal firm, Client.find(client_id).firm
+ assert_equal count, firm.dependent_clients_of_firm.delete_all(:nullify)
+ assert_nil Client.find(client_id).firm
+ end
+
def test_delete_all_accepts_limited_parameters
firm = companies(:first_firm)
assert_raise(ArgumentError) do
@@ -1582,6 +1730,38 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert companies(:first_firm).clients_of_firm.reload.empty?, "37signals has no clients after destroy all and refresh"
end
+ def test_destroy_all_on_association_clears_scope
+ author = Author.create!(name: "Gannon")
+ posts = author.posts
+ posts.create!(title: "test", body: "body")
+ posts.destroy_all
+ 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
+ post = posts.create!(title: "test", body: "body")
+ posts.destroy(post)
+ assert_nil posts.first
+ end
+
+ def test_delete_on_association_clears_scope
+ author = Author.create!(name: "Gannon")
+ posts = author.posts
+ post = posts.create!(title: "test", body: "body")
+ posts.delete(post)
+ assert_nil posts.first
+ end
+
def test_dependence
firm = companies(:first_firm)
assert_equal 3, firm.clients.size
@@ -1645,6 +1825,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal num_accounts, Account.count
end
+ def test_depends_and_nullify_on_polymorphic_assoc
+ author = PersonWithPolymorphicDependentNullifyComments.create!(first_name: "Laertis")
+ comment = posts(:welcome).comments.first
+ comment.author = author
+ comment.save!
+
+ assert_equal comment.author_id, author.id
+ assert_equal comment.author_type, author.class.name
+
+ author.destroy
+ comment.reload
+
+ assert_nil comment.author_id
+ assert_nil comment.author_type
+ end
+
def test_restrict_with_exception
firm = RestrictedWithExceptionFirm.create!(name: "restrict")
firm.companies.create(name: "child")
@@ -1748,7 +1944,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients = []
firm.save
- assert_queries(0, ignore_none: true) do
+ assert_no_queries do
firm.clients = []
end
@@ -1770,8 +1966,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_transactions_when_replacing_on_new_record
- assert_no_queries(ignore_none: false) do
- firm = Firm.new
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ firm = Firm.new
+ assert_no_queries do
firm.clients_of_firm = [Client.new("name" => "New Client")]
end
end
@@ -1783,7 +1982,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
company = companies(:first_firm)
company.clients.reload
- assert_queries(0) do
+ assert_no_queries do
company.client_ids
company.client_ids
end
@@ -1798,7 +1997,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_counter_cache_on_unloaded_association
car = Car.create(name: "My AppliCar")
- assert_equal car.engines.size, 0
+ 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
+ firm.clients.build
+ assert_equal 1, firm.clients.size
+ end
+
+ def test_ids_reader_cache_should_be_cleared_when_collection_is_deleted
+ firm = companies(:first_firm)
+ assert_equal [2, 3, 11], firm.client_ids
+ client = firm.clients.first
+ firm.clients.delete(client)
+ assert_equal [3, 11], firm.client_ids
end
def test_get_ids_ignores_include_option
@@ -1810,11 +2024,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_get_ids_for_association_on_new_record_does_not_try_to_find_records
- Company.columns # Load schema information so we don't query below
- Contract.columns # if running just this test.
+ # Load schema information so we don't query below if running just this test.
+ companies(:first_client).contract_ids
company = Company.new
- assert_queries(0) do
+ assert_no_queries do
company.contract_ids
end
@@ -1859,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
@@ -1871,8 +2087,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_has_many_through_respects_hash_conditions
- assert_equal authors(:david).hello_posts, authors(:david).hello_posts_with_hash_conditions
- assert_equal authors(:david).hello_post_comments, authors(:david).hello_post_comments_with_hash_conditions
+ assert_equal authors(:david).hello_posts.sort_by(&:id), authors(:david).hello_posts_with_hash_conditions.sort_by(&:id)
+ assert_equal authors(:david).hello_post_comments.sort_by(&:id), authors(:david).hello_post_comments_with_hash_conditions.sort_by(&:id)
end
def test_include_uses_array_include_after_loaded
@@ -1920,7 +2136,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.load_target
assert_predicate firm.clients, :loaded?
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
firm.clients.first
assert_equal 2, firm.clients.first(2).size
firm.clients.last
@@ -2134,21 +2350,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_defining_has_many_association_with_delete_all_dependency_lazily_evaluates_target_class
- ActiveRecord::Reflection::AssociationReflection.any_instance.expects(:class_name).never
- class_eval(<<-EOF, __FILE__, __LINE__ + 1)
- class DeleteAllModel < ActiveRecord::Base
- has_many :nonentities, :dependent => :delete_all
- end
- EOF
+ assert_not_called_on_instance_of(
+ ActiveRecord::Reflection::AssociationReflection,
+ :class_name,
+ ) do
+ class_eval(<<-EOF, __FILE__, __LINE__ + 1)
+ class DeleteAllModel < ActiveRecord::Base
+ has_many :nonentities, :dependent => :delete_all
+ end
+ EOF
+ end
end
def test_defining_has_many_association_with_nullify_dependency_lazily_evaluates_target_class
- ActiveRecord::Reflection::AssociationReflection.any_instance.expects(:class_name).never
- class_eval(<<-EOF, __FILE__, __LINE__ + 1)
- class NullifyModel < ActiveRecord::Base
- has_many :nonentities, :dependent => :nullify
- end
- EOF
+ assert_not_called_on_instance_of(
+ ActiveRecord::Reflection::AssociationReflection,
+ :class_name,
+ ) do
+ class_eval(<<-EOF, __FILE__, __LINE__ + 1)
+ class NullifyModel < ActiveRecord::Base
+ has_many :nonentities, :dependent => :nullify
+ end
+ EOF
+ end
end
def test_attributes_are_being_set_when_initialized_from_has_many_association_with_where_clause
@@ -2294,6 +2518,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_collection_association_with_private_kernel_method
firm = companies(:first_firm)
assert_equal [accounts(:signals37)], firm.accounts.open
+ assert_equal [accounts(:signals37)], firm.accounts.available
+ end
+
+ def test_association_with_or_doesnt_set_inverse_instance_key
+ firm = companies(:first_firm)
+ accounts = firm.accounts.or(Account.where(firm_id: nil)).order(:id)
+ assert_equal [firm.id, nil], accounts.map(&:firm_id)
+ end
+
+ def test_association_with_rewhere_doesnt_set_inverse_instance_key
+ firm = companies(:first_firm)
+ accounts = firm.accounts.rewhere(firm_id: [firm.id, nil]).order(:id)
+ assert_equal [firm.id, nil], accounts.map(&:firm_id)
end
test "first_or_initialize adds the record to the association" do
@@ -2325,7 +2562,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "has many associations on new records use null relations" do
post = Post.new
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [], post.comments
assert_equal [], post.comments.where(body: "omg")
assert_equal [], post.comments.pluck(:body)
@@ -2716,6 +2953,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_has_many_with_out_of_range_value
+ reference = Reference.create!(id: 2147483648) # out of range in the integer
+ assert_equal [], reference.ideal_jobs
+ end
+
private
def force_signal37_to_load_all_clients_of_firm
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 97e83441a2..c13789f7ec 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -49,11 +49,24 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
Reader.create person_id: 0, post_id: 0
end
+ def test_has_many_through_create_record
+ assert books(:awdr).subscribers.create!(nick: "bob")
+ end
+
def test_marshal_dump
preloaded = Post.includes(:first_blue_tags).first
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
@@ -74,6 +87,15 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
club1.members.sort_by(&:id)
end
+ def test_preload_multiple_instances_of_the_same_record
+ club = Club.create!(name: "Aaron cool banana club")
+ Membership.create! club: club, member: Member.create!(name: "Aaron")
+ Membership.create! club: club, member: Member.create!(name: "Bob")
+
+ preloaded_clubs = Club.joins(:memberships).preload(:membership).to_a
+ assert_no_queries { preloaded_clubs.each(&:membership) }
+ end
+
def test_ordered_has_many_through
person_prime = Class.new(ActiveRecord::Base) do
def self.name; "Person"; end
@@ -194,7 +216,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_no_difference "Job.count" do
assert_difference "Reference.count", -1 do
- person.reload.jobs_with_dependent_destroy.delete_all
+ assert_equal 1, person.reload.jobs_with_dependent_destroy.delete_all
end
end
end
@@ -205,7 +227,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_no_difference "Job.count" do
assert_no_difference "Reference.count" do
- person.reload.jobs_with_dependent_nullify.delete_all
+ assert_equal 1, person.reload.jobs_with_dependent_nullify.delete_all
end
end
end
@@ -216,17 +238,26 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_no_difference "Job.count" do
assert_difference "Reference.count", -1 do
- person.reload.jobs_with_dependent_delete_all.delete_all
+ assert_equal 1, person.reload.jobs_with_dependent_delete_all.delete_all
end
end
end
+ def test_delete_all_on_association_clears_scope
+ post = Post.create!(title: "Rails 6", body: "")
+ people = post.people
+ people.create!(first_name: "Jeb")
+ people.delete_all
+ assert_nil people.first
+ end
+
def test_concat
person = people(:david)
post = posts(:thinking)
- post.people.concat [person]
+ result = post.people.concat [person]
assert_equal 1, post.people.size
assert_equal 1, post.people.reload.size
+ assert_equal post.people, result
end
def test_associate_existing_record_twice_should_add_to_target_twice
@@ -268,7 +299,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_queries(1) { posts(:thinking) }
new_person = nil # so block binding catches it
- assert_queries(0) do
+ # Load schema information so we don't query below if running just this test.
+ Person.define_attribute_methods
+
+ assert_no_queries do
new_person = Person.new first_name: "bob"
end
@@ -288,7 +322,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_associate_new_by_building
assert_queries(1) { posts(:thinking) }
- assert_queries(0) do
+ # Load schema information so we don't query below if running just this test.
+ Person.define_attribute_methods
+
+ assert_no_queries do
posts(:thinking).people.build(first_name: "Bob")
posts(:thinking).people.new(first_name: "Ted")
end
@@ -354,7 +391,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_delete_association
- assert_queries(2) { posts(:welcome);people(:michael); }
+ assert_queries(2) { posts(:welcome); people(:michael); }
assert_queries(1) do
posts(:welcome).people.delete(people(:michael))
@@ -389,6 +426,30 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_empty posts(:welcome).people.reload
end
+ def test_destroy_all_on_association_clears_scope
+ post = Post.create!(title: "Rails 6", body: "")
+ people = post.people
+ people.create!(first_name: "Jeb")
+ people.destroy_all
+ assert_nil people.first
+ end
+
+ def test_destroy_on_association_clears_scope
+ post = Post.create!(title: "Rails 6", body: "")
+ people = post.people
+ person = people.create!(first_name: "Jeb")
+ people.destroy(person)
+ assert_nil people.first
+ end
+
+ def test_delete_on_association_clears_scope
+ post = Post.create!(title: "Rails 6", body: "")
+ people = post.people
+ person = people.create!(first_name: "Jeb")
+ people.delete(person)
+ assert_nil people.first
+ end
+
def test_should_raise_exception_for_destroying_mismatching_records
assert_no_difference ["Person.count", "Reader.count"] do
assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:welcome).people.destroy(posts(:thinking)) }
@@ -557,7 +618,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_replace_association
- assert_queries(4) { posts(:welcome);people(:david);people(:michael); posts(:welcome).people.reload }
+ assert_queries(4) { posts(:welcome); people(:david); people(:michael); posts(:welcome).people.reload }
# 1 query to delete the existing reader (michael)
# 1 query to associate the new reader (david)
@@ -565,15 +626,25 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
posts(:welcome).people = [people(:david)]
end
- assert_queries(0) {
+ assert_no_queries do
assert_includes posts(:welcome).people, people(:david)
assert_not_includes posts(:welcome).people, people(:michael)
- }
+ end
assert_includes posts(:welcome).reload.people.reload, people(:david)
assert_not_includes posts(:welcome).reload.people.reload, people(:michael)
end
+ def test_replace_association_with_duplicates
+ post = posts(:thinking)
+ person = people(:david)
+
+ assert_difference "post.people.count", 2 do
+ post.people = [person]
+ post.people = [person, person]
+ end
+ end
+
def test_replace_order_is_preserved
posts(:welcome).people.clear
posts(:welcome).people = [people(:david), people(:michael)]
@@ -686,13 +757,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_clear_associations
- assert_queries(2) { posts(:welcome);posts(:welcome).people.reload }
+ assert_queries(2) { posts(:welcome); posts(:welcome).people.reload }
assert_queries(1) do
posts(:welcome).people.clear
end
- assert_queries(0) do
+ assert_no_queries do
assert_empty posts(:welcome).people
end
@@ -782,7 +853,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
person = people(:michael)
person.posts.reload
- assert_queries(0) do
+ assert_no_queries do
person.post_ids
person.post_ids
end
@@ -1192,7 +1263,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_through_associations_on_new_records_use_null_relations
person = Person.new
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [], person.posts
assert_equal [], person.posts.where(body: "omg")
assert_equal [], person.posts.pluck(:body)
@@ -1406,6 +1477,24 @@ 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")
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index d7e898a1c0..7bb629466d 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -12,6 +12,9 @@ require "models/bulb"
require "models/author"
require "models/image"
require "models/post"
+require "models/drink_designer"
+require "models/chef"
+require "models/department"
class HasOneAssociationsTest < ActiveRecord::TestCase
self.use_transactional_tests = false unless supports_savepoints?
@@ -22,25 +25,29 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
def test_has_one
- assert_equal companies(:first_firm).account, Account.find(1)
- assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit
+ firm = companies(:first_firm)
+ first_account = Account.find(1)
+ assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) do
+ assert_equal first_account, firm.account
+ assert_equal first_account.credit_limit, firm.account.credit_limit
+ end
end
def test_has_one_does_not_use_order_by
ActiveRecord::SQLCounter.clear_log
companies(:first_firm).account
ensure
- log_all = ActiveRecord::SQLCounter.log_all
- assert log_all.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query: #{log_all}"
+ sql_log = ActiveRecord::SQLCounter.log
+ assert sql_log.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query: #{sql_log}"
end
def test_has_one_cache_nils
firm = companies(:another_firm)
assert_queries(1) { assert_nil firm.account }
- assert_queries(0) { assert_nil firm.account }
+ assert_no_queries { assert_nil firm.account }
- firms = Firm.all.merge!(includes: :account).to_a
- assert_queries(0) { firms.each(&:account) }
+ firms = Firm.includes(:account).to_a
+ assert_no_queries { firms.each(&:account) }
end
def test_with_select
@@ -110,6 +117,21 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_nil Account.find(old_account_id).firm_id
end
+ def test_nullify_on_polymorphic_association
+ department = Department.create!
+ designer = DrinkDesignerWithPolymorphicDependentNullifyChef.create!
+ chef = department.chefs.create!(employable: designer)
+
+ assert_equal chef.employable_id, designer.id
+ assert_equal chef.employable_type, designer.class.name
+
+ designer.destroy!
+ chef.reload
+
+ assert_nil chef.employable_id
+ assert_nil chef.employable_type
+ end
+
def test_nullification_on_destroyed_association
developer = Developer.create!(name: "Someone")
ship = Ship.create!(name: "Planet Caravan", developer: developer)
@@ -231,9 +253,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
def test_build_association_dont_create_transaction
- assert_no_queries(ignore_none: false) {
- Firm.new.build_account
- }
+ # Load schema information so we don't query below if running just this test.
+ Account.define_attribute_methods
+
+ firm = Firm.new
+ assert_no_queries do
+ firm.build_account
+ end
end
def test_building_the_associated_object_with_implicit_sti_base_class
@@ -329,6 +355,29 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal 80, odegy.reload_account.credit_limit
end
+ def test_reload_association_with_query_cache
+ odegy_id = companies(:odegy).id
+
+ connection = ActiveRecord::Base.connection
+ connection.enable_query_cache!
+ connection.clear_query_cache
+
+ # Populate the cache with a query
+ odegy = Company.find(odegy_id)
+ # Populate the cache with a second query
+ odegy.account
+
+ assert_equal 2, connection.query_cache.size
+
+ # Clear the cache and fetch the account again, populating the cache with a query
+ assert_queries(1) { odegy.reload_account }
+
+ # This query is not cached anymore, so it should make a real SQL query
+ assert_queries(1) { Company.find(odegy_id) }
+ ensure
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+
def test_build
firm = Firm.new("name" => "GlobalMegaCorp")
firm.save
@@ -661,6 +710,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
self.table_name = "books"
belongs_to :author, class_name: "SpecialAuthor"
has_one :subscription, class_name: "SpecialSupscription", foreign_key: "subscriber_id"
+
+ enum status: [:proposed, :written, :published]
end
class SpecialAuthor < ActiveRecord::Base
@@ -678,6 +729,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
book = SpecialBook.create!(status: "published")
author.book = book
+ assert_equal "published", book.status
assert_not_equal 0, SpecialAuthor.joins(:book).where(books: { status: "published" }).count
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 0309663943..69b4872519 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -35,6 +35,13 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_equal clubs(:boring_club), @member.club
end
+ def test_has_one_through_executes_limited_query
+ boring_club = clubs(:boring_club)
+ assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) do
+ assert_equal boring_club, @member.general_club
+ end
+ end
+
def test_creating_association_creates_through_record
new_member = Member.create(name: "Chris")
new_member.club = Club.create(name: "LRUG")
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/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb
index 0e54e8c1b0..0a8863c35d 100644
--- a/activerecord/test/cases/associations/left_outer_join_association_test.rb
+++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb
@@ -46,6 +46,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 +66,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 03ed1c1d47..35da74102d 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -137,7 +137,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_through_has_one_through_with_has_one_source_reflection_preload
members = assert_queries(4) { Member.includes(:nested_sponsors).to_a }
mustache = sponsors(:moustache_club_sponsor_for_groucho)
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [mustache], members.first.nested_sponsors
end
end
@@ -196,7 +196,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
# postgresql test if randomly executed then executes "SHOW max_identifier_length". Hence
# the need to ignore certain predefined sqls that deal with system calls.
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [groucho_details, other_details], members.first.organization_member_details_2.sort_by(&:id)
end
end
@@ -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/required_test.rb b/activerecord/test/cases/associations/required_test.rb
index 65a3bb5efe..c7a78e6bc4 100644
--- a/activerecord/test/cases/associations/required_test.rb
+++ b/activerecord/test/cases/associations/required_test.rb
@@ -25,20 +25,18 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
end
test "belongs_to associations can be optional by default" do
- begin
- original_value = ActiveRecord::Base.belongs_to_required_by_default
- ActiveRecord::Base.belongs_to_required_by_default = false
+ original_value = ActiveRecord::Base.belongs_to_required_by_default
+ ActiveRecord::Base.belongs_to_required_by_default = false
- model = subclass_of(Child) do
- belongs_to :parent, inverse_of: false,
- class_name: "RequiredAssociationsTest::Parent"
- end
-
- assert model.new.save
- assert model.new(parent: Parent.new).save
- ensure
- ActiveRecord::Base.belongs_to_required_by_default = original_value
+ model = subclass_of(Child) do
+ belongs_to :parent, inverse_of: false,
+ class_name: "RequiredAssociationsTest::Parent"
end
+
+ assert model.new.save
+ assert model.new(parent: Parent.new).save
+ ensure
+ ActiveRecord::Base.belongs_to_required_by_default = original_value
end
test "required belongs_to associations have presence validated" do
@@ -56,24 +54,22 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
end
test "belongs_to associations can be required by default" do
- begin
- original_value = ActiveRecord::Base.belongs_to_required_by_default
- ActiveRecord::Base.belongs_to_required_by_default = true
+ original_value = ActiveRecord::Base.belongs_to_required_by_default
+ ActiveRecord::Base.belongs_to_required_by_default = true
- model = subclass_of(Child) do
- belongs_to :parent, inverse_of: false,
- class_name: "RequiredAssociationsTest::Parent"
- end
+ model = subclass_of(Child) do
+ belongs_to :parent, inverse_of: false,
+ class_name: "RequiredAssociationsTest::Parent"
+ end
- record = model.new
- assert_not record.save
- assert_equal ["Parent must exist"], record.errors.full_messages
+ record = model.new
+ assert_not record.save
+ assert_equal ["Parent must exist"], record.errors.full_messages
- record.parent = Parent.new
- assert record.save
- ensure
- ActiveRecord::Base.belongs_to_required_by_default = original_value
- end
+ record.parent = Parent.new
+ assert record.save
+ ensure
+ ActiveRecord::Base.belongs_to_required_by_default = original_value
end
test "has_one associations are not required by default" do
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 739eb02e0c..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,
@@ -80,7 +85,7 @@ class AssociationsTest < ActiveRecord::TestCase
def test_force_reload
firm = Firm.new("name" => "A New Firm, Inc")
firm.save
- firm.clients.each {} # forcing to load all clients
+ firm.clients.each { } # forcing to load all clients
assert firm.clients.empty?, "New firm shouldn't have client objects"
assert_equal 0, firm.clients.size, "New firm should have 0 clients"
@@ -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 0bfd46a522..9fd62dcf72 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -56,6 +56,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]", t.attribute_for_inspect(:content)
end
+ test "attribute_for_inspect with a non-primary key id attribute" do
+ t = topics(:first).becomes(TitlePrimaryKeyTopic)
+ t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters"
+
+ assert_equal "1", t.attribute_for_inspect(:id)
+ end
+
test "attribute_present" do
t = Topic.new
t.title = "hello there!"
@@ -310,6 +317,18 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "New topic", topic.title
end
+ test "write_attribute raises ActiveModel::MissingAttributeError when the attribute does not exist" do
+ topic = Topic.first
+ assert_raises(ActiveModel::MissingAttributeError) { topic.update_columns(no_column_exists: "Hello!") }
+ assert_raises(ActiveModel::UnknownAttributeError) { topic.update(no_column_exists: "Hello!") }
+ end
+
+ test "write_attribute allows writing to aliased attributes" do
+ topic = Topic.first
+ assert_nothing_raised { topic.update_columns(heading: "Hello!") }
+ assert_nothing_raised { topic.update(heading: "Hello!") }
+ end
+
test "read_attribute" do
topic = Topic.new
topic.title = "Don't change the topic"
@@ -414,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
@@ -435,8 +458,71 @@ 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
+ object = Company.find_by_sql(<<~SQL).first
SELECT c1.*, c2.type as string_value, c2.rating as int_value
FROM companies c1, companies c2
WHERE c1.firm_id = c2.id
@@ -692,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
@@ -993,7 +1083,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
test "generated attribute methods ancestors have correct class" do
mod = Topic.send(:generated_attribute_methods)
- assert_match %r(GeneratedAttributeMethods), mod.inspect
+ assert_match %r(Topic::GeneratedAttributeMethods), mod.inspect
end
private
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 3bc56694be..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)
@@ -148,6 +159,20 @@ module ActiveRecord
assert_equal 2, klass.new.counter
end
+ test "procs for default values are evaluated even after column_defaults is called" do
+ klass = Class.new(OverloadedType) do
+ @@counter = 0
+ attribute :counter, :integer, default: -> { @@counter += 1 }
+ end
+
+ assert_equal 1, klass.new.counter
+
+ # column_defaults will increment the counter since the proc is called
+ klass.column_defaults
+
+ assert_equal 3, klass.new.counter
+ end
+
test "procs are memoized before type casting" do
klass = Class.new(OverloadedType) do
@@counter = 0
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index ade1f4b44d..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"
@@ -14,6 +15,7 @@ require "models/line_item"
require "models/order"
require "models/parrot"
require "models/pirate"
+require "models/project"
require "models/ship"
require "models/ship_part"
require "models/tag"
@@ -558,6 +560,13 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
assert_equal no_of_clients + 1, Client.count
end
+ def test_parent_should_save_children_record_with_foreign_key_validation_set_in_before_save_callback
+ company = NewlyContractedCompany.new(name: "test")
+
+ assert company.save
+ assert_not_empty company.reload.new_contracts
+ end
+
def test_parent_should_not_get_saved_with_duplicate_children_records
assert_no_difference "Reply.count" do
assert_no_difference "SillyUniqueReply.count" do
@@ -568,7 +577,13 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
])
assert_not reply.save
- assert_not_empty reply.errors
+ assert_equal ["is invalid"], reply.errors[:silly_unique_replies]
+ assert_empty reply.silly_unique_replies.first.errors
+
+ assert_equal(
+ ["has already been taken"],
+ reply.silly_unique_replies.last.errors[:content]
+ )
end
end
end
@@ -628,7 +643,11 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_before_save
company = companies(:first_firm)
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
assert_not_predicate company.clients_of_firm, :loaded?
company.name += "-changed"
@@ -639,7 +658,11 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_many_before_save
company = companies(:first_firm)
- assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ assert_no_queries { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
company.name += "-changed"
assert_queries(3) { assert company.save }
@@ -648,7 +671,11 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_via_block_before_save
company = companies(:first_firm)
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } }
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ new_client = assert_no_queries { company.clients_of_firm.build { |client| client.name = "Another Client" } }
assert_not_predicate company.clients_of_firm, :loaded?
company.name += "-changed"
@@ -659,7 +686,11 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_many_via_block_before_save
company = companies(:first_firm)
- assert_no_queries(ignore_none: false) do
+
+ # Load schema information so we don't query below if running just this test.
+ Client.define_attribute_methods
+
+ assert_no_queries do
company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) do |client|
client.name = "changed"
end
@@ -1086,7 +1117,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
assert @pirate.save
Pirate.transaction do
- assert_queries(0) do
+ assert_no_queries do
assert @pirate.save
end
end
@@ -1167,12 +1198,12 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
def test_changed_for_autosave_should_handle_cycles
@ship.pirate = @pirate
- assert_queries(0) { @ship.save! }
+ assert_no_queries { @ship.save! }
@parrot = @pirate.parrots.create(name: "some_name")
@parrot.name = "changed_name"
assert_queries(1) { @ship.save! }
- assert_queries(0) { @ship.save! }
+ assert_no_queries { @ship.save! }
end
def test_should_automatically_save_bang_the_associated_model
@@ -1289,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 }
+ 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 { author.save }
end
end
@@ -1774,3 +1829,21 @@ class TestAutosaveAssociationOnAHasManyAssociationWithInverse < ActiveRecord::Te
assert_equal 1, comment.post_comments_count
end
end
+
+class TestAutosaveAssociationOnAHasManyAssociationDefinedInSubclassWithAcceptsNestedAttributes < ActiveRecord::TestCase
+ 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!(
+ "projects_attributes" => {
+ "0" => {
+ "name" => "Updated",
+ "id" => valid_project.id
+ }
+ }
+ )
+ valid_project.reload
+
+ assert_equal "Updated", valid_project.name
+ end
+end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index d216fe16fa..99f47cfe37 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -282,11 +282,13 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_initialize_with_invalid_attribute
- Topic.new("title" => "test",
- "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31")
- rescue ActiveRecord::MultiparameterAssignmentErrors => ex
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ Topic.new("title" => "test",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00")
+ end
+
assert_equal(1, ex.errors.size)
- assert_equal("last_read", ex.errors[0].attribute)
+ assert_equal("written_on", ex.errors[0].attribute)
end
def test_create_after_initialize_without_block
@@ -436,12 +438,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
@@ -689,6 +685,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
@@ -853,8 +852,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal company, Company.find(company.id)
end
- # TODO: extend defaults tests to other databases!
- if current_adapter?(:PostgreSQLAdapter)
+ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter, :SQLite3Adapter)
def test_default
with_timezone_config default: :local do
default = Default.new
@@ -866,7 +864,10 @@ class BasicsTest < ActiveRecord::TestCase
# char types
assert_equal "Y", default.char1
assert_equal "a varchar field", default.char2
- assert_equal "a text field", default.char3
+ # Mysql text type can't have default value
+ unless current_adapter?(:Mysql2Adapter)
+ assert_equal "a text field", default.char3
+ end
end
end
end
@@ -1034,11 +1035,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
@@ -1054,23 +1050,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
@@ -1222,14 +1218,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
@@ -1443,6 +1440,14 @@ class BasicsTest < ActiveRecord::TestCase
assert_not_respond_to developer, :first_name=
end
+ test "when ignored attribute is loaded, cast type should be preferred over DB type" do
+ developer = AttributedDeveloper.create
+ developer.update_column :name, "name"
+
+ loaded_developer = AttributedDeveloper.where(id: developer.id).select("*").first
+ assert_equal "Developer: name", loaded_developer.name
+ end
+
test "ignored columns not included in SELECT" do
query = Developer.all.to_sql.downcase
@@ -1476,4 +1481,64 @@ class BasicsTest < ActiveRecord::TestCase
ensure
ActiveRecord::Base.protected_environments = previous_protected_environments
end
+
+ test "creating a record raises if preventing writes" do
+ error = assert_raises ActiveRecord::ReadOnlyError do
+ ActiveRecord::Base.connection.while_preventing_writes do
+ Bird.create! name: "Bluejay"
+ end
+ end
+
+ assert_match %r/\AWrite query attempted while in readonly mode: INSERT /, error.message
+ end
+
+ test "updating a record raises if preventing writes" do
+ bird = Bird.create! name: "Bluejay"
+
+ error = assert_raises ActiveRecord::ReadOnlyError do
+ ActiveRecord::Base.connection.while_preventing_writes do
+ bird.update! name: "Robin"
+ end
+ end
+
+ assert_match %r/\AWrite query attempted while in readonly mode: UPDATE /, error.message
+ end
+
+ test "deleting a record raises if preventing writes" do
+ bird = Bird.create! name: "Bluejay"
+
+ error = assert_raises ActiveRecord::ReadOnlyError do
+ ActiveRecord::Base.connection.while_preventing_writes do
+ bird.destroy!
+ end
+ end
+
+ assert_match %r/\AWrite query attempted while in readonly mode: DELETE /, error.message
+ end
+
+ test "selecting a record does not raise if preventing writes" do
+ bird = Bird.create! name: "Bluejay"
+
+ ActiveRecord::Base.connection.while_preventing_writes do
+ assert_equal bird, Bird.where(name: "Bluejay").first
+ end
+ end
+
+ test "an explain query does not raise if preventing writes" do
+ Bird.create!(name: "Bluejay")
+
+ ActiveRecord::Base.connection.while_preventing_writes do
+ assert_queries(2) { Bird.where(name: "Bluejay").explain }
+ end
+ end
+
+ test "an empty transaction does not raise if preventing writes" do
+ ActiveRecord::Base.connection.while_preventing_writes do
+ assert_queries(2, ignore_none: true) do
+ Bird.transaction do
+ ActiveRecord::Base.connection.materialize_transactions
+ end
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index c8163901c6..cf6e280898 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -24,7 +24,7 @@ class EachTest < ActiveRecord::TestCase
def test_each_should_not_return_query_chain_and_execute_only_one_query
assert_queries(1) do
- result = Post.find_each(batch_size: 100000) {}
+ result = Post.find_each(batch_size: 100000) { }
assert_nil result
end
end
@@ -155,7 +155,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified
- not_a_post = "not a post".dup
+ not_a_post = +"not a post"
def not_a_post.id; end
not_a_post.stub(:id, -> { raise StandardError.new("not_a_post had #id called on it") }) do
assert_nothing_raised do
@@ -183,7 +183,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_error_on_ignore_the_order
assert_raise(ArgumentError) do
- PostWithDefaultScope.find_in_batches(error_on_ignore: true) {}
+ PostWithDefaultScope.find_in_batches(error_on_ignore: true) { }
end
end
@@ -192,7 +192,7 @@ class EachTest < ActiveRecord::TestCase
prev = ActiveRecord::Base.error_on_ignored_order
ActiveRecord::Base.error_on_ignored_order = true
assert_nothing_raised do
- PostWithDefaultScope.find_in_batches(error_on_ignore: false) {}
+ PostWithDefaultScope.find_in_batches(error_on_ignore: false) { }
end
ensure
# Set back to default
@@ -204,7 +204,7 @@ class EachTest < ActiveRecord::TestCase
prev = ActiveRecord::Base.error_on_ignored_order
ActiveRecord::Base.error_on_ignored_order = true
assert_raise(ArgumentError) do
- PostWithDefaultScope.find_in_batches() {}
+ PostWithDefaultScope.find_in_batches() { }
end
ensure
# Set back to default
@@ -213,7 +213,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_not_error_by_default
assert_nothing_raised do
- PostWithDefaultScope.find_in_batches() {}
+ PostWithDefaultScope.find_in_batches() { }
end
end
@@ -228,7 +228,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_not_modify_passed_options
assert_nothing_raised do
- Post.find_in_batches({ batch_size: 42, start: 1 }.freeze) {}
+ Post.find_in_batches({ batch_size: 42, start: 1 }.freeze) { }
end
end
@@ -420,7 +420,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified
- not_a_post = "not a post".dup
+ not_a_post = +"not a post"
def not_a_post.id
raise StandardError.new("not_a_post had #id called on it")
end
@@ -430,7 +430,7 @@ class EachTest < ActiveRecord::TestCase
assert_kind_of ActiveRecord::Relation, relation
assert_kind_of Post, relation.first
- relation = [not_a_post] * relation.count
+ [not_a_post] * relation.count
end
end
end
@@ -446,7 +446,7 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_should_not_modify_passed_options
assert_nothing_raised do
- Post.in_batches({ of: 42, start: 1 }.freeze) {}
+ Post.in_batches({ of: 42, start: 1 }.freeze) { }
end
end
@@ -597,15 +597,15 @@ class EachTest < ActiveRecord::TestCase
table: table_alias,
predicate_builder: predicate_builder
)
- posts.find_each {}
+ posts.find_each { }
end
end
test ".find_each bypasses the query cache for its own queries" do
Post.cache do
assert_queries(2) do
- Post.find_each {}
- Post.find_each {}
+ Post.find_each { }
+ Post.find_each { }
end
end
end
@@ -624,8 +624,8 @@ class EachTest < ActiveRecord::TestCase
test ".find_in_batches bypasses the query cache for its own queries" do
Post.cache do
assert_queries(2) do
- Post.find_in_batches {}
- Post.find_in_batches {}
+ Post.find_in_batches { }
+ Post.find_in_batches { }
end
end
end
@@ -644,8 +644,8 @@ class EachTest < ActiveRecord::TestCase
test ".in_batches bypasses the query cache for its own queries" do
Post.cache do
assert_queries(2) do
- Post.in_batches {}
- Post.in_batches {}
+ Post.in_batches { }
+ Post.in_batches { }
end
end
end
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index d5376ece69..58abdb47f7 100644
--- a/activerecord/test/cases/binary_test.rb
+++ b/activerecord/test/cases/binary_test.rb
@@ -12,7 +12,7 @@ unless current_adapter?(:DB2Adapter)
FIXTURES = %w(flowers.jpg example.log test.txt)
def test_mixed_encoding
- str = "\x80".dup
+ str = +"\x80"
str.force_encoding("ASCII-8BIT")
binary = Binary.new name: "いただきます!", data: str
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 91cc49385c..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,100 @@ 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)
+
+ topics = Topic.where(id: (1 .. bind_params_length).to_a << 2**63)
+ assert_equal Topic.count, topics.count
+
+ topics = Topic.where.not(id: (1 .. bind_params_length).to_a << 2**63)
+ 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)
@@ -67,17 +162,29 @@ if ActiveRecord::Base.connection.prepared_statements
assert_logs_binds(binds)
end
- def test_deprecate_supports_statement_cache
- assert_deprecated { ActiveRecord::Base.connection.supports_statement_cache? }
- 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/cache_key_test.rb b/activerecord/test/cases/cache_key_test.rb
index 3a569f226e..c27eb8a65d 100644
--- a/activerecord/test/cases/cache_key_test.rb
+++ b/activerecord/test/cases/cache_key_test.rb
@@ -44,10 +44,88 @@ module ActiveRecord
test "cache_key_with_version always has both key and version" do
r1 = CacheMeWithVersion.create
- assert_equal "active_record/cache_key_test/cache_me_with_versions/#{r1.id}-#{r1.updated_at.to_s(:usec)}", r1.cache_key_with_version
+ assert_equal "active_record/cache_key_test/cache_me_with_versions/#{r1.id}-#{r1.updated_at.utc.to_s(:usec)}", r1.cache_key_with_version
r2 = CacheMe.create
- assert_equal "active_record/cache_key_test/cache_mes/#{r2.id}-#{r2.updated_at.to_s(:usec)}", r2.cache_key_with_version
+ assert_equal "active_record/cache_key_test/cache_mes/#{r2.id}-#{r2.updated_at.utc.to_s(:usec)}", r2.cache_key_with_version
+ end
+
+ test "cache_version is the same when it comes from the DB or from the user" do
+ skip("Mysql2 and PostgreSQL don't return a string value for updated_at") if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
+
+ record = CacheMeWithVersion.create
+ record_from_db = CacheMeWithVersion.find(record.id)
+ assert_not_called(record_from_db, :updated_at) do
+ record_from_db.cache_version
+ end
+
+ assert_equal record.cache_version, record_from_db.cache_version
+ end
+
+ test "cache_version does not truncate zeros when timestamp ends in zeros" do
+ skip("Mysql2 and PostgreSQL don't return a string value for updated_at") if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
+
+ travel_to Time.now.beginning_of_day do
+ record = CacheMeWithVersion.create
+ record_from_db = CacheMeWithVersion.find(record.id)
+ assert_not_called(record_from_db, :updated_at) do
+ record_from_db.cache_version
+ end
+
+ assert_equal record.cache_version, record_from_db.cache_version
+ end
+ end
+
+ test "cache_version calls updated_at when the value is generated at create time" do
+ record = CacheMeWithVersion.create
+ assert_called(record, :updated_at) do
+ record.cache_version
+ end
+ end
+
+ test "cache_version does NOT call updated_at when value is from the database" do
+ skip("Mysql2 and PostgreSQL don't return a string value for updated_at") if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
+
+ record = CacheMeWithVersion.create
+ record_from_db = CacheMeWithVersion.find(record.id)
+ assert_not_called(record_from_db, :updated_at) do
+ record_from_db.cache_version
+ end
+ end
+
+ test "cache_version does call updated_at when it is assigned via a Time object" do
+ record = CacheMeWithVersion.create
+ record_from_db = CacheMeWithVersion.find(record.id)
+ assert_called(record_from_db, :updated_at) do
+ record_from_db.updated_at = Time.now
+ record_from_db.cache_version
+ end
+ end
+
+ test "cache_version does call updated_at when it is assigned via a string" do
+ record = CacheMeWithVersion.create
+ record_from_db = CacheMeWithVersion.find(record.id)
+ assert_called(record_from_db, :updated_at) do
+ record_from_db.updated_at = Time.now.to_s
+ record_from_db.cache_version
+ end
+ end
+
+ test "cache_version does call updated_at when it is assigned via a hash" do
+ record = CacheMeWithVersion.create
+ record_from_db = CacheMeWithVersion.find(record.id)
+ assert_called(record_from_db, :updated_at) do
+ record_from_db.updated_at = { 1 => 2016, 2 => 11, 3 => 12, 4 => 1, 5 => 2, 6 => 3, 7 => 22 }
+ record_from_db.cache_version
+ end
+ end
+
+ test "updated_at on class but not on instance raises an error" do
+ record = CacheMeWithVersion.create
+ record_from_db = CacheMeWithVersion.where(id: record.id).select(:id).first
+ assert_raises(ActiveModel::MissingAttributeError) do
+ record_from_db.cache_version
+ end
end
end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 5c9ed42173..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
@@ -218,8 +219,8 @@ class CalculationsTest < ActiveRecord::TestCase
Account.select("credit_limit, firm_name").count
}
- assert_match %r{accounts}i, e.message
- assert_match "credit_limit, firm_name", e.message
+ assert_match %r{accounts}i, e.sql
+ assert_match "credit_limit, firm_name", e.sql
end
def test_apply_distinct_in_count
@@ -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)
@@ -428,6 +458,8 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_count_selected_field_with_include
assert_equal 6, Account.includes(:firm).distinct.count
assert_equal 4, Account.includes(:firm).distinct.select(:credit_limit).count
+ assert_equal 4, Account.includes(:firm).distinct.count("DISTINCT credit_limit")
+ assert_equal 4, Account.includes(:firm).distinct.count("DISTINCT(credit_limit)")
end
def test_should_not_perform_joined_include_by_default
@@ -458,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
@@ -509,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
@@ -688,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
@@ -717,6 +770,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [], Topic.includes(:replies).order(:id).offset(5).pluck(:id)
end
+ def test_pluck_with_join
+ assert_equal [[2, 2], [4, 4]], Reply.includes(:topic).pluck(:id, :"topics.id")
+ end
+
def test_group_by_with_limit
expected = { "Post" => 8, "SpecialPost" => 1 }
actual = Post.includes(:comments).group(:type).order(:type).limit(2).count("comments.id")
@@ -832,13 +889,13 @@ class CalculationsTest < ActiveRecord::TestCase
def test_pick_one
assert_equal "The First Topic", Topic.order(:id).pick(:heading)
assert_nil Topic.none.pick(:heading)
- assert_nil Topic.where("1=0").pick(:heading)
+ assert_nil Topic.where(id: 9999999999999999999).pick(:heading)
end
def test_pick_two
assert_equal ["David", "david@loudthinking.com"], Topic.order(:id).pick(:author_name, :author_email_address)
assert_nil Topic.none.pick(:author_name, :author_email_address)
- assert_nil Topic.where("1=0").pick(:author_name, :author_email_address)
+ assert_nil Topic.where(id: 9999999999999999999).pick(:author_name, :author_email_address)
end
def test_pick_delegate_to_all
@@ -876,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)
@@ -911,15 +949,15 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal({ "proposed" => 2, "published" => 2 }, Book.group(:status).count)
end
- def test_deprecate_count_with_block_and_column_name
- assert_deprecated do
- assert_equal 6, Account.count(:firm_id) { true }
+ def test_count_with_block_and_column_name_raises_an_error
+ assert_raises(ArgumentError) do
+ Account.count(:firm_id) { true }
end
end
- def test_deprecate_sum_with_block_and_column_name
- assert_deprecated do
- assert_equal 6, Account.sum(:firm_id) { 1 }
+ def test_sum_with_block_and_column_name_raises_an_error
+ assert_raises(ArgumentError) do
+ Account.sum(:firm_id) { 1 }
end
end
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 253c3099d6..4d6a112af5 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -21,7 +21,7 @@ class CallbackDeveloper < ActiveRecord::Base
def callback_object(callback_method)
klass = Class.new
- klass.send(:define_method, callback_method) do |model|
+ klass.define_method(callback_method) do |model|
model.history << [callback_method, :object]
end
klass.new
@@ -480,7 +480,7 @@ class CallbacksTest < ActiveRecord::TestCase
def test_before_save_doesnt_allow_on_option
exception = assert_raises ArgumentError do
Class.new(ActiveRecord::Base) do
- before_save(on: :create) {}
+ before_save(on: :create) { }
end
end
assert_equal "Unknown key: :on. Valid keys are: :if, :unless, :prepend", exception.message
@@ -489,7 +489,7 @@ class CallbacksTest < ActiveRecord::TestCase
def test_around_save_doesnt_allow_on_option
exception = assert_raises ArgumentError do
Class.new(ActiveRecord::Base) do
- around_save(on: :create) {}
+ around_save(on: :create) { }
end
end
assert_equal "Unknown key: :on. Valid keys are: :if, :unless, :prepend", exception.message
@@ -498,7 +498,7 @@ class CallbacksTest < ActiveRecord::TestCase
def test_after_save_doesnt_allow_on_option
exception = assert_raises ArgumentError do
Class.new(ActiveRecord::Base) do
- after_save(on: :create) {}
+ after_save(on: :create) { }
end
end
assert_equal "Unknown key: :on. Valid keys are: :if, :unless, :prepend", exception.message
diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb
index a5d908344a..483383257b 100644
--- a/activerecord/test/cases/collection_cache_key_test.rb
+++ b/activerecord/test/cases/collection_cache_key_test.rb
@@ -42,6 +42,20 @@ module ActiveRecord
assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
end
+ test "cache_key for relation with custom select and limit" do
+ developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5)
+ developers_with_select = developers.select("developers.*")
+ last_developer_timestamp = developers.first.updated_at
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers_with_select.cache_key)
+
+ /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers_with_select.cache_key
+
+ assert_equal ActiveSupport::Digest.hexdigest(developers_with_select.to_sql), $1
+ assert_equal developers.count.to_s, $2
+ assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
+ end
+
test "cache_key for loaded relation" do
developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5).load
last_developer_timestamp = developers.first.updated_at
@@ -91,12 +105,12 @@ module ActiveRecord
developers = Developer.where(name: "David")
assert_queries(1) { developers.cache_key }
- assert_queries(0) { developers.cache_key }
+ assert_no_queries { developers.cache_key }
end
test "it doesn't trigger any query if the relation is already loaded" do
developers = Developer.where(name: "David").load
- assert_queries(0) { developers.cache_key }
+ assert_no_queries { developers.cache_key }
end
test "relation cache_key changes when the sql query changes" do
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index b8e623f17b..6282759a10 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -28,13 +28,16 @@ module ActiveRecord
end
def test_establish_connection_uses_spec_name
+ old_config = ActiveRecord::Base.configurations
config = { "readonly" => { "adapter" => "sqlite3" } }
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config)
+ ActiveRecord::Base.configurations = config
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(ActiveRecord::Base.configurations)
spec = resolver.spec(:readonly)
@handler.establish_connection(spec.to_hash)
assert_not_nil @handler.retrieve_connection_pool("readonly")
ensure
+ ActiveRecord::Base.configurations = old_config
@handler.remove_connection("readonly")
end
@@ -148,6 +151,35 @@ module ActiveRecord
ActiveRecord::Base.configurations = @prev_configs
end
+ def test_symbolized_configurations_assignment
+ @prev_configs = ActiveRecord::Base.configurations
+ config = {
+ development: {
+ primary: {
+ adapter: "sqlite3",
+ database: "db/development.sqlite3",
+ },
+ },
+ test: {
+ primary: {
+ adapter: "sqlite3",
+ database: "db/test.sqlite3",
+ },
+ },
+ }
+ ActiveRecord::Base.configurations = config
+ ActiveRecord::Base.configurations.configs_for.each do |db_config|
+ assert_instance_of ActiveRecord::DatabaseConfigurations::HashConfig, db_config
+ assert_instance_of String, db_config.env_name
+ assert_instance_of String, db_config.spec_name
+ db_config.config.keys.each do |key|
+ assert_instance_of String, key
+ end
+ end
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ end
+
def test_retrieve_connection
assert @handler.retrieve_connection(@spec_name)
end
@@ -350,6 +382,11 @@ module ActiveRecord
assert_not_nil ActiveRecord::Base.connection
assert_same klass2.connection, ActiveRecord::Base.connection
end
+
+ def test_default_handlers_are_writing_and_reading
+ assert_equal :writing, ActiveRecord::Base.writing_role
+ assert_equal :reading, ActiveRecord::Base.reading_role
+ end
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
new file mode 100644
index 0000000000..36591097b6
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
@@ -0,0 +1,391 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/person"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class ConnectionHandlersMultiDbTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ fixtures :people
+
+ def setup
+ @handlers = { writing: ConnectionHandler.new, reading: ConnectionHandler.new }
+ @rw_handler = @handlers[:writing]
+ @ro_handler = @handlers[:reading]
+ @spec_name = "primary"
+ @rw_pool = @handlers[:writing].establish_connection(ActiveRecord::Base.configurations["arunit"])
+ @ro_pool = @handlers[:reading].establish_connection(ActiveRecord::Base.configurations["arunit"])
+ end
+
+ def teardown
+ ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
+ end
+
+ class MultiConnectionTestModel < ActiveRecord::Base
+ end
+
+ def test_multiple_connection_handlers_works_in_a_threaded_environment
+ tf_writing = Tempfile.open "test_writing"
+ tf_reading = Tempfile.open "test_reading"
+
+ MultiConnectionTestModel.connects_to database: { writing: { database: tf_writing.path, adapter: "sqlite3" }, reading: { database: tf_reading.path, adapter: "sqlite3" } }
+
+ MultiConnectionTestModel.connection.execute("CREATE TABLE `test_1` (connection_role VARCHAR (255))")
+ MultiConnectionTestModel.connection.execute("INSERT INTO test_1 VALUES ('writing')")
+
+ ActiveRecord::Base.connected_to(role: :reading) do
+ MultiConnectionTestModel.connection.execute("CREATE TABLE `test_1` (connection_role VARCHAR (255))")
+ MultiConnectionTestModel.connection.execute("INSERT INTO test_1 VALUES ('reading')")
+ end
+
+ read_latch = Concurrent::CountDownLatch.new
+ write_latch = Concurrent::CountDownLatch.new
+
+ MultiConnectionTestModel.connection
+
+ thread = Thread.new do
+ MultiConnectionTestModel.connection
+
+ write_latch.wait
+ assert_equal "writing", MultiConnectionTestModel.connection.select_value("SELECT connection_role from test_1")
+ read_latch.count_down
+ end
+
+ ActiveRecord::Base.connected_to(role: :reading) do
+ write_latch.count_down
+ assert_equal "reading", MultiConnectionTestModel.connection.select_value("SELECT connection_role from test_1")
+ read_latch.wait
+ end
+
+ thread.join
+ ensure
+ tf_reading.close
+ tf_reading.unlink
+ tf_writing.close
+ tf_writing.unlink
+ end
+
+ unless in_memory_db?
+ def test_establish_connection_using_3_levels_config
+ 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" }
+ }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ ActiveRecord::Base.connects_to(database: { writing: :primary, reading: :readonly })
+
+ assert_not_nil pool = ActiveRecord::Base.connection_handlers[:writing].retrieve_connection_pool("primary")
+ assert_equal "db/primary.sqlite3", pool.spec.config[:database]
+
+ assert_not_nil pool = ActiveRecord::Base.connection_handlers[:reading].retrieve_connection_pool("primary")
+ assert_equal "db/readonly.sqlite3", pool.spec.config[:database]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ActiveRecord::Base.establish_connection(:arunit)
+ ENV["RAILS_ENV"] = previous_env
+ end
+
+ def test_switching_connections_via_handler
+ 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" }
+ }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ ActiveRecord::Base.connects_to(database: { writing: :primary, reading: :readonly })
+
+ ActiveRecord::Base.connected_to(role: :reading) do
+ @ro_handler = ActiveRecord::Base.connection_handler
+ assert_equal ActiveRecord::Base.connection_handler, ActiveRecord::Base.connection_handlers[:reading]
+ assert_equal :reading, ActiveRecord::Base.current_role
+ assert ActiveRecord::Base.connected_to?(role: :reading)
+ assert_not ActiveRecord::Base.connected_to?(role: :writing)
+ end
+
+ ActiveRecord::Base.connected_to(role: :writing) do
+ assert_equal ActiveRecord::Base.connection_handler, ActiveRecord::Base.connection_handlers[:writing]
+ assert_not_equal @ro_handler, ActiveRecord::Base.connection_handler
+ assert_equal :writing, ActiveRecord::Base.current_role
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ assert_not ActiveRecord::Base.connected_to?(role: :reading)
+ end
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ActiveRecord::Base.establish_connection(:arunit)
+ ENV["RAILS_ENV"] = previous_env
+ end
+
+ def test_establish_connection_using_3_levels_config_with_non_default_handlers
+ 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" }
+ }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ ActiveRecord::Base.connects_to(database: { default: :primary, readonly: :readonly })
+
+ assert_not_nil pool = ActiveRecord::Base.connection_handlers[:default].retrieve_connection_pool("primary")
+ assert_equal "db/primary.sqlite3", pool.spec.config[:database]
+
+ assert_not_nil pool = ActiveRecord::Base.connection_handlers[:readonly].retrieve_connection_pool("primary")
+ assert_equal "db/readonly.sqlite3", pool.spec.config[:database]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ActiveRecord::Base.establish_connection(:arunit)
+ ENV["RAILS_ENV"] = previous_env
+ end
+
+ def test_switching_connections_with_database_url
+ previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
+ previous_url, ENV["DATABASE_URL"] = ENV["DATABASE_URL"], "postgres://localhost/foo"
+
+ ActiveRecord::Base.connected_to(database: { writing: "postgres://localhost/bar" }) 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({ adapter: "postgresql", database: "bar", host: "localhost" }, pool.spec.config)
+ end
+ ensure
+ ActiveRecord::Base.establish_connection(:arunit)
+ ENV["RAILS_ENV"] = previous_env
+ ENV["DATABASE_URL"] = previous_url
+ end
+
+ def test_switching_connections_with_database_config_hash
+ previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
+ config = { adapter: "sqlite3", database: "db/readonly.sqlite3" }
+
+ ActiveRecord::Base.connected_to(database: { writing: config }) 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, pool.spec.config)
+ end
+ ensure
+ ActiveRecord::Base.establish_connection(:arunit)
+ ENV["RAILS_ENV"] = previous_env
+ end
+
+ def test_switching_connections_with_database_and_role_raises
+ error = assert_raises(ArgumentError) do
+ ActiveRecord::Base.connected_to(database: :readonly, role: :writing) { }
+ end
+ assert_equal "connected_to can only accept a `database` or a `role` argument, but not both arguments.", error.message
+ end
+
+ def test_switching_connections_without_database_and_role_raises
+ error = assert_raises(ArgumentError) do
+ ActiveRecord::Base.connected_to { }
+ end
+ assert_equal "must provide a `database` or a `role`.", error.message
+ end
+
+ def test_switching_connections_with_database_symbol
+ 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" }
+ }
+ }
+ @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)
+
+ handler = ActiveRecord::Base.connection_handler
+ assert_equal handler, ActiveRecord::Base.connection_handlers[:readonly]
+
+ assert_not_nil pool = handler.retrieve_connection_pool("primary")
+ assert_equal(config["default_env"]["readonly"], pool.spec.config)
+ end
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ActiveRecord::Base.establish_connection(:arunit)
+ ENV["RAILS_ENV"] = previous_env
+ end
+
+ def test_connects_to_with_single_configuration
+ config = {
+ "development" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" },
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ ActiveRecord::Base.connects_to database: { writing: :development }
+
+ assert_equal 1, ActiveRecord::Base.connection_handlers.size
+ assert_equal ActiveRecord::Base.connection_handler, ActiveRecord::Base.connection_handlers[:writing]
+ assert_equal :writing, ActiveRecord::Base.current_role
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ActiveRecord::Base.establish_connection(:arunit)
+ end
+
+ def test_connects_to_using_top_level_key_in_two_level_config
+ config = {
+ "development" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" },
+ "development_readonly" => { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ ActiveRecord::Base.connects_to database: { writing: :development, reading: :development_readonly }
+
+ assert_not_nil pool = ActiveRecord::Base.connection_handlers[:reading].retrieve_connection_pool("primary")
+ assert_equal "db/readonly.sqlite3", pool.spec.config[:database]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ActiveRecord::Base.establish_connection(:arunit)
+ end
+
+ def test_connects_to_returns_array_of_established_connections
+ config = {
+ "development" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" },
+ "development_readonly" => { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ result = ActiveRecord::Base.connects_to database: { writing: :development, reading: :development_readonly }
+
+ assert_equal(
+ [
+ ActiveRecord::Base.connection_handlers[:writing].retrieve_connection_pool("primary"),
+ ActiveRecord::Base.connection_handlers[:reading].retrieve_connection_pool("primary")
+ ],
+ result
+ )
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ActiveRecord::Base.establish_connection(:arunit)
+ end
+ end
+
+ def test_connection_pools
+ assert_equal([@rw_pool], @handlers[:writing].connection_pools)
+ assert_equal([@ro_pool], @handlers[:reading].connection_pools)
+ end
+
+ def test_retrieve_connection
+ assert @rw_handler.retrieve_connection(@spec_name)
+ assert @ro_handler.retrieve_connection(@spec_name)
+ end
+
+ def test_active_connections?
+ assert_not_predicate @rw_handler, :active_connections?
+ assert_not_predicate @ro_handler, :active_connections?
+
+ assert @rw_handler.retrieve_connection(@spec_name)
+ assert @ro_handler.retrieve_connection(@spec_name)
+
+ assert_predicate @rw_handler, :active_connections?
+ assert_predicate @ro_handler, :active_connections?
+
+ @rw_handler.clear_active_connections!
+ assert_not_predicate @rw_handler, :active_connections?
+
+ @ro_handler.clear_active_connections!
+ assert_not_predicate @ro_handler, :active_connections?
+ end
+
+ def test_retrieve_connection_pool
+ assert_not_nil @rw_handler.retrieve_connection_pool(@spec_name)
+ assert_not_nil @ro_handler.retrieve_connection_pool(@spec_name)
+ end
+
+ def test_retrieve_connection_pool_with_invalid_id
+ assert_nil @rw_handler.retrieve_connection_pool("foo")
+ assert_nil @ro_handler.retrieve_connection_pool("foo")
+ end
+
+ def test_connection_handlers_are_per_thread_and_not_per_fiber
+ original_handlers = ActiveRecord::Base.connection_handlers
+
+ ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler, reading: ActiveRecord::ConnectionAdapters::ConnectionHandler.new }
+
+ reading_handler = ActiveRecord::Base.connection_handlers[:reading]
+
+ reading = ActiveRecord::Base.with_handler(:reading) do
+ Person.connection_handler
+ end
+
+ assert_not_equal reading, ActiveRecord::Base.connection_handler
+ assert_equal reading, reading_handler
+ ensure
+ ActiveRecord::Base.connection_handlers = original_handlers
+ end
+
+ def test_connection_handlers_swapping_connections_in_fiber
+ original_handlers = ActiveRecord::Base.connection_handlers
+
+ ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler, reading: ActiveRecord::ConnectionAdapters::ConnectionHandler.new }
+
+ reading_handler = ActiveRecord::Base.connection_handlers[:reading]
+
+ enum = Enumerator.new do |r|
+ r << ActiveRecord::Base.connection_handler
+ end
+
+ reading = ActiveRecord::Base.with_handler(:reading) do
+ enum.next
+ end
+
+ assert_equal reading, reading_handler
+ ensure
+ ActiveRecord::Base.connection_handlers = original_handlers
+ end
+
+ def test_calling_connected_to_on_a_non_existent_handler_raises
+ error = assert_raises ActiveRecord::ConnectionNotEstablished do
+ ActiveRecord::Base.connected_to(role: :reading) do
+ Person.first
+ end
+ end
+
+ assert_equal "No connection pool with 'primary' found for the 'reading' role.", error.message
+ end
+
+ def test_default_handlers_are_writing_and_reading
+ assert_equal :writing, ActiveRecord::Base.writing_role
+ assert_equal :reading, ActiveRecord::Base.reading_role
+ end
+
+ def test_an_application_can_change_the_default_handlers
+ old_writing = ActiveRecord::Base.writing_role
+ old_reading = ActiveRecord::Base.reading_role
+ ActiveRecord::Base.writing_role = :default
+ ActiveRecord::Base.reading_role = :readonly
+
+ assert_equal :default, ActiveRecord::Base.writing_role
+ assert_equal :readonly, ActiveRecord::Base.reading_role
+ ensure
+ ActiveRecord::Base.writing_role = old_writing
+ ActiveRecord::Base.reading_role = old_reading
+ end
+ end
+ end
+end
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 1b64324cc4..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
@@ -18,11 +18,14 @@ module ActiveRecord
end
def resolve_config(config)
- ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ configs.to_h
end
def resolve_spec(spec, config)
- ConnectionSpecification::Resolver.new(resolve_config(config)).resolve(spec)
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
+ resolver.resolve(spec, spec)
end
def test_resolver_with_database_uri_and_current_env_symbol_key
@@ -43,6 +46,14 @@ module ActiveRecord
assert_equal expected, actual
end
+ def test_resolver_with_nil_database_url_and_current_env
+ ENV["RAILS_ENV"] = "foo"
+ config = { "foo" => { "adapter" => "postgres", "url" => ENV["DATABASE_URL"] } }
+ actual = resolve_spec(:foo, config)
+ expected = { "adapter" => "postgres", "url" => nil, "name" => "foo" }
+ assert_equal expected, actual
+ end
+
def test_resolver_with_database_uri_and_current_env_symbol_key_and_rack_env
ENV["DATABASE_URL"] = "postgres://localhost/foo"
ENV["RACK_ENV"] = "foo"
@@ -61,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/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
index 02e76ce146..38331aa641 100644
--- a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
@@ -27,8 +27,12 @@ if current_adapter?(:Mysql2Adapter)
def test_string_types
assert_lookup_type :string, "enum('one', 'two', 'three')"
assert_lookup_type :string, "ENUM('one', 'two', 'three')"
+ assert_lookup_type :string, "enum ('one', 'two', 'three')"
+ assert_lookup_type :string, "ENUM ('one', 'two', 'three')"
assert_lookup_type :string, "set('one', 'two', 'three')"
assert_lookup_type :string, "SET('one', 'two', 'three')"
+ assert_lookup_type :string, "set ('one', 'two', 'three')"
+ assert_lookup_type :string, "SET ('one', 'two', 'three')"
end
def test_set_type_with_value_matching_other_type
diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
index 67496381d1..89a9c30f9b 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,30 @@ 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
+ 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 +116,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 +125,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
@@ -91,8 +139,23 @@ module ActiveRecord
@cache.clear_data_source_cache!("posts")
end
- private
+ test "#columns_hash? is populated by #columns_hash" do
+ assert_not @cache.columns_hash?("posts")
+
+ @cache.columns_hash("posts")
+ assert @cache.columns_hash?("posts")
+ end
+
+ test "#columns_hash? is not populated by #data_source_exists?" do
+ assert_not @cache.columns_hash?("posts")
+
+ @cache.data_source_exists?("posts")
+
+ assert_not @cache.columns_hash?("posts")
+ end
+
+ private
def schema_dump_path
"test/assets/schema_dump_5_1.yml"
end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index 0941ee3309..b9b5cc0e28 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -106,7 +106,7 @@ module ActiveRecord
def middleware(app)
lambda do |env|
a, b, c = executor.wrap { app.call(env) }
- [a, b, Rack::BodyProxy.new(c) {}]
+ [a, b, Rack::BodyProxy.new(c) { }]
end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 9ac03629c3..a15ad9a45b 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -91,7 +91,9 @@ module ActiveRecord
end
def test_full_pool_exception
+ @pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout
@pool.size.times { assert @pool.checkout }
+
assert_raises(ConnectionTimeoutError) do
@pool.checkout
end
@@ -109,6 +111,44 @@ module ActiveRecord
assert_equal connection, t.join.value
end
+ def test_full_pool_blocking_shares_load_interlock
+ @pool.instance_variable_set(:@size, 1)
+
+ load_interlock_latch = Concurrent::CountDownLatch.new
+ connection_latch = Concurrent::CountDownLatch.new
+
+ able_to_get_connection = false
+ able_to_load = false
+
+ thread_with_load_interlock = Thread.new do
+ ActiveSupport::Dependencies.interlock.running do
+ load_interlock_latch.count_down
+ connection_latch.wait
+
+ @pool.with_connection do
+ able_to_get_connection = true
+ end
+ end
+ end
+
+ thread_with_last_connection = Thread.new do
+ @pool.with_connection do
+ connection_latch.count_down
+ load_interlock_latch.wait
+
+ ActiveSupport::Dependencies.interlock.loading do
+ able_to_load = true
+ end
+ end
+ end
+
+ thread_with_load_interlock.join
+ thread_with_last_connection.join
+
+ assert able_to_get_connection
+ assert able_to_load
+ end
+
def test_removing_releases_latch
cs = @pool.size.times.map { @pool.checkout }
t = Thread.new { @pool.checkout }
@@ -156,6 +196,48 @@ module ActiveRecord
@pool.connections.each { |conn| conn.close if conn.in_use? }
end
+ def test_idle_timeout_configuration
+ @pool.disconnect!
+ spec = ActiveRecord::Base.connection_pool.spec
+ spec.config.merge!(idle_timeout: "0.02")
+ @pool = ConnectionPool.new(spec)
+ idle_conn = @pool.checkout
+ @pool.checkin(idle_conn)
+
+ idle_conn.instance_variable_set(
+ :@idle_since,
+ Concurrent.monotonic_time - 0.01
+ )
+
+ @pool.flush
+ assert_equal 1, @pool.connections.length
+
+ idle_conn.instance_variable_set(
+ :@idle_since,
+ Concurrent.monotonic_time - 0.02
+ )
+
+ @pool.flush
+ assert_equal 0, @pool.connections.length
+ end
+
+ def test_disable_flush
+ @pool.disconnect!
+ spec = ActiveRecord::Base.connection_pool.spec
+ spec.config.merge!(idle_timeout: -5)
+ @pool = ConnectionPool.new(spec)
+ idle_conn = @pool.checkout
+ @pool.checkin(idle_conn)
+
+ idle_conn.instance_variable_set(
+ :@idle_since,
+ Concurrent.monotonic_time - 1
+ )
+
+ @pool.flush
+ assert_equal 1, @pool.connections.length
+ end
+
def test_flush
idle_conn = @pool.checkout
recent_conn = @pool.checkout
@@ -166,9 +248,10 @@ module ActiveRecord
assert_equal 3, @pool.connections.length
- def idle_conn.seconds_idle
- 1000
- end
+ idle_conn.instance_variable_set(
+ :@idle_since,
+ Concurrent.monotonic_time - 1000
+ )
@pool.flush(30)
@@ -484,23 +567,21 @@ module ActiveRecord
def test_disconnect_and_clear_reloadable_connections_attempt_to_wait_for_threads_to_return_their_conns
[:disconnect, :disconnect!, :clear_reloadable_connections, :clear_reloadable_connections!].each do |group_action_method|
- begin
- thread = timed_join_result = nil
- @pool.with_connection do |connection|
- thread = Thread.new { @pool.send(group_action_method) }
-
- # give the other `thread` some time to get stuck in `group_action_method`
- timed_join_result = thread.join(0.3)
- # thread.join # => `nil` means the other thread hasn't finished running and is still waiting for us to
- # release our connection
- assert_nil timed_join_result
-
- # assert that since this is within default timeout our connection hasn't been forcefully taken away from us
- assert_predicate @pool, :active_connection?
- end
- ensure
- thread.join if thread && !timed_join_result # clean up the other thread
+ thread = timed_join_result = nil
+ @pool.with_connection do |connection|
+ thread = Thread.new { @pool.send(group_action_method) }
+
+ # give the other `thread` some time to get stuck in `group_action_method`
+ timed_join_result = thread.join(0.3)
+ # thread.join # => `nil` means the other thread hasn't finished running and is still waiting for us to
+ # release our connection
+ assert_nil timed_join_result
+
+ # assert that since this is within default timeout our connection hasn't been forcefully taken away from us
+ assert_predicate @pool, :active_connection?
end
+ ensure
+ thread.join if thread && !timed_join_result # clean up the other thread
end
end
@@ -578,7 +659,7 @@ module ActiveRecord
end
stuck_thread = Thread.new do
- pool.with_connection {}
+ pool.with_connection { }
end
# wait for stuck_thread to get in queue
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index 5b80f16a44..72be14f507 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -7,11 +7,15 @@ module ActiveRecord
class ConnectionSpecification
class ResolverTest < ActiveRecord::TestCase
def resolve(spec, config = {})
- Resolver.new(config).resolve(spec)
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
+ resolver.resolve(spec, spec)
end
def spec(spec, config = {})
- Resolver.new(config).spec(spec)
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
+ resolver.spec(spec)
end
def test_url_invalid_adapter
diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb
index 6e7ae2efb4..36e3d543cd 100644
--- a/activerecord/test/cases/core_test.rb
+++ b/activerecord/test/cases/core_test.rb
@@ -30,13 +30,18 @@ class CoreTest < ActiveRecord::TestCase
assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.all.merge!(select: "id, title", where: "id = 1").first.inspect
end
+ def test_inspect_instance_with_non_primary_key_id_attribute
+ topic = topics(:first).becomes(TitlePrimaryKeyTopic)
+ assert_match(/id: 1/, topic.inspect)
+ end
+
def test_inspect_class_without_table
assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect
end
def test_pretty_print_new
topic = Topic.new
- actual = "".dup
+ actual = +""
PP.pp(topic, StringIO.new(actual))
expected = <<~PRETTY
#<Topic:0xXXXXXX
@@ -65,7 +70,7 @@ class CoreTest < ActiveRecord::TestCase
def test_pretty_print_persisted
topic = topics(:first)
- actual = "".dup
+ actual = +""
PP.pp(topic, StringIO.new(actual))
expected = <<~PRETTY
#<Topic:0x\\w+
@@ -93,7 +98,7 @@ class CoreTest < ActiveRecord::TestCase
def test_pretty_print_uninitialized
topic = Topic.allocate
- actual = "".dup
+ actual = +""
PP.pp(topic, StringIO.new(actual))
expected = "#<Topic:XXXXXX not initialized>\n"
assert actual.start_with?(expected.split("XXXXXX").first)
@@ -106,8 +111,15 @@ class CoreTest < ActiveRecord::TestCase
"inspecting topic"
end
end
- actual = "".dup
+ actual = +""
PP.pp(subtopic.new, StringIO.new(actual))
assert_equal "inspecting topic\n", actual
end
+
+ def test_pretty_print_with_non_primary_key_id_attribute
+ topic = topics(:first).becomes(TitlePrimaryKeyTopic)
+ actual = +""
+ PP.pp(topic, StringIO.new(actual))
+ assert_match(/id: 1/, actual)
+ end
end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 99d286dc52..cc4f86a0fb 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -144,7 +144,7 @@ class CounterCacheTest < ActiveRecord::TestCase
test "update other counters on parent destroy" do
david, joanna = dog_lovers(:david, :joanna)
- joanna = joanna # squelch a warning
+ _ = joanna # squelch a warning
assert_difference "joanna.reload.dogs_count", -1 do
david.destroy
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
new file mode 100644
index 0000000000..fd02d2acb4
--- /dev/null
+++ b/activerecord/test/cases/database_selector_test.rb
@@ -0,0 +1,166 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/person"
+require "action_dispatch"
+
+module ActiveRecord
+ class DatabaseSelectorTest < ActiveRecord::TestCase
+ setup do
+ @session_store = {}
+ @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
+
+ def test_writing_the_session_timestamps
+ assert @session.update_last_write_timestamp
+
+ session2 = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session.new(@session_store)
+ assert_equal @session.last_write_timestamp, session2.last_write_timestamp
+ end
+
+ def test_writing_session_time_changes
+ assert @session.update_last_write_timestamp
+
+ before = @session.last_write_timestamp
+ sleep(0.1)
+
+ assert @session.update_last_write_timestamp
+ assert_not_equal before, @session.last_write_timestamp
+ end
+
+ def test_read_from_replicas
+ @session_store[:last_write] = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session.convert_time_to_timestamp(Time.now - 5.seconds)
+
+ resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver.new(@session)
+
+ called = false
+ resolver.read do
+ called = true
+ assert ActiveRecord::Base.connected_to?(role: :reading)
+ end
+ assert called
+ end
+
+ def test_read_from_primary
+ @session_store[:last_write] = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session.convert_time_to_timestamp(Time.now)
+
+ resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver.new(@session)
+
+ called = false
+ resolver.read do
+ called = true
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ end
+ assert called
+ end
+
+ def test_write_to_primary
+ resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver.new(@session)
+
+ # Session should start empty
+ assert_nil @session_store[:last_write]
+
+ called = false
+ resolver.write do
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ called = true
+ end
+ assert called
+
+ # and be populated by the last write time
+ assert @session_store[:last_write]
+ end
+
+ def test_write_to_primary_with_exception
+ resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver.new(@session)
+
+ # Session should start empty
+ assert_nil @session_store[:last_write]
+
+ called = false
+ assert_raises(ActiveRecord::RecordNotFound) do
+ resolver.write do
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ called = true
+ raise ActiveRecord::RecordNotFound
+ end
+ end
+ assert called
+
+ # and be populated by the last write time
+ assert @session_store[:last_write]
+ end
+
+ def test_read_from_primary_with_options
+ resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver.new(@session, delay: 5.seconds)
+
+ # Session should start empty
+ assert_nil @session_store[:last_write]
+
+ called = false
+ resolver.write do
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ called = true
+ end
+ assert called
+
+ # and be populated by the last write time
+ assert @session_store[:last_write]
+
+ read = false
+ resolver.read do
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ read = true
+ end
+ assert read
+ end
+
+ def test_read_from_replica_with_no_delay
+ resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver.new(@session, delay: 0.seconds)
+
+ # Session should start empty
+ assert_nil @session_store[:last_write]
+
+ called = false
+ resolver.write do
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ called = true
+ end
+ assert called
+
+ # and be populated by the last write time
+ assert @session_store[:last_write]
+
+ read = false
+ resolver.read do
+ assert ActiveRecord::Base.connected_to?(role: :reading)
+ read = true
+ end
+ assert read
+ end
+
+ def test_the_middleware_chooses_writing_role_with_POST_request
+ middleware = ActiveRecord::Middleware::DatabaseSelector.new(lambda { |env|
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ [200, {}, ["body"]]
+ })
+ assert_equal [200, {}, ["body"]], middleware.call("REQUEST_METHOD" => "POST")
+ end
+
+ def test_the_middleware_chooses_reading_role_with_GET_request
+ middleware = ActiveRecord::Middleware::DatabaseSelector.new(lambda { |env|
+ assert ActiveRecord::Base.connected_to?(role: :reading)
+ [200, {}, ["body"]]
+ })
+ assert_equal [200, {}, ["body"]], middleware.call("REQUEST_METHOD" => "GET")
+ end
+ end
+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 0f957d41cf..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
@@ -106,6 +106,13 @@ if current_adapter?(:Mysql2Adapter)
class MysqlDefaultExpressionTest < ActiveRecord::TestCase
include SchemaDumpingHelper
+ if supports_default_expression?
+ test "schema dump includes default expression" do
+ output = dump_table_schema("defaults")
+ assert_match %r/t\.binary\s+"uuid",\s+limit: 36,\s+default: -> { "\(uuid\(\)\)" }/i, output
+ end
+ end
+
if subsecond_precision_supported?
test "schema dump datetime includes default expression" do
output = dump_table_schema("datetime_defaults")
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 83cc2aa319..a2a501a794 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -336,7 +336,7 @@ class DirtyTest < ActiveRecord::TestCase
end
with_partial_writes Pirate, true do
- assert_queries(0) { 2.times { pirate.save! } }
+ assert_no_queries { 2.times { pirate.save! } }
assert_equal old_updated_on, pirate.reload.updated_on
assert_queries(1) { pirate.catchphrase = "bar"; pirate.save! }
@@ -352,10 +352,10 @@ 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_queries(0) { 2.times { person.save! } }
+ assert_no_queries { 2.times { person.save! } }
assert_equal old_lock_version, person.reload.lock_version
assert_queries(1) { person.first_name = "bar"; person.save! }
@@ -567,8 +567,6 @@ class DirtyTest < ActiveRecord::TestCase
assert_not_nil pirate.previous_changes["updated_on"][1]
assert_not pirate.previous_changes.key?("parrot_id")
assert_not pirate.previous_changes.key?("created_on")
- ensure
- travel_back
end
class Testings < ActiveRecord::Base; end
@@ -879,6 +877,26 @@ class DirtyTest < ActiveRecord::TestCase
raise "changed? should be false" if changed?
raise "has_changes_to_save? should be false" if has_changes_to_save?
raise "saved_changes? should be true" unless saved_changes?
+ raise "id_in_database should not be nil" if id_in_database.nil?
+ end
+ end
+
+ person = klass.create!(first_name: "Sean")
+ assert_not_predicate person, :changed?
+ end
+
+ test "changed? in around callbacks after yield returns false" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "people"
+
+ around_create :check_around
+
+ def check_around
+ yield
+ raise "changed? should be false" if changed?
+ raise "has_changes_to_save? should be false" if has_changes_to_save?
+ raise "saved_changes? should be true" unless saved_changes?
+ raise "id_in_database should not be nil" if id_in_database.nil?
end
end
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index d5a1d11e12..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]
@@ -265,6 +270,35 @@ class EnumTest < ActiveRecord::TestCase
assert_equal "published", @book.status
end
+ test "invalid definition values raise an ArgumentError" do
+ e = assert_raises(ArgumentError) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: [proposed: 1, written: 2, published: 3]
+ end
+ end
+
+ assert_match(/must be either a hash, an array of symbols, or an array of strings./, e.message)
+
+ e = assert_raises(ArgumentError) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: { "" => 1, "active" => 2 }
+ end
+ end
+
+ assert_match(/Enum label name must not be blank/, e.message)
+
+ e = assert_raises(ArgumentError) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: ["active", ""]
+ end
+ end
+
+ assert_match(/Enum label name must not be blank/, e.message)
+ end
+
test "reserved enum names" do
klass = Class.new(ActiveRecord::Base) do
self.table_name = "books"
@@ -409,6 +443,20 @@ class EnumTest < ActiveRecord::TestCase
assert_equal ["drafted", "uploaded"], book2.status_change
end
+ test "attempting to modify enum raises error" do
+ e = assert_raises(RuntimeError) do
+ Book.statuses["bad_enum"] = 40
+ end
+
+ assert_match(/can't modify frozen/, e.message)
+
+ e = assert_raises(RuntimeError) do
+ Book.statuses.delete("published")
+ end
+
+ assert_match(/can't modify frozen/, e.message)
+ end
+
test "declare multiple enums at a time" do
klass = Class.new(ActiveRecord::Base) do
self.table_name = "books"
@@ -508,4 +556,13 @@ class EnumTest < ActiveRecord::TestCase
test "data type of Enum type" do
assert_equal :integer, Book.type_for_attribute("status").type
end
+
+ test "scopes can be disabled" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: [:proposed, :written], _scopes: false
+ end
+
+ assert_raises(NoMethodError) { klass.proposed }
+ end
end
diff --git a/activerecord/test/cases/errors_test.rb b/activerecord/test/cases/errors_test.rb
index b90e6a66c5..0d2be944b5 100644
--- a/activerecord/test/cases/errors_test.rb
+++ b/activerecord/test/cases/errors_test.rb
@@ -8,11 +8,9 @@ class ErrorsTest < ActiveRecord::TestCase
error_klasses = ObjectSpace.each_object(Class).select { |klass| klass < base }
(error_klasses - [ActiveRecord::AmbiguousSourceReflectionForThroughAssociation]).each do |error_klass|
- begin
- error_klass.new.inspect
- rescue ArgumentError
- raise "Instance of #{error_klass} can't be initialized with no arguments"
- end
+ error_klass.new.inspect
+ rescue ArgumentError
+ raise "Instance of #{error_klass} can't be initialized with no arguments"
end
end
end
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index 82cc891970..79a0630193 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -40,7 +40,7 @@ if ActiveRecord::Base.connection.supports_explain?
assert_equal binds, queries[0][1]
end
- def test_collects_nothing_if_the_statement_is_not_whitelisted
+ def test_collects_nothing_if_the_statement_is_not_explainable
SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "SHOW max_identifier_length")
assert_empty queries
end
diff --git a/activerecord/test/cases/filter_attributes_test.rb b/activerecord/test/cases/filter_attributes_test.rb
new file mode 100644
index 0000000000..2f4c9b0ef7
--- /dev/null
+++ b/activerecord/test/cases/filter_attributes_test.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/admin"
+require "models/admin/user"
+require "models/admin/account"
+require "models/user"
+require "pp"
+
+class FilterAttributesTest < ActiveRecord::TestCase
+ fixtures :"admin/users", :"admin/accounts"
+
+ setup do
+ @previous_filter_attributes = ActiveRecord::Base.filter_attributes
+ ActiveRecord::Base.filter_attributes = [:name]
+ end
+
+ teardown do
+ ActiveRecord::Base.filter_attributes = @previous_filter_attributes
+ end
+
+ test "filter_attributes" do
+ Admin::User.all.each do |user|
+ assert_includes user.inspect, "name: [FILTERED]"
+ assert_equal 1, user.inspect.scan("[FILTERED]").length
+ end
+
+ Admin::Account.all.each do |account|
+ assert_includes account.inspect, "name: [FILTERED]"
+ assert_equal 1, account.inspect.scan("[FILTERED]").length
+ end
+ end
+
+ test "string filter_attributes perform pertial match" do
+ ActiveRecord::Base.filter_attributes = ["n"]
+ Admin::Account.all.each do |account|
+ assert_includes account.inspect, "name: [FILTERED]"
+ assert_equal 1, account.inspect.scan("[FILTERED]").length
+ end
+ end
+
+ test "regex filter_attributes are accepted" do
+ ActiveRecord::Base.filter_attributes = [/\An\z/]
+ account = Admin::Account.find_by(name: "37signals")
+ assert_includes account.inspect, 'name: "37signals"'
+ assert_equal 0, account.inspect.scan("[FILTERED]").length
+
+ ActiveRecord::Base.filter_attributes = [/\An/]
+ account = Admin::Account.find_by(name: "37signals")
+ assert_includes account.reload.inspect, "name: [FILTERED]"
+ assert_equal 1, account.inspect.scan("[FILTERED]").length
+ end
+
+ test "proc filter_attributes are accepted" do
+ ActiveRecord::Base.filter_attributes = [ lambda { |key, value| value.reverse! if key == "name" } ]
+ account = Admin::Account.find_by(name: "37signals")
+ assert_includes account.inspect, 'name: "slangis73"'
+ end
+
+ test "filter_attributes could be overwritten by models" do
+ Admin::Account.all.each do |account|
+ assert_includes account.inspect, "name: [FILTERED]"
+ assert_equal 1, account.inspect.scan("[FILTERED]").length
+ end
+
+ begin
+ Admin::Account.filter_attributes = []
+
+ # Above changes should not impact other models
+ Admin::User.all.each do |user|
+ assert_includes user.inspect, "name: [FILTERED]"
+ assert_equal 1, user.inspect.scan("[FILTERED]").length
+ end
+
+ Admin::Account.all.each do |account|
+ assert_not_includes account.inspect, "name: [FILTERED]"
+ assert_equal 0, account.inspect.scan("[FILTERED]").length
+ end
+ ensure
+ Admin::Account.remove_instance_variable(:@filter_attributes)
+ end
+ end
+
+ test "filter_attributes should not filter nil value" do
+ account = Admin::Account.new
+
+ assert_includes account.inspect, "name: nil"
+ assert_not_includes account.inspect, "name: [FILTERED]"
+ assert_equal 0, account.inspect.scan("[FILTERED]").length
+ end
+
+ test "filter_attributes should handle [FILTERED] value properly" do
+ User.filter_attributes = ["auth"]
+ user = User.new(token: "[FILTERED]", auth_token: "[FILTERED]")
+
+ assert_includes user.inspect, "auth_token: [FILTERED]"
+ assert_includes user.inspect, 'token: "[FILTERED]"'
+ ensure
+ User.remove_instance_variable(:@filter_attributes)
+ end
+
+ test "filter_attributes on pretty_print" do
+ user = admin_users(:david)
+ actual = "".dup
+ PP.pp(user, StringIO.new(actual))
+
+ assert_includes actual, "name: [FILTERED]"
+ assert_equal 1, actual.scan("[FILTERED]").length
+ end
+
+ test "filter_attributes on pretty_print should not filter nil value" do
+ user = Admin::User.new
+ actual = "".dup
+ PP.pp(user, StringIO.new(actual))
+
+ assert_includes actual, "name: nil"
+ assert_not_includes actual, "name: [FILTERED]"
+ assert_equal 0, actual.scan("[FILTERED]").length
+ end
+
+ test "filter_attributes on pretty_print should handle [FILTERED] value properly" do
+ User.filter_attributes = ["auth"]
+ user = User.new(token: "[FILTERED]", auth_token: "[FILTERED]")
+ actual = "".dup
+ PP.pp(user, StringIO.new(actual))
+
+ assert_includes actual, "auth_token: [FILTERED]"
+ assert_includes actual, 'token: "[FILTERED]"'
+ ensure
+ User.remove_instance_variable(:@filter_attributes)
+ end
+end
diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb
index 59af4e6961..66413a98e4 100644
--- a/activerecord/test/cases/finder_respond_to_test.rb
+++ b/activerecord/test/cases/finder_respond_to_test.rb
@@ -12,10 +12,10 @@ class FinderRespondToTest < ActiveRecord::TestCase
end
def test_should_preserve_normal_respond_to_behaviour_and_respond_to_newly_added_method
- class << Topic; self; end.send(:define_method, :method_added_for_finder_respond_to_test) {}
+ Topic.singleton_class.define_method(:method_added_for_finder_respond_to_test) { }
assert_respond_to Topic, :method_added_for_finder_respond_to_test
ensure
- class << Topic; self; end.send(:remove_method, :method_added_for_finder_respond_to_test)
+ Topic.singleton_class.remove_method :method_added_for_finder_respond_to_test
end
def test_should_preserve_normal_respond_to_behaviour_and_respond_to_standard_object_method
@@ -56,6 +56,6 @@ class FinderRespondToTest < ActiveRecord::TestCase
private
def ensure_topic_method_is_not_cached(method_id)
- class << Topic; self; end.send(:remove_method, method_id) if Topic.public_methods.include? method_id
+ Topic.singleton_class.remove_method method_id if Topic.public_methods.include? method_id
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 2f256dd36a..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"
@@ -20,6 +21,8 @@ require "models/matey"
require "models/dog"
require "models/car"
require "models/tyre"
+require "models/subscriber"
+require "support/stubs/strong_parameters"
class FinderTest < ActiveRecord::TestCase
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :author_addresses, :customers, :categories, :categorizations, :cars
@@ -167,6 +170,7 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.exists?(id: [1, 9999])
assert_equal false, Topic.exists?(45)
+ assert_equal false, Topic.exists?(9999999999999999999999999999999)
assert_equal false, Topic.exists?(Topic.new.id)
assert_raise(NoMethodError) { Topic.exists?([1, 2]) }
@@ -211,17 +215,35 @@ class FinderTest < ActiveRecord::TestCase
assert_equal false, relation.exists?(false)
end
+ def test_exists_with_string
+ assert_equal false, Subscriber.exists?("foo")
+ assert_equal false, Subscriber.exists?(" ")
+
+ Subscriber.create!(id: "foo")
+ Subscriber.create!(id: " ")
+
+ assert_equal true, Subscriber.exists?("foo")
+ assert_equal true, Subscriber.exists?(" ")
+ end
+
+ def test_exists_with_strong_parameters
+ assert_equal false, Subscriber.exists?(ProtectedParams.new(nick: "foo").permit!)
+
+ Subscriber.create!(nick: "foo")
+
+ assert_equal true, Subscriber.exists?(ProtectedParams.new(nick: "foo").permit!)
+
+ assert_raises(ActiveModel::ForbiddenAttributesError) do
+ Subscriber.exists?(ProtectedParams.new(nick: "foo"))
+ end
+ end
+
def test_exists_passing_active_record_object_is_not_permitted
assert_raises(ArgumentError) do
Topic.exists?(Topic.new)
end
end
- def test_exists_returns_false_when_parameter_has_invalid_type
- assert_equal false, Topic.exists?("foo")
- assert_equal false, Topic.exists?(("9" * 53).to_i) # number that's bigger than int
- end
-
def test_exists_does_not_select_columns_without_alias
assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do
Topic.exists?
@@ -246,6 +268,20 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.first.replies.exists?
end
+ def test_exists_with_empty_hash_arg
+ assert_equal true, Topic.exists?({})
+ end
+
+ def test_exists_with_distinct_and_offset_and_joins
+ assert Post.left_joins(:comments).distinct.offset(10).exists?
+ assert_not Post.left_joins(:comments).distinct.offset(11).exists?
+ end
+
+ def test_exists_with_distinct_and_offset_and_select
+ assert Post.select(:body).distinct.offset(3).exists?
+ assert_not Post.select(:body).distinct.offset(4).exists?
+ end
+
# Ensure +exists?+ runs without an error by excluding distinct value.
# See https://github.com/rails/rails/pull/26981.
def test_exists_with_order_and_distinct
@@ -257,6 +293,17 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.order(Arel.sql("invalid sql here")).exists?
end
+ def test_exists_with_large_number
+ assert_equal true, Topic.where(id: [1, 9223372036854775808]).exists?
+ assert_equal true, Topic.where(id: 1..9223372036854775808).exists?
+ assert_equal true, Topic.where(id: -9223372036854775809..9223372036854775808).exists?
+ assert_equal false, Topic.where(id: 9223372036854775808..9223372036854775809).exists?
+ assert_equal false, Topic.where(id: -9223372036854775810..-9223372036854775809).exists?
+ assert_equal false, Topic.where(id: 9223372036854775808..1).exists?
+ assert_equal true, Topic.where(id: 1).or(Topic.where(id: 9223372036854775808)).exists?
+ assert_equal true, Topic.where.not(id: 9223372036854775808).exists?
+ end
+
def test_exists_with_joins
assert_equal true, Topic.joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
end
@@ -358,20 +405,26 @@ class FinderTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotFound) do
Topic.where("1=1").find(9999999999999999999999999999999)
end
+ assert_equal topics(:first), Topic.where(id: [1, 9999999999999999999999999999999]).find(1)
end
def test_find_by_on_relation_with_large_number
assert_nil Topic.where("1=1").find_by(id: 9999999999999999999999999999999)
+ assert_equal topics(:first), Topic.where(id: [1, 9999999999999999999999999999999]).find_by(id: 1)
end
def test_find_by_bang_on_relation_with_large_number
assert_raises(ActiveRecord::RecordNotFound) do
Topic.where("1=1").find_by!(id: 9999999999999999999999999999999)
end
+ assert_equal topics(:first), Topic.where(id: [1, 9999999999999999999999999999999]).find_by!(id: 1)
end
def test_find_an_empty_array
- assert_equal [], Topic.find([])
+ empty_array = []
+ result = Topic.find(empty_array)
+ assert_equal [], result
+ assert_not_same empty_array, result
end
def test_find_doesnt_have_implicit_ordering
@@ -409,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
@@ -463,6 +516,7 @@ class FinderTest < ActiveRecord::TestCase
expected = topics(:first)
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.first
+ assert_equal expected, Topic.limit(5).first
end
def test_model_class_responds_to_first_bang
@@ -485,6 +539,7 @@ class FinderTest < ActiveRecord::TestCase
expected = topics(:second)
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.second
+ assert_equal expected, Topic.limit(5).second
end
def test_model_class_responds_to_second_bang
@@ -507,6 +562,7 @@ class FinderTest < ActiveRecord::TestCase
expected = topics(:third)
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.third
+ assert_equal expected, Topic.limit(5).third
end
def test_model_class_responds_to_third_bang
@@ -529,6 +585,7 @@ class FinderTest < ActiveRecord::TestCase
expected = topics(:fourth)
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.fourth
+ assert_equal expected, Topic.limit(5).fourth
end
def test_model_class_responds_to_fourth_bang
@@ -551,6 +608,7 @@ class FinderTest < ActiveRecord::TestCase
expected = topics(:fifth)
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.fifth
+ assert_equal expected, Topic.limit(5).fifth
end
def test_model_class_responds_to_fifth_bang
@@ -713,6 +771,24 @@ class FinderTest < ActiveRecord::TestCase
assert_equal comments.limit(2).to_a.first(3), comments.limit(2).first(3)
end
+ def test_first_have_determined_order_by_default
+ expected = [companies(:second_client), companies(:another_client)]
+ clients = Client.where(name: expected.map(&:name))
+
+ assert_equal expected, clients.first(2)
+ assert_equal expected, clients.limit(5).first(2)
+ end
+
+ def test_implicit_order_column_is_configurable
+ old_implicit_order_column = Topic.implicit_order_column
+ Topic.implicit_order_column = "title"
+
+ assert_equal topics(:fifth), Topic.first
+ assert_equal topics(:third), Topic.last
+ ensure
+ Topic.implicit_order_column = old_implicit_order_column
+ end
+
def test_take_and_first_and_last_with_integer_should_return_an_array
assert_kind_of Array, Topic.take(5)
assert_kind_of Array, Topic.first(5)
@@ -900,6 +976,7 @@ class FinderTest < ActiveRecord::TestCase
assert_kind_of Money, zaphod_balance
found_customers = Customer.where(balance: [david_balance, zaphod_balance])
assert_equal [customers(:david), customers(:zaphod)], found_customers.sort_by(&:id)
+ assert_equal Customer.where(balance: [david_balance.amount, zaphod_balance.amount]).to_sql, found_customers.to_sql
end
def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate
@@ -937,6 +1014,24 @@ class FinderTest < ActiveRecord::TestCase
assert_equal customers(:david), found_customer
end
+ def test_hash_condition_find_nil_with_aggregate_having_one_mapping
+ assert_nil customers(:zaphod).gps_location
+ found_customer = Customer.where(gps_location: nil, name: customers(:zaphod).name).first
+ assert_equal customers(:zaphod), found_customer
+ end
+
+ def test_hash_condition_find_nil_with_aggregate_having_multiple_mappings
+ customers(:david).update(address: nil)
+ assert_nil customers(:david).address_street
+ assert_nil customers(:david).address_city
+ found_customer = Customer.where(address: nil, name: customers(:david).name).first
+ assert_equal customers(:david), found_customer
+ end
+
+ def test_hash_condition_find_empty_array_with_aggregate_having_multiple_mappings
+ assert_nil Customer.where(address: []).first
+ end
+
def test_condition_utc_time_interpolation_with_default_timezone_local
with_env_tz "America/New_York" do
with_timezone_config default: :local do
@@ -1078,7 +1173,7 @@ class FinderTest < ActiveRecord::TestCase
def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
# ensure this test can run independently of order
- class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.include?(:find_by_credit_limit)
+ Account.singleton_class.remove_method :find_by_credit_limit if Account.public_methods.include?(:find_by_credit_limit)
a = Account.where("firm_id = ?", 6).find_by_credit_limit(50)
assert_equal a, Account.where("firm_id = ?", 6).find_by_credit_limit(50) # find_by_credit_limit has been cached
end
@@ -1265,21 +1360,6 @@ class FinderTest < ActiveRecord::TestCase
end
end
- def test_first_and_last_with_limit_for_order_without_primary_key
- # While Topic.first should impose an ordering by primary key,
- # Topic.limit(n).first should not
-
- Topic.first.touch # PostgreSQL changes the default order if no order clause is used
-
- assert_equal Topic.limit(1).to_a.first, Topic.limit(1).first
- assert_equal Topic.limit(2).to_a.first, Topic.limit(2).first
- assert_equal Topic.limit(2).to_a.first(2), Topic.limit(2).first(2)
-
- assert_equal Topic.limit(1).to_a.last, Topic.limit(1).last
- assert_equal Topic.limit(2).to_a.last, Topic.limit(2).last
- assert_equal Topic.limit(2).to_a.last(2), Topic.limit(2).last(2)
- end
-
def test_finder_with_offset_string
assert_nothing_raised { Topic.offset("3").to_a }
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index c65523d8c1..0cb868da6e 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -73,14 +73,12 @@ class FixturesTest < ActiveRecord::TestCase
if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
def test_bulk_insert
- begin
- subscriber = InsertQuerySubscriber.new
- subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber)
- create_fixtures("bulbs")
- assert_equal 1, subscriber.events.size, "It takes one INSERT query to insert two fixtures"
- ensure
- ActiveSupport::Notifications.unsubscribe(subscription)
- end
+ subscriber = InsertQuerySubscriber.new
+ subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber)
+ create_fixtures("bulbs")
+ assert_equal 1, subscriber.events.size, "It takes one INSERT query to insert two fixtures"
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscription)
end
def test_bulk_insert_multiple_table_with_a_multi_statement_query
@@ -211,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] },
@@ -303,20 +301,6 @@ class FixturesTest < ActiveRecord::TestCase
assert_equal fixtures, result.to_a
end
- def test_deprecated_insert_fixtures
- fixtures = [
- { "name" => "first", "wheels_count" => 2 },
- { "name" => "second", "wheels_count" => 3 }
- ]
- conn = ActiveRecord::Base.connection
- conn.delete("DELETE FROM aircraft")
- assert_deprecated do
- conn.insert_fixtures(fixtures, "aircraft")
- end
- result = conn.select_all("SELECT name, wheels_count FROM aircraft ORDER BY id")
- assert_equal fixtures, result.to_a
- end
-
def test_broken_yaml_exception
badyaml = Tempfile.new ["foo", ".yml"]
badyaml.write "a: : "
@@ -473,11 +457,11 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_empty_yaml_fixture
- assert_not_nil ActiveRecord::FixtureSet.new(Account.connection, "accounts", Account, FIXTURES_ROOT + "/naked/yml/accounts")
+ assert_not_nil ActiveRecord::FixtureSet.new(nil, "accounts", Account, FIXTURES_ROOT + "/naked/yml/accounts")
end
def test_empty_yaml_fixture_with_a_comment_in_it
- assert_not_nil ActiveRecord::FixtureSet.new(Account.connection, "companies", Company, FIXTURES_ROOT + "/naked/yml/companies")
+ assert_not_nil ActiveRecord::FixtureSet.new(nil, "companies", Company, FIXTURES_ROOT + "/naked/yml/companies")
end
def test_nonexistent_fixture_file
@@ -487,14 +471,14 @@ class FixturesTest < ActiveRecord::TestCase
assert_empty Dir[nonexistent_fixture_path + "*"]
assert_raise(Errno::ENOENT) do
- ActiveRecord::FixtureSet.new(Account.connection, "companies", Company, nonexistent_fixture_path)
+ ActiveRecord::FixtureSet.new(nil, "companies", Company, nonexistent_fixture_path)
end
end
def test_dirty_dirty_yaml_file
fixture_path = FIXTURES_ROOT + "/naked/yml/courses"
error = assert_raise(ActiveRecord::Fixture::FormatError) do
- ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, fixture_path)
+ ActiveRecord::FixtureSet.new(nil, "courses", Course, fixture_path)
end
assert_equal "fixture is not a hash: #{fixture_path}.yml", error.to_s
end
@@ -502,7 +486,7 @@ class FixturesTest < ActiveRecord::TestCase
def test_yaml_file_with_one_invalid_fixture
fixture_path = FIXTURES_ROOT + "/naked/yml/courses_with_invalid_key"
error = assert_raise(ActiveRecord::Fixture::FormatError) do
- ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, fixture_path)
+ ActiveRecord::FixtureSet.new(nil, "courses", Course, fixture_path)
end
assert_equal "fixture key is not a hash: #{fixture_path}.yml, keys: [\"two\"]", error.to_s
end
@@ -512,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
@@ -525,7 +505,7 @@ class FixturesTest < ActiveRecord::TestCase
def test_omap_fixtures
assert_nothing_raised do
- fixtures = ActiveRecord::FixtureSet.new(Account.connection, "categories", Category, FIXTURES_ROOT + "/categories_ordered")
+ fixtures = ActiveRecord::FixtureSet.new(nil, "categories", Category, FIXTURES_ROOT + "/categories_ordered")
fixtures.each.with_index do |(name, fixture), i|
assert_equal "fixture_no_#{i}", name
@@ -596,7 +576,7 @@ class HasManyThroughFixture < ActiveRecord::TestCase
parrots = File.join FIXTURES_ROOT, "parrots"
- fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots
+ fs = ActiveRecord::FixtureSet.new(nil, "parrots", parrot, parrots)
rows = fs.table_rows
assert_equal load_has_and_belongs_to_many["parrots_treasures"], rows["parrots_treasures"]
end
@@ -614,18 +594,22 @@ class HasManyThroughFixture < ActiveRecord::TestCase
parrots = File.join FIXTURES_ROOT, "parrots"
- fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots
+ fs = ActiveRecord::FixtureSet.new(nil, "parrots", parrot, parrots)
rows = fs.table_rows
assert_equal load_has_and_belongs_to_many["parrots_treasures"], rows["parrot_treasures"]
end
+ def test_has_and_belongs_to_many_order
+ assert_equal ["parrots", "parrots_treasures"], load_has_and_belongs_to_many.keys
+ end
+
def load_has_and_belongs_to_many
parrot = make_model "Parrot"
parrot.has_and_belongs_to_many :treasures
parrots = File.join FIXTURES_ROOT, "parrots"
- fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots
+ fs = ActiveRecord::FixtureSet.new(nil, "parrots", parrot, parrots)
fs.table_rows
end
end
@@ -936,7 +920,7 @@ class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase
def lock_thread=(lock_thread); end
end.new
- assert_called_with(connection, :begin_transaction, [joinable: false]) do
+ assert_called_with(connection, :begin_transaction, [joinable: false, _lazy: false]) do
fire_connection_notification(connection)
end
end
@@ -974,7 +958,7 @@ class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase
connection_id: connection.object_id
}
- message_bus.instrument("!connection.active_record", payload) {}
+ message_bus.instrument("!connection.active_record", payload) { }
end
end
end
@@ -1344,3 +1328,53 @@ class SameNameDifferentDatabaseFixturesTest < ActiveRecord::TestCase
assert_kind_of OtherDog, other_dogs(:lassie)
end
end
+
+class NilFixturePathTest < ActiveRecord::TestCase
+ test "raises an error when all fixtures loaded" do
+ error = assert_raises(StandardError) do
+ TestCase = Class.new(ActiveRecord::TestCase)
+ TestCase.class_eval do
+ self.fixture_path = nil
+ fixtures :all
+ end
+ end
+ assert_equal <<~MSG.squish, error.message
+ No fixture path found.
+ Please set `NilFixturePathTest::TestCase.fixture_path`.
+ MSG
+ end
+end
+
+class MultipleDatabaseFixturesTest < ActiveRecord::TestCase
+ test "enlist_fixture_connections ensures multiple databases share a connection pool" do
+ with_temporary_connection_pool do
+ ActiveRecord::Base.connects_to database: { writing: :arunit, reading: :arunit2 }
+
+ rw_conn = ActiveRecord::Base.connection
+ ro_conn = ActiveRecord::Base.connection_handlers[:reading].connection_pool_list.first.connection
+
+ assert_not_equal rw_conn, ro_conn
+
+ enlist_fixture_connections
+
+ rw_conn = ActiveRecord::Base.connection
+ ro_conn = ActiveRecord::Base.connection_handlers[:reading].connection_pool_list.first.connection
+
+ assert_equal rw_conn, ro_conn
+ end
+ ensure
+ ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.connection_handler }
+ end
+
+ private
+
+ def with_temporary_connection_pool
+ old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
+ new_pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new ActiveRecord::Base.connection_pool.spec
+ ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = new_pool
+
+ yield
+ ensure
+ ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = old_pool
+ end
+end
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/habtm_destroy_order_test.rb b/activerecord/test/cases/habtm_destroy_order_test.rb
index b15e1b48c4..9dbd339fe7 100644
--- a/activerecord/test/cases/habtm_destroy_order_test.rb
+++ b/activerecord/test/cases/habtm_destroy_order_test.rb
@@ -30,23 +30,21 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase
test "not destroying a student with lessons leaves student<=>lesson association intact" do
# test a normal before_destroy doesn't destroy the habtm joins
- begin
- sicp = Lesson.new(name: "SICP")
- ben = Student.new(name: "Ben Bitdiddle")
- # add a before destroy to student
- Student.class_eval do
- before_destroy do
- raise ActiveRecord::Rollback unless lessons.empty?
- end
+ sicp = Lesson.new(name: "SICP")
+ ben = Student.new(name: "Ben Bitdiddle")
+ # add a before destroy to student
+ Student.class_eval do
+ before_destroy do
+ raise ActiveRecord::Rollback unless lessons.empty?
end
- ben.lessons << sicp
- ben.save!
- ben.destroy
- assert_not_empty ben.reload.lessons
- ensure
- # get rid of it so Student is still like it was
- Student.reset_callbacks(:destroy)
end
+ ben.lessons << sicp
+ ben.save!
+ ben.destroy
+ assert_not_empty ben.reload.lessons
+ ensure
+ # get rid of it so Student is still like it was
+ Student.reset_callbacks(:destroy)
end
test "not destroying a lesson with students leaves student<=>lesson association intact" do
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 66f11fe5bd..543a0aeb39 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -48,8 +48,27 @@ def mysql_enforcing_gtid_consistency?
current_adapter?(:Mysql2Adapter) && "ON" == ActiveRecord::Base.connection.show_variable("enforce_gtid_consistency")
end
-def supports_savepoints?
- ActiveRecord::Base.connection.supports_savepoints?
+def supports_default_expression?
+ if current_adapter?(:PostgreSQLAdapter)
+ true
+ elsif current_adapter?(:Mysql2Adapter)
+ conn = ActiveRecord::Base.connection
+ !conn.mariadb? && conn.database_version >= "8.0.13"
+ end
+end
+
+%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")
@@ -184,4 +203,4 @@ module InTimeZone
end
end
-require "mocha/minitest" # FIXME: stop using mocha
+require_relative "../../../tools/test_common"
diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb
index e7778af55b..7b388ebc5e 100644
--- a/activerecord/test/cases/hot_compatibility_test.rb
+++ b/activerecord/test/cases/hot_compatibility_test.rb
@@ -56,7 +56,7 @@ class HotCompatibilityTest < ActiveRecord::TestCase
assert_equal "bar", record.foo
end
- if current_adapter?(:PostgreSQLAdapter)
+ if current_adapter?(:PostgreSQLAdapter) && ActiveRecord::Base.connection.prepared_statements
test "cleans up after prepared statement failure in a transaction" do
with_two_connections do |original_connection, ddl_connection|
record = @klass.create! bar: "bar"
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 3d3189900f..629167e9ed 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -240,7 +240,7 @@ class InheritanceTest < ActiveRecord::TestCase
cabbage = vegetable.becomes!(Cabbage)
assert_equal "Cabbage", cabbage.custom_type
- vegetable = cabbage.becomes!(Vegetable)
+ cabbage.becomes!(Vegetable)
assert_nil cabbage.custom_type
end
@@ -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
@@ -654,7 +657,7 @@ class InheritanceAttributeMappingTest < ActiveRecord::TestCase
assert_equal ["omg_inheritance_attribute_mapping_test/company"], ActiveRecord::Base.connection.select_values("SELECT sponsorable_type FROM sponsors")
- sponsor = Sponsor.first
+ sponsor = Sponsor.find(sponsor.id)
assert_equal startup, sponsor.sponsorable
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 e6e8468757..06382b6c7c 100644
--- a/activerecord/test/cases/instrumentation_test.rb
+++ b/activerecord/test/cases/instrumentation_test.rb
@@ -5,6 +5,10 @@ require "models/book"
module ActiveRecord
class InstrumentationTest < ActiveRecord::TestCase
+ def setup
+ ActiveRecord::Base.connection.schema_cache.add(Book.table_name)
+ end
+
def test_payload_name_on_load
Book.create(name: "test book")
subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
@@ -37,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
@@ -50,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/integration_test.rb b/activerecord/test/cases/integration_test.rb
index 36cd63c4d4..4185e8d682 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -157,78 +157,87 @@ class IntegrationTest < ActiveRecord::TestCase
skip("Subsecond precision is not supported") unless subsecond_precision_supported?
dev = Developer.first
key = dev.cache_key
- dev.touch
+ travel_to dev.updated_at + 0.000001 do
+ dev.touch
+ end
assert_not_equal key, dev.cache_key
end
def test_cache_key_format_is_not_too_precise
- skip("Subsecond precision is not supported") unless subsecond_precision_supported?
dev = Developer.first
dev.touch
key = dev.cache_key
assert_equal key, dev.reload.cache_key
end
- def test_named_timestamps_for_cache_key
- assert_deprecated do
- owner = owners(:blackbeard)
- assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:usec)}", owner.cache_key(:updated_at, :happy_at)
+ def test_cache_version_format_is_precise_enough
+ skip("Subsecond precision is not supported") unless subsecond_precision_supported?
+ with_cache_versioning do
+ dev = Developer.first
+ version = dev.cache_version.to_param
+ travel_to Developer.first.updated_at + 0.000001 do
+ dev.touch
+ end
+ assert_not_equal version, dev.cache_version.to_param
end
end
- def test_cache_key_when_named_timestamp_is_nil
- assert_deprecated do
- owner = owners(:blackbeard)
- owner.happy_at = nil
- assert_equal "owners/#{owner.id}", owner.cache_key(:happy_at)
+ def test_cache_version_format_is_not_too_precise
+ with_cache_versioning do
+ dev = Developer.first
+ dev.touch
+ key = dev.cache_version.to_param
+ assert_equal key, dev.reload.cache_version.to_param
end
end
def test_cache_key_is_stable_with_versioning_on
- Developer.cache_versioning = true
-
- developer = Developer.first
- first_key = developer.cache_key
+ with_cache_versioning do
+ developer = Developer.first
+ first_key = developer.cache_key
- developer.touch
- second_key = developer.cache_key
+ developer.touch
+ second_key = developer.cache_key
- assert_equal first_key, second_key
- ensure
- Developer.cache_versioning = false
+ assert_equal first_key, second_key
+ end
end
def test_cache_version_changes_with_versioning_on
- Developer.cache_versioning = true
+ with_cache_versioning do
+ developer = Developer.first
+ first_version = developer.cache_version
- developer = Developer.first
- first_version = developer.cache_version
+ travel 10.seconds do
+ developer.touch
+ end
- travel 10.seconds do
- developer.touch
- end
-
- second_version = developer.cache_version
+ second_version = developer.cache_version
- assert_not_equal first_version, second_version
- ensure
- Developer.cache_versioning = false
+ assert_not_equal first_version, second_version
+ end
end
def test_cache_key_retains_version_when_custom_timestamp_is_used
- Developer.cache_versioning = true
+ with_cache_versioning do
+ developer = Developer.first
+ first_key = developer.cache_key_with_version
- developer = Developer.first
- first_key = developer.cache_key_with_version
+ travel 10.seconds do
+ developer.touch
+ end
- travel 10.seconds do
- developer.touch
- end
+ second_key = developer.cache_key_with_version
- second_key = developer.cache_key_with_version
+ assert_not_equal first_key, second_key
+ end
+ end
- assert_not_equal first_key, second_key
+ def with_cache_versioning(value = true)
+ @old_cache_versioning = ActiveRecord::Base.cache_versioning
+ ActiveRecord::Base.cache_versioning = value
+ yield
ensure
- Developer.cache_versioning = false
+ ActiveRecord::Base.cache_versioning = @old_cache_versioning
end
end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 363beb4780..d68cc40107 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -22,6 +22,14 @@ module ActiveRecord
end
end
+ class InvertibleTransactionMigration < InvertibleMigration
+ def change
+ transaction do
+ super
+ end
+ end
+ end
+
class InvertibleRevertMigration < SilentMigration
def change
revert do
@@ -271,6 +279,14 @@ module ActiveRecord
assert_not revert.connection.table_exists?("horses")
end
+ def test_migrate_revert_transaction
+ migration = InvertibleTransactionMigration.new
+ migration.migrate :up
+ assert migration.connection.table_exists?("horses")
+ migration.migrate :down
+ assert_not migration.connection.table_exists?("horses")
+ end
+
def test_migrate_revert_change_column_default
migration1 = ChangeColumnDefault1.new
migration1.migrate(:up)
@@ -292,6 +308,8 @@ module ActiveRecord
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/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index e2742ed33e..ae2597adc8 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -44,6 +44,7 @@ class LogSubscriberTest < ActiveRecord::TestCase
def setup
@old_logger = ActiveRecord::Base.logger
Developer.primary_key
+ ActiveRecord::Base.connection.materialize_transactions
super
ActiveRecord::LogSubscriber.attach_to(:active_record)
end
@@ -177,11 +178,25 @@ class LogSubscriberTest < ActiveRecord::TestCase
logger = TestDebugLogSubscriber.new
logger.sql(Event.new(0, sql: "hi mom!"))
+ assert_equal 2, @logger.logged(:debug).size
assert_match(/↳/, @logger.logged(:debug).last)
ensure
ActiveRecord::Base.verbose_query_logs = false
end
+ def test_verbose_query_with_ignored_callstack
+ ActiveRecord::Base.verbose_query_logs = true
+
+ logger = TestDebugLogSubscriber.new
+ def logger.extract_query_source_location(*); nil; end
+
+ logger.sql(Event.new(0, sql: "hi mom!"))
+ assert_equal 1, @logger.logged(:debug).size
+ assert_no_match(/↳/, @logger.logged(:debug).last)
+ ensure
+ ActiveRecord::Base.verbose_query_logs = false
+ end
+
def test_verbose_query_logs_disabled_by_default
logger = TestDebugLogSubscriber.new
logger.sql(Event.new(0, sql: "hi mom!"))
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/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index 034bf32165..c108d372d1 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -164,6 +164,14 @@ module ActiveRecord
end
end
+ def test_column_creates_column_with_index
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {}]
+ @connection.expect :add_index, nil, [:delete_me, :bar, {}]
+ t.column :bar, :integer, index: true
+ end
+ end
+
def test_index_creates_index
with_change_table do |t|
@connection.expect :add_index, nil, [:delete_me, :bar, {}]
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 3a11bb081b..01f8628fc5 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -117,13 +117,13 @@ module ActiveRecord
end
def test_invert_create_table_with_options_and_block
- block = Proc.new {}
+ block = Proc.new { }
drop_table = @recorder.inverse_of :create_table, [:people_reminders, id: false], &block
assert_equal [:drop_table, [:people_reminders, id: false], block], drop_table
end
def test_invert_drop_table
- block = Proc.new {}
+ block = Proc.new { }
create_table = @recorder.inverse_of :drop_table, [:people_reminders, id: false], &block
assert_equal [:create_table, [:people_reminders, id: false], block], create_table
end
@@ -145,7 +145,7 @@ module ActiveRecord
end
def test_invert_drop_join_table
- block = Proc.new {}
+ block = Proc.new { }
create_join_table = @recorder.inverse_of :drop_join_table, [:musics, :artists, table_name: :catalog], &block
assert_equal [:create_join_table, [:musics, :artists, table_name: :catalog], block], create_join_table
end
@@ -329,11 +329,24 @@ module ActiveRecord
assert_equal [:add_foreign_key, [:dogs, :people, primary_key: "person_id"]], enable
end
+ def test_invert_remove_foreign_key_with_primary_key_and_to_table_in_options
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, to_table: :people, primary_key: "uuid"]
+ assert_equal [:add_foreign_key, [:dogs, :people, primary_key: "uuid"]], enable
+ end
+
def test_invert_remove_foreign_key_with_on_delete_on_update
enable = @recorder.inverse_of :remove_foreign_key, [:dogs, :people, on_delete: :nullify, on_update: :cascade]
assert_equal [:add_foreign_key, [:dogs, :people, on_delete: :nullify, on_update: :cascade]], enable
end
+ def test_invert_remove_foreign_key_with_to_table_in_options
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, to_table: :people]
+ assert_equal [:add_foreign_key, [:dogs, :people]], enable
+
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, to_table: :people, column: :owner_id]
+ assert_equal [:add_foreign_key, [:dogs, :people, column: :owner_id]], enable
+ end
+
def test_invert_remove_foreign_key_is_irreversible_without_to_table
assert_raises ActiveRecord::IrreversibleMigration do
@recorder.inverse_of :remove_foreign_key, [:dogs, column: "owner_id"]
@@ -347,6 +360,16 @@ module ActiveRecord
@recorder.inverse_of :remove_foreign_key, [:dogs]
end
end
+
+ def test_invert_transaction_with_irreversible_inside_is_irreversible
+ assert_raises(ActiveRecord::IrreversibleMigration) do
+ @recorder.revert do
+ @recorder.transaction do
+ @recorder.execute "some sql"
+ end
+ end
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 69a50674af..5753bd7117 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -86,8 +86,8 @@ module ActiveRecord
ActiveRecord::Migrator.new(:up, [migration]).migrate
- assert connection.columns(:more_testings).find { |c| c.name == "created_at" }.null
- assert connection.columns(:more_testings).find { |c| c.name == "updated_at" }.null
+ assert connection.column_exists?(:more_testings, :created_at, null: true)
+ assert connection.column_exists?(:more_testings, :updated_at, null: true)
ensure
connection.drop_table :more_testings rescue nil
end
@@ -103,8 +103,25 @@ module ActiveRecord
ActiveRecord::Migrator.new(:up, [migration]).migrate
- assert connection.columns(:testings).find { |c| c.name == "created_at" }.null
- assert connection.columns(:testings).find { |c| c.name == "updated_at" }.null
+ assert connection.column_exists?(:testings, :created_at, null: true)
+ assert connection.column_exists?(:testings, :updated_at, null: true)
+ end
+
+ if ActiveRecord::Base.connection.supports_bulk_alter?
+ def test_timestamps_have_null_constraints_if_not_present_in_migration_of_change_table_with_bulk
+ migration = Class.new(ActiveRecord::Migration[4.2]) {
+ def migrate(x)
+ change_table :testings, bulk: true do |t|
+ t.timestamps
+ end
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert connection.column_exists?(:testings, :created_at, null: true)
+ assert connection.column_exists?(:testings, :updated_at, null: true)
+ end
end
def test_timestamps_have_null_constraints_if_not_present_in_migration_for_adding_timestamps_to_existing_table
@@ -116,8 +133,70 @@ module ActiveRecord
ActiveRecord::Migrator.new(:up, [migration]).migrate
- assert connection.columns(:testings).find { |c| c.name == "created_at" }.null
- assert connection.columns(:testings).find { |c| c.name == "updated_at" }.null
+ assert connection.column_exists?(:testings, :created_at, null: true)
+ assert connection.column_exists?(:testings, :updated_at, null: true)
+ end
+
+ def test_timestamps_doesnt_set_precision_on_create_table
+ migration = Class.new(ActiveRecord::Migration[5.2]) {
+ def migrate(x)
+ create_table :more_testings do |t|
+ t.timestamps
+ end
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert connection.column_exists?(:more_testings, :created_at, null: false, **precision_implicit_default)
+ assert connection.column_exists?(:more_testings, :updated_at, null: false, **precision_implicit_default)
+ ensure
+ connection.drop_table :more_testings rescue nil
+ end
+
+ def test_timestamps_doesnt_set_precision_on_change_table
+ migration = Class.new(ActiveRecord::Migration[5.2]) {
+ def migrate(x)
+ change_table :testings do |t|
+ t.timestamps default: Time.now
+ end
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert connection.column_exists?(:testings, :created_at, null: false, **precision_implicit_default)
+ assert connection.column_exists?(:testings, :updated_at, null: false, **precision_implicit_default)
+ end
+
+ if ActiveRecord::Base.connection.supports_bulk_alter?
+ def test_timestamps_doesnt_set_precision_on_change_table_with_bulk
+ migration = Class.new(ActiveRecord::Migration[5.2]) {
+ def migrate(x)
+ change_table :testings, bulk: true do |t|
+ t.timestamps
+ end
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert connection.column_exists?(:testings, :created_at, null: false, **precision_implicit_default)
+ assert connection.column_exists?(:testings, :updated_at, null: false, **precision_implicit_default)
+ end
+ end
+
+ def test_timestamps_doesnt_set_precision_on_add_timestamps
+ migration = Class.new(ActiveRecord::Migration[5.2]) {
+ def migrate(x)
+ add_timestamps :testings, default: Time.now
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert connection.column_exists?(:testings, :created_at, null: false, **precision_implicit_default)
+ assert connection.column_exists?(:testings, :updated_at, null: false, **precision_implicit_default)
end
def test_legacy_migrations_raises_exception_when_inherited
@@ -127,6 +206,20 @@ module ActiveRecord
assert_match(/LegacyMigration < ActiveRecord::Migration\[4\.2\]/, e.message)
end
+ def test_legacy_migrations_not_raise_exception_on_reverting_transaction
+ migration = Class.new(ActiveRecord::Migration[5.2]) {
+ def change
+ transaction do
+ execute "select 1"
+ end
+ end
+ }.new
+
+ assert_nothing_raised do
+ migration.migrate(:down)
+ end
+ end
+
if current_adapter?(:PostgreSQLAdapter)
class Testing < ActiveRecord::Base
end
@@ -145,6 +238,15 @@ module ActiveRecord
ActiveRecord::Base.clear_cache!
end
end
+
+ private
+ def precision_implicit_default
+ if current_adapter?(:Mysql2Adapter)
+ { presicion: 0 }
+ else
+ { presicion: nil }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index c471dd1106..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,45 +31,140 @@ 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
def test_change_column_of_parent_table
- foreign_keys = ActiveRecord::Base.connection.foreign_keys("astronauts")
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(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 Astronaut.table_name, :name, :astronaut_name
+
+ 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 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 Astronaut.table_name, :rocket_id, :new_rocket_id
+
+ 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 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
@@ -108,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
@@ -122,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
@@ -136,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
@@ -155,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
@@ -229,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")
@@ -260,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
@@ -268,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?
@@ -349,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
@@ -403,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|
@@ -411,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
@@ -435,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/pending_migrations_test.rb b/activerecord/test/cases/migration/pending_migrations_test.rb
index dedb5ea502..119bfd372a 100644
--- a/activerecord/test/cases/migration/pending_migrations_test.rb
+++ b/activerecord/test/cases/migration/pending_migrations_test.rb
@@ -25,7 +25,7 @@ module ActiveRecord
ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
assert_raises ActiveRecord::PendingMigrationError do
- CheckPending.new(Proc.new {}).call({})
+ CheckPending.new(Proc.new { }).call({})
end
end
@@ -34,7 +34,7 @@ module ActiveRecord
migrator = Base.connection.migration_context
capture(:stdout) { migrator.migrate }
- assert_nil CheckPending.new(Proc.new {}).call({})
+ assert_nil CheckPending.new(Proc.new { }).call({})
end
end
end
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index 7a092103c7..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
@@ -152,35 +150,38 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
test "foreign key methods respect pluralize_table_names" do
- begin
- original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names
- ActiveRecord::Base.pluralize_table_names = false
- @connection.create_table :testing
- @connection.change_table :testing_parents do |t|
- t.references :testing, foreign_key: true
- end
+ original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names
+ ActiveRecord::Base.pluralize_table_names = false
+ @connection.create_table :testing
+ @connection.change_table :testing_parents do |t|
+ t.references :testing, foreign_key: true
+ end
- fk = @connection.foreign_keys("testing_parents").first
- assert_equal "testing_parents", fk.from_table
- assert_equal "testing", fk.to_table
+ fk = @connection.foreign_keys("testing_parents").first
+ assert_equal "testing_parents", fk.from_table
+ assert_equal "testing", fk.to_table
- assert_difference "@connection.foreign_keys('testing_parents').size", -1 do
- @connection.remove_reference :testing_parents, :testing, foreign_key: true
- end
- ensure
- ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names
- @connection.drop_table "testing", if_exists: true
+ assert_difference "@connection.foreign_keys('testing_parents').size", -1 do
+ @connection.remove_reference :testing_parents, :testing, foreign_key: true
end
+ ensure
+ ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names
+ @connection.drop_table "testing", if_exists: true
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
@@ -234,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 d1292dc53d..8e8ed494d9 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -71,13 +71,10 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migration.verbose = @verbose_was
end
- def test_migrator_migrations_path_is_deprecated
- assert_deprecated do
- ActiveRecord::Migrator.migrations_path = "/whatever"
- end
- ensure
+ def test_passing_migrations_paths_to_assume_migrated_upto_version_is_deprecated
+ ActiveRecord::SchemaMigration.create_table
assert_deprecated do
- ActiveRecord::Migrator.migrations_path = "db/migrate"
+ ActiveRecord::Base.connection.assume_migrated_upto_version(0, [])
end
end
@@ -87,7 +84,6 @@ class MigrationTest < ActiveRecord::TestCase
def test_migrator_versions
migrations_path = MIGRATIONS_ROOT + "/valid"
- old_path = ActiveRecord::Migrator.migrations_paths
migrator = ActiveRecord::MigrationContext.new(migrations_path)
migrator.up
@@ -100,24 +96,18 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::SchemaMigration.create!(version: 3)
assert_equal true, migrator.needs_migration?
- ensure
- ActiveRecord::MigrationContext.new(old_path)
end
def test_migration_detection_without_schema_migration_table
ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
migrations_path = MIGRATIONS_ROOT + "/valid"
- old_path = ActiveRecord::Migrator.migrations_paths
migrator = ActiveRecord::MigrationContext.new(migrations_path)
assert_equal true, migrator.needs_migration?
- ensure
- ActiveRecord::MigrationContext.new(old_path)
end
def test_any_migrations
- old_path = ActiveRecord::Migrator.migrations_paths
migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid")
assert_predicate migrator, :any_migrations?
@@ -125,8 +115,6 @@ class MigrationTest < ActiveRecord::TestCase
migrator_empty = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/empty")
assert_not_predicate migrator_empty, :any_migrations?
- ensure
- ActiveRecord::MigrationContext.new(old_path)
end
def test_migration_version
@@ -136,6 +124,36 @@ class MigrationTest < ActiveRecord::TestCase
assert_equal 20131219224947, migrator.current_version
end
+ def test_create_table_raises_if_already_exists
+ connection = Person.connection
+ connection.create_table :testings, force: true do |t|
+ t.string :foo
+ end
+
+ assert_raise(ActiveRecord::StatementInvalid) do
+ connection.create_table :testings do |t|
+ t.string :foo
+ end
+ end
+ ensure
+ connection.drop_table :testings, if_exists: true
+ end
+
+ def test_create_table_with_if_not_exists_true
+ connection = Person.connection
+ connection.create_table :testings, force: true do |t|
+ t.string :foo
+ end
+
+ assert_nothing_raised do
+ connection.create_table :testings, if_not_exists: true do |t|
+ t.string :foo
+ end
+ end
+ ensure
+ connection.drop_table :testings, if_exists: true
+ end
+
def test_create_table_with_force_true_does_not_drop_nonexisting_table
# using a copy as we need the drop_table method to
# continue to work for the ensure block of the test
@@ -367,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
@@ -387,13 +406,13 @@ 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
def test_internal_metadata_stores_environment
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrations_path = MIGRATIONS_ROOT + "/valid"
- old_path = ActiveRecord::Migrator.migrations_paths
migrator = ActiveRecord::MigrationContext.new(migrations_path)
migrator.up
@@ -410,7 +429,6 @@ class MigrationTest < ActiveRecord::TestCase
migrator.up
assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
ensure
- migrator = ActiveRecord::MigrationContext.new(old_path)
ENV["RAILS_ENV"] = original_rails_env
ENV["RACK_ENV"] = original_rack_env
migrator.up
@@ -422,16 +440,11 @@ class MigrationTest < ActiveRecord::TestCase
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrations_path = MIGRATIONS_ROOT + "/valid"
- old_path = ActiveRecord::Migrator.migrations_paths
- current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrator = ActiveRecord::MigrationContext.new(migrations_path)
migrator.up
assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
assert_equal "bar", ActiveRecord::InternalMetadata[:foo]
- ensure
- migrator = ActiveRecord::MigrationContext.new(old_path)
- migrator.up
end
def test_proper_table_name_on_migration
@@ -556,69 +569,68 @@ 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
- begin
- 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
- end
-
- # should be all good w/ a custom sequence name
- assert_nothing_raised do
- begin
- 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
- 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|
+ t.text :bigtext, size: 0xfffffffff
+ end
+ end
+
+ 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
end
if ActiveRecord::Base.connection.supports_advisory_locks?
@@ -727,15 +739,13 @@ class MigrationTest < ActiveRecord::TestCase
test_terminated = Concurrent::CountDownLatch.new
other_process = Thread.new do
- begin
- conn = ActiveRecord::Base.connection_pool.checkout
- conn.get_advisory_lock(lock_id)
- thread_lock.count_down
- test_terminated.wait # hold the lock open until we tested everything
- ensure
- conn.release_advisory_lock(lock_id)
- ActiveRecord::Base.connection_pool.checkin(conn)
- end
+ conn = ActiveRecord::Base.connection_pool.checkout
+ conn.get_advisory_lock(lock_id)
+ thread_lock.count_down
+ test_terminated.wait # hold the lock open until we tested everything
+ ensure
+ conn.release_advisory_lock(lock_id)
+ ActiveRecord::Base.connection_pool.checkin(conn)
end
thread_lock.wait # wait until the 'other process' has the lock
@@ -793,12 +803,20 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
end
def test_adding_multiple_columns
- assert_queries(1) do
+ classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
+ expected_query_count = {
+ "Mysql2Adapter" => 1,
+ "PostgreSQLAdapter" => 2, # one for bulk change, one for comment
+ }.fetch(classname) {
+ raise "need an expected query count for #{classname}"
+ }
+
+ assert_queries(expected_query_count) do
with_bulk_change_table do |t|
t.column :name, :string
t.string :qualification, :experience
t.integer :age, default: 0
- t.date :birthdate
+ t.date :birthdate, comment: "This is a comment"
t.timestamps null: true
end
end
@@ -806,6 +824,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
assert_equal 8, columns.size
[:name, :qualification, :experience].each { |s| assert_equal :string, column(s).type }
assert_equal "0", column(:age).default
+ assert_equal "This is a comment", column(:birthdate).comment
end
def test_removing_columns
@@ -1150,7 +1169,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
def test_check_pending_with_stdlib_logger
old, ActiveRecord::Base.logger = ActiveRecord::Base.logger, ::Logger.new($stdout)
quietly do
- assert_nothing_raised { ActiveRecord::Migration::CheckPending.new(Proc.new {}).call({}) }
+ assert_nothing_raised { ActiveRecord::Migration::CheckPending.new(Proc.new { }).call({}) }
end
ensure
ActiveRecord::Base.logger = old
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index 873455cf67..30e199f1c5 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -100,7 +100,6 @@ class MigratorTest < ActiveRecord::TestCase
def test_finds_migrations_in_subdirectories
migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid_with_subdirectories").migrations
-
[[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
assert_equal migrations[i].version, pair.first
assert_equal migrations[i].name, pair.last
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index 192d2f5251..f11c441c65 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -106,14 +106,12 @@ class MultipleDbTest < ActiveRecord::TestCase
end
def test_associations_should_work_when_model_has_no_connection
- begin
- ActiveRecord::Base.remove_connection
- assert_nothing_raised do
- College.first.courses.first
- end
- ensure
- ActiveRecord::Base.establish_connection :arunit
+ ActiveRecord::Base.remove_connection
+ assert_nothing_raised do
+ College.first.courses.first
end
+ ensure
+ ActiveRecord::Base.establish_connection :arunit
end
end
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index ec01a2965d..bb1c1ea17d 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -217,6 +217,18 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
mean_pirate.parrot_attributes = { name: "James" }
assert_equal "James", mean_pirate.parrot.name
end
+
+ def test_should_not_create_duplicates_with_create_with
+ Man.accepts_nested_attributes_for(:interests)
+
+ assert_difference("Interest.count", 1) do
+ Man.create_with(
+ interests_attributes: [{ topic: "Pirate king" }]
+ ).find_or_create_by!(
+ name: "Monkey D. Luffy"
+ )
+ end
+ end
end
class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
@@ -1094,3 +1106,15 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR
assert_equal ["Ship name can't be blank"], part.errors.full_messages
end
end
+
+class TestNestedAttributesWithExtend < ActiveRecord::TestCase
+ setup do
+ Pirate.accepts_nested_attributes_for :treasures
+ end
+
+ def test_extend_affects_nested_attributes
+ pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
+ pirate.treasures_attributes = [{ id: nil }]
+ assert_equal "from extension", pirate.treasures[0].name
+ end
+end
diff --git a/activerecord/test/cases/null_relation_test.rb b/activerecord/test/cases/null_relation_test.rb
index 17527568f8..ee96ea1af6 100644
--- a/activerecord/test/cases/null_relation_test.rb
+++ b/activerecord/test/cases/null_relation_test.rb
@@ -10,26 +10,27 @@ class NullRelationTest < ActiveRecord::TestCase
fixtures :posts, :comments
def test_none
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [], Developer.none
assert_equal [], Developer.all.none
end
end
def test_none_chainable
- assert_no_queries(ignore_none: false) do
+ Developer.send(:load_schema)
+ assert_no_queries do
assert_equal [], Developer.none.where(name: "David")
end
end
def test_none_chainable_to_existing_scope_extension_method
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal 1, Topic.anonymous_extension.none.one
end
end
def test_none_chained_to_methods_firing_queries_straight_to_db
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal [], Developer.none.pluck(:id, :name)
assert_equal 0, Developer.none.delete_all
assert_equal 0, Developer.none.update_all(name: "David")
@@ -39,7 +40,7 @@ class NullRelationTest < ActiveRecord::TestCase
end
def test_null_relation_content_size_methods
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal 0, Developer.none.size
assert_equal 0, Developer.none.count
assert_equal true, Developer.none.empty?
@@ -61,7 +62,7 @@ class NullRelationTest < ActiveRecord::TestCase
[:count, :sum].each do |method|
define_method "test_null_relation_#{method}" do
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_equal 0, Comment.none.public_send(method, :id)
assert_equal Hash.new, Comment.none.group(:post_id).public_send(method, :id)
end
@@ -70,7 +71,7 @@ class NullRelationTest < ActiveRecord::TestCase
[:average, :minimum, :maximum].each do |method|
define_method "test_null_relation_#{method}" do
- assert_no_queries(ignore_none: false) do
+ assert_no_queries do
assert_nil Comment.none.public_send(method, :id)
assert_equal Hash.new, Comment.none.group(:post_id).public_send(method, :id)
end
diff --git a/activerecord/test/cases/numeric_data_test.rb b/activerecord/test/cases/numeric_data_test.rb
index 14db63890e..079e664ee4 100644
--- a/activerecord/test/cases/numeric_data_test.rb
+++ b/activerecord/test/cases/numeric_data_test.rb
@@ -24,8 +24,10 @@ class NumericDataTest < ActiveRecord::TestCase
)
assert m.save
- m1 = NumericData.find(m.id)
- assert_not_nil m1
+ m1 = NumericData.find_by(
+ bank_balance: 1586.43,
+ big_bank_balance: BigDecimal("1000234000567.95")
+ )
assert_kind_of Integer, m1.world_population
assert_equal 2**62, m1.world_population
@@ -49,8 +51,10 @@ class NumericDataTest < ActiveRecord::TestCase
)
assert m.save
- m1 = NumericData.find(m.id)
- assert_not_nil m1
+ m1 = NumericData.find_by(
+ bank_balance: 1586.43122334,
+ big_bank_balance: BigDecimal("234000567.952344")
+ )
assert_kind_of Integer, m1.world_population
assert_equal 2**62, m1.world_population
@@ -64,4 +68,26 @@ class NumericDataTest < ActiveRecord::TestCase
assert_kind_of BigDecimal, m1.big_bank_balance
assert_equal BigDecimal("234000567.95"), m1.big_bank_balance
end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_numeric_fields_with_nan
+ m = NumericData.new(
+ bank_balance: BigDecimal("NaN"),
+ big_bank_balance: BigDecimal("NaN"),
+ world_population: 2**62,
+ my_house_population: 3
+ )
+ assert_predicate m.bank_balance, :nan?
+ assert_predicate m.big_bank_balance, :nan?
+ assert m.save
+
+ m1 = NumericData.find_by(
+ bank_balance: BigDecimal("NaN"),
+ big_bank_balance: BigDecimal("NaN")
+ )
+
+ assert_predicate m1.bank_balance, :nan?
+ assert_predicate m1.big_bank_balance, :nan?
+ end
+ end
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 7348a22dd3..d5057ad381 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -13,90 +13,15 @@ require "models/developer"
require "models/computer"
require "models/project"
require "models/minimalistic"
-require "models/warehouse_thing"
require "models/parrot"
require "models/minivan"
-require "models/owner"
require "models/person"
-require "models/pet"
require "models/ship"
-require "models/toy"
require "models/admin"
require "models/admin/user"
-require "rexml/document"
class PersistenceTest < ActiveRecord::TestCase
- fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :author_addresses, :categorizations, :categories, :posts, :minivans, :pets, :toys
-
- # Oracle UPDATE does not support ORDER BY
- unless current_adapter?(:OracleAdapter)
- def test_update_all_ignores_order_without_limit_from_association
- author = authors(:david)
- assert_nothing_raised do
- assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ])
- end
- end
-
- def test_update_all_doesnt_ignore_order
- assert_equal authors(:david).id + 1, authors(:mary).id # make sure there is going to be a duplicate PK error
- test_update_with_order_succeeds = lambda do |order|
- begin
- Author.order(order).update_all("id = id + 1")
- rescue ActiveRecord::ActiveRecordError
- false
- end
- end
-
- if test_update_with_order_succeeds.call("id DESC")
- assert_not test_update_with_order_succeeds.call("id ASC") # test that this wasn't a fluke and using an incorrect order results in an exception
- else
- # test that we're failing because the current Arel's engine doesn't support UPDATE ORDER BY queries is using subselects instead
- assert_sql(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC\)\z/i) do
- test_update_with_order_succeeds.call("id DESC")
- end
- end
- end
-
- def test_update_all_with_order_and_limit_updates_subset_only
- author = authors(:david)
- limited_posts = author.posts_sorted_by_id_limited
- assert_equal 1, limited_posts.size
- assert_equal 2, limited_posts.limit(2).size
- assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
- assert_equal "bulk update!", posts(:welcome).body
- assert_not_equal "bulk update!", posts(:thinking).body
- end
-
- def test_update_all_with_order_and_limit_and_offset_updates_subset_only
- author = authors(:david)
- limited_posts = author.posts_sorted_by_id_limited.offset(1)
- assert_equal 1, limited_posts.size
- assert_equal 2, limited_posts.limit(2).size
- assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
- assert_equal "bulk update!", posts(:thinking).body
- assert_not_equal "bulk update!", posts(:welcome).body
- 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
- end
+ fixtures :topics, :companies, :developers, :accounts, :minimalistics, :authors, :author_addresses, :posts, :minivans
def test_update_many
topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
@@ -128,6 +53,20 @@ class PersistenceTest < ActiveRecord::TestCase
assert_not_equal "2 updated", Topic.find(2).content
end
+ def test_class_level_update_without_ids
+ topics = Topic.all
+ assert_equal 5, topics.length
+ topics.each do |topic|
+ assert_not_equal "updated", topic.content
+ end
+
+ updated = Topic.update(content: "updated")
+ assert_equal 5, updated.length
+ updated.each do |topic|
+ assert_equal "updated", topic.content
+ end
+ end
+
def test_class_level_update_is_affected_by_scoping
topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
@@ -145,34 +84,6 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal Topic.count, Topic.delete_all
end
- def test_delete_all_with_joins_and_where_part_is_hash
- pets = Pet.joins(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.delete_all
- end
-
- def test_delete_all_with_joins_and_where_part_is_not_hash
- pets = Pet.joins(:toys).where("toys.name = ?", "Bone")
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.delete_all
- end
-
- def test_delete_all_with_left_joins
- pets = Pet.left_joins(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.delete_all
- end
-
- def test_delete_all_with_includes
- pets = Pet.includes(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.delete_all
- end
-
def test_increment_attribute
assert_equal 50, accounts(:signals37).credit_limit
accounts(:signals37).increment! :credit_limit
@@ -230,16 +141,9 @@ class PersistenceTest < ActiveRecord::TestCase
assert_operator previously_written_on, :<, topic.written_on
end
- def test_destroy_all
- conditions = "author_name = 'Mary'"
- topics_by_mary = Topic.all.merge!(where: conditions, order: "id").to_a
- assert_not_empty topics_by_mary
-
- assert_difference("Topic.count", -topics_by_mary.size) do
- destroyed = Topic.where(conditions).destroy_all.sort_by(&:id)
- assert_equal topics_by_mary, destroyed
- assert destroyed.all?(&:frozen?), "destroyed topics should be frozen"
- end
+ def test_increment_with_no_arg
+ topic = topics(:first)
+ assert_raises(ArgumentError) { topic.increment! }
end
def test_destroy_many
@@ -556,19 +460,17 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_update_attribute_does_not_run_sql_if_attribute_is_not_changed
- klass = Class.new(Topic) do
- def self.name; "Topic"; end
- end
- topic = klass.create(title: "Another New Topic")
- assert_queries(0) do
+ topic = Topic.create(title: "Another New Topic")
+ assert_no_queries do
assert topic.update_attribute(:title, "Another New Topic")
end
end
def test_update_does_not_run_sql_if_record_has_not_changed
topic = Topic.create(title: "Another New Topic")
- assert_queries(0) { assert topic.update(title: "Another New Topic") }
- assert_queries(0) { assert topic.update(title: "Another New Topic") }
+ assert_no_queries do
+ assert topic.update(title: "Another New Topic")
+ end
end
def test_delete
@@ -638,32 +540,6 @@ class PersistenceTest < ActiveRecord::TestCase
assert_nil Topic.find(2).last_read
end
- def test_update_all_with_joins
- pets = Pet.joins(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.update_all(name: "Bob")
- end
-
- def test_update_all_with_left_joins
- pets = Pet.left_joins(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.update_all(name: "Bob")
- end
-
- def test_update_all_with_includes
- pets = Pet.includes(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.update_all(name: "Bob")
- end
-
- def test_update_all_with_non_standard_table_name
- assert_equal 1, WarehouseThing.where(id: 1).update_all(["value = ?", 0])
- assert_equal 0, WarehouseThing.find(1).value
- end
-
def test_delete_new_record
client = Client.new(name: "37signals")
client.delete
@@ -1185,21 +1061,19 @@ class PersistenceTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.disable_query_cache!
end
- class SaveTest < ActiveRecord::TestCase
- def test_save_touch_false
- pet = Pet.create!(
- name: "Bob",
- created_at: 1.day.ago,
- updated_at: 1.day.ago)
+ def test_save_touch_false
+ parrot = Parrot.create!(
+ name: "Bob",
+ created_at: 1.day.ago,
+ updated_at: 1.day.ago)
- created_at = pet.created_at
- updated_at = pet.updated_at
+ created_at = parrot.created_at
+ updated_at = parrot.updated_at
- pet.name = "Barb"
- pet.save!(touch: false)
- assert_equal pet.created_at, created_at
- assert_equal pet.updated_at, updated_at
- end
+ parrot.name = "Barb"
+ parrot.save!(touch: false)
+ assert_equal parrot.created_at, created_at
+ assert_equal parrot.updated_at, updated_at
end
def test_reset_column_information_resets_children
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index fa7f759e51..080aeb0989 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -25,14 +25,12 @@ class PooledConnectionsTest < ActiveRecord::TestCase
@timed_out = 0
threads.times do
Thread.new do
- begin
- conn = ActiveRecord::Base.connection_pool.checkout
- sleep 0.1
- ActiveRecord::Base.connection_pool.checkin conn
- @connection_count += 1
- rescue ActiveRecord::ConnectionTimeoutError
- @timed_out += 1
- end
+ conn = ActiveRecord::Base.connection_pool.checkout
+ sleep 0.1
+ ActiveRecord::Base.connection_pool.checkin conn
+ @connection_count += 1
+ rescue ActiveRecord::ConnectionTimeoutError
+ @timed_out += 1
end.join
end
end
@@ -42,14 +40,12 @@ class PooledConnectionsTest < ActiveRecord::TestCase
@connection_count = 0
@timed_out = 0
loops.times do
- begin
- conn = ActiveRecord::Base.connection_pool.checkout
- ActiveRecord::Base.connection_pool.checkin conn
- @connection_count += 1
- ActiveRecord::Base.connection.data_sources
- rescue ActiveRecord::ConnectionTimeoutError
- @timed_out += 1
- end
+ conn = ActiveRecord::Base.connection_pool.checkout
+ ActiveRecord::Base.connection_pool.checkin conn
+ @connection_count += 1
+ ActiveRecord::Base.connection.data_sources
+ rescue ActiveRecord::ConnectionTimeoutError
+ @timed_out += 1
end
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 4ed7469039..4759d3b6b2 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -354,7 +354,6 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
end
def test_composite_primary_key_out_of_order
- skip if current_adapter?(:SQLite3Adapter)
assert_equal ["code", "region"], @connection.primary_keys("barcodes_reverse")
end
@@ -376,7 +375,6 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
end
def test_dumping_composite_primary_key_out_of_order
- skip if current_adapter?(:SQLite3Adapter)
schema = dump_table_schema "barcodes_reverse"
assert_match %r{create_table "barcodes_reverse", primary_key: \["code", "region"\]}, schema
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 69be091869..eb32b690aa 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -55,88 +55,97 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_cache :off
end
- private def with_temporary_connection_pool
- old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
- new_pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new ActiveRecord::Base.connection_pool.spec
- ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = new_pool
+ def test_query_cache_is_applied_to_connections_in_all_handlers
+ ActiveRecord::Base.connection_handlers = {
+ writing: ActiveRecord::Base.default_connection_handler,
+ reading: ActiveRecord::ConnectionAdapters::ConnectionHandler.new
+ }
+
+ ActiveRecord::Base.connected_to(role: :reading) do
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations["arunit"])
+ end
+
+ mw = middleware { |env|
+ ro_conn = ActiveRecord::Base.connection_handlers[:reading].connection_pool_list.first.connection
+ assert_predicate ActiveRecord::Base.connection, :query_cache_enabled
+ assert_predicate ro_conn, :query_cache_enabled
+ }
- yield
+ mw.call({})
ensure
- ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = old_pool
+ ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
end
def test_query_cache_across_threads
with_temporary_connection_pool do
- begin
- if in_memory_db?
- # Separate connections to an in-memory database create an entirely new database,
- # with an empty schema etc, so we just stub out this schema on the fly.
- ActiveRecord::Base.connection_pool.with_connection do |connection|
- connection.create_table :tasks do |t|
- t.datetime :starting
- t.datetime :ending
- end
+ if in_memory_db?
+ # Separate connections to an in-memory database create an entirely new database,
+ # with an empty schema etc, so we just stub out this schema on the fly.
+ ActiveRecord::Base.connection_pool.with_connection do |connection|
+ connection.create_table :tasks do |t|
+ t.datetime :starting
+ t.datetime :ending
end
- ActiveRecord::FixtureSet.create_fixtures(self.class.fixture_path, ["tasks"], {}, ActiveRecord::Base)
end
+ ActiveRecord::FixtureSet.create_fixtures(self.class.fixture_path, ["tasks"], {}, ActiveRecord::Base)
+ end
- ActiveRecord::Base.connection_pool.connections.each do |conn|
- assert_cache :off, conn
- end
+ ActiveRecord::Base.connection_pool.connections.each do |conn|
+ assert_cache :off, conn
+ end
- assert_not_predicate ActiveRecord::Base.connection, :nil?
- assert_cache :off
+ assert_not_predicate ActiveRecord::Base.connection, :nil?
+ assert_cache :off
- middleware {
- assert_cache :clean
+ middleware {
+ assert_cache :clean
- Task.find 1
- assert_cache :dirty
+ Task.find 1
+ assert_cache :dirty
- thread_1_connection = ActiveRecord::Base.connection
- ActiveRecord::Base.clear_active_connections!
- assert_cache :off, thread_1_connection
+ thread_1_connection = ActiveRecord::Base.connection
+ ActiveRecord::Base.clear_active_connections!
+ assert_cache :off, thread_1_connection
- started = Concurrent::Event.new
- checked = Concurrent::Event.new
+ started = Concurrent::Event.new
+ checked = Concurrent::Event.new
- thread_2_connection = nil
- thread = Thread.new {
- thread_2_connection = ActiveRecord::Base.connection
+ thread_2_connection = nil
+ thread = Thread.new {
+ thread_2_connection = ActiveRecord::Base.connection
- assert_equal thread_2_connection, thread_1_connection
- assert_cache :off
+ assert_equal thread_2_connection, thread_1_connection
+ assert_cache :off
- middleware {
- assert_cache :clean
+ middleware {
+ assert_cache :clean
- Task.find 1
- assert_cache :dirty
+ Task.find 1
+ assert_cache :dirty
- started.set
- checked.wait
+ started.set
+ checked.wait
- ActiveRecord::Base.clear_active_connections!
- }.call({})
- }
+ ActiveRecord::Base.clear_active_connections!
+ }.call({})
+ }
- started.wait
+ started.wait
- thread_1_connection = ActiveRecord::Base.connection
- assert_not_equal thread_1_connection, thread_2_connection
- assert_cache :dirty, thread_2_connection
- checked.set
- thread.join
+ thread_1_connection = ActiveRecord::Base.connection
+ assert_not_equal thread_1_connection, thread_2_connection
+ assert_cache :dirty, thread_2_connection
+ checked.set
+ thread.join
- assert_cache :off, thread_2_connection
- }.call({})
+ assert_cache :off, thread_2_connection
+ }.call({})
- ActiveRecord::Base.connection_pool.connections.each do |conn|
- assert_cache :off, conn
- end
- ensure
- ActiveRecord::Base.connection_pool.disconnect!
+ ActiveRecord::Base.connection_pool.connections.each do |conn|
+ assert_cache :off, conn
end
+ ensure
+ ActiveRecord::Base.connection_pool.disconnect!
end
end
@@ -200,7 +209,7 @@ class QueryCacheTest < ActiveRecord::TestCase
Task.cache do
assert_queries(2) { Task.find(1); Task.find(2) }
end
- assert_queries(0) { Task.find(1); Task.find(1); Task.find(2) }
+ assert_no_queries { Task.find(1); Task.find(1); Task.find(2) }
end
end
@@ -305,7 +314,7 @@ class QueryCacheTest < ActiveRecord::TestCase
payload[:sql].downcase!
end
- assert_raises frozen_error_class do
+ assert_raises FrozenError do
ActiveRecord::Base.cache do
assert_queries(1) { Task.find(1); Task.find(1) }
end
@@ -363,12 +372,10 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_not_predicate Task, :connected?
Task.cache do
- begin
- assert_queries(1) { Task.find(1); Task.find(1) }
- ensure
- ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name)
- Task.connection_specification_name = spec_name
- end
+ assert_queries(1) { Task.find(1); Task.find(1) }
+ ensure
+ ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name)
+ Task.connection_specification_name = spec_name
end
end
end
@@ -382,7 +389,7 @@ class QueryCacheTest < ActiveRecord::TestCase
end
# Check that if the same query is run again, no queries are executed
- assert_queries(0) do
+ assert_no_queries do
assert_equal 0, Post.where(title: "test").to_a.count
end
@@ -437,8 +444,9 @@ class QueryCacheTest < ActiveRecord::TestCase
# Clear places where type information is cached
Task.reset_column_information
Task.initialize_find_by_cache
+ Task.define_attribute_methods
- assert_queries(0) do
+ assert_no_queries do
Task.find(1)
end
end
@@ -494,7 +502,56 @@ class QueryCacheTest < ActiveRecord::TestCase
}.call({})
end
+ def test_clear_query_cache_is_called_on_all_connections
+ skip "with in memory db, reading role won't be able to see database on writing role" if in_memory_db?
+ with_temporary_connection_pool do
+ ActiveRecord::Base.connection_handlers = {
+ writing: ActiveRecord::Base.default_connection_handler,
+ reading: ActiveRecord::ConnectionAdapters::ConnectionHandler.new
+ }
+
+ ActiveRecord::Base.connected_to(role: :reading) do
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations["arunit"])
+ end
+
+ mw = middleware { |env|
+ ActiveRecord::Base.connected_to(role: :reading) do
+ @topic = Topic.first
+ end
+
+ assert @topic
+
+ ActiveRecord::Base.connected_to(role: :writing) do
+ @topic.title = "It doesn't have to be crazy at work"
+ @topic.save!
+ end
+
+ assert_equal "It doesn't have to be crazy at work", @topic.title
+
+ ActiveRecord::Base.connected_to(role: :reading) do
+ @topic = Topic.first
+ assert_equal "It doesn't have to be crazy at work", @topic.title
+ end
+ }
+
+ mw.call({})
+ end
+ ensure
+ ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
+ end
+
private
+
+ def with_temporary_connection_pool
+ old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
+ new_pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new ActiveRecord::Base.connection_pool.spec
+ ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = new_pool
+
+ yield
+ ensure
+ ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = old_pool
+ end
+
def middleware(&app)
executor = Class.new(ActiveSupport::Executor)
ActiveRecord::QueryCache.install_executor_hooks executor
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index f875dc96f7..723fccc8d9 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -90,24 +90,28 @@ module ActiveRecord
end
def test_quoted_time_dst_utc
- with_timezone_config default: :utc do
- t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30")
+ with_env_tz "America/New_York" do
+ with_timezone_config default: :utc do
+ t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30")
- expected = t.change(year: 2000, month: 1, day: 1)
- expected = expected.getutc.to_s(:db).slice(11..-1)
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getutc.to_s(:db).slice(11..-1)
- assert_equal expected, @quoter.quoted_time(t)
+ assert_equal expected, @quoter.quoted_time(t)
+ end
end
end
def test_quoted_time_dst_local
- with_timezone_config default: :local do
- t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30")
+ with_env_tz "America/New_York" do
+ with_timezone_config default: :local do
+ t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30")
- expected = t.change(year: 2000, month: 1, day: 1)
- expected = expected.getlocal.to_s(:db).slice(11..-1)
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getlocal.to_s(:db).slice(11..-1)
- assert_equal expected, @quoter.quoted_time(t)
+ assert_equal expected, @quoter.quoted_time(t)
+ end
end
end
diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb
index b034fe3e3b..402ddcf05a 100644
--- a/activerecord/test/cases/reaper_test.rb
+++ b/activerecord/test/cases/reaper_test.rb
@@ -48,7 +48,7 @@ module ActiveRecord
reaper = ConnectionPool::Reaper.new(fp, 0.0001)
reaper.run
- until fp.reaped
+ until fp.flushed
Thread.pass
end
assert fp.reaped
@@ -61,9 +61,9 @@ module ActiveRecord
def test_reaping_frequency_configuration
spec = ActiveRecord::Base.connection_pool.spec.dup
- spec.config[:reaping_frequency] = 100
+ spec.config[:reaping_frequency] = "10.01"
pool = ConnectionPool.new spec
- assert_equal 100, pool.reaper.frequency
+ assert_equal 10.01, pool.reaper.frequency
end
def test_connection_pool_starts_reaper
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index 3f3d41980c..085006c9a2 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -5,7 +5,7 @@ require "models/post"
require "models/comment"
module ActiveRecord
- module DelegationWhitelistTests
+ module DelegationTests
ARRAY_DELEGATES = [
:+, :-, :|, :&, :[], :shuffle,
:all?, :collect, :compact, :detect, :each, :each_cons, :each_with_index,
@@ -21,25 +21,14 @@ module ActiveRecord
assert_respond_to target, method
end
end
- end
-
- module DeprecatedArelDelegationTests
- AREL_METHODS = [
- :with, :orders, :froms, :project, :projections, :taken, :constraints, :exists, :locked, :where_sql,
- :ast, :source, :join_sources, :to_dot, :create_insert, :create_true, :create_false
- ]
- def test_deprecate_arel_delegation
- AREL_METHODS.each do |method|
- assert_deprecated { target.public_send(method) }
- assert_deprecated { target.public_send(method) }
- end
+ def test_not_respond_to_arel_method
+ assert_not_respond_to target, :exists
end
end
class DelegationAssociationTest < ActiveRecord::TestCase
- include DelegationWhitelistTests
- include DeprecatedArelDelegationTests
+ include DelegationTests
def target
Post.new.comments
@@ -47,8 +36,7 @@ module ActiveRecord
end
class DelegationRelationTest < ActiveRecord::TestCase
- include DelegationWhitelistTests
- include DeprecatedArelDelegationTests
+ include DelegationTests
def target
Comment.all
@@ -56,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
new file mode 100644
index 0000000000..d1c13fa1b5
--- /dev/null
+++ b/activerecord/test/cases/relation/delete_all_test.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/author"
+require "models/post"
+require "models/pet"
+require "models/toy"
+
+class DeleteAllTest < ActiveRecord::TestCase
+ fixtures :authors, :author_addresses, :posts, :pets, :toys
+
+ def test_destroy_all
+ davids = Author.where(name: "David")
+
+ # Force load
+ assert_equal [authors(:david)], davids.to_a
+ assert_predicate davids, :loaded?
+
+ assert_difference("Author.count", -1) do
+ destroyed = davids.destroy_all
+ assert_equal [authors(:david)], destroyed
+ assert_predicate destroyed.first, :frozen?
+ end
+
+ assert_equal [], davids.to_a
+ assert_predicate davids, :loaded?
+ end
+
+ def test_delete_all
+ davids = Author.where(name: "David")
+
+ assert_difference("Author.count", -1) { davids.delete_all }
+ assert_not_predicate davids, :loaded?
+ end
+
+ def test_delete_all_loaded
+ davids = Author.where(name: "David")
+
+ # Force load
+ assert_equal [authors(:david)], davids.to_a
+ assert_predicate davids, :loaded?
+
+ assert_difference("Author.count", -1) { davids.delete_all }
+
+ assert_equal [], davids.to_a
+ assert_predicate davids, :loaded?
+ end
+
+ def test_delete_all_with_unpermitted_relation_raises_error
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.distinct.delete_all }
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all }
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.having("SUM(id) < 3").delete_all }
+ end
+
+ def test_delete_all_with_joins_and_where_part_is_hash
+ pets = Pet.joins(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
+ end
+
+ def test_delete_all_with_joins_and_where_part_is_not_hash
+ pets = Pet.joins(:toys).where("toys.name = ?", "Bone")
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
+ end
+
+ def test_delete_all_with_left_joins
+ pets = Pet.left_joins(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
+ end
+
+ def test_delete_all_with_includes
+ pets = Pet.includes(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
+ 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
+end
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index f53ef1fe35..5c5e760e34 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -121,6 +121,32 @@ class RelationMergingTest < ActiveRecord::TestCase
relation = relation.merge(Post.from("posts"))
assert_not_empty relation.from_clause
end
+
+ def test_merging_with_from_clause_on_different_class
+ assert Comment.joins(:post).merge(Post.from("posts")).first
+ end
+
+ def test_merging_with_order_with_binds
+ relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
+ assert_equal ["title LIKE '%suffix'"], relation.order_values
+ end
+
+ def test_merging_with_order_without_binds
+ 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/or_test.rb b/activerecord/test/cases/relation/or_test.rb
index 065819e0f1..8623867864 100644
--- a/activerecord/test/cases/relation/or_test.rb
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -30,6 +30,11 @@ module ActiveRecord
assert_equal expected, Post.where("id = 1").or(Post.none).to_a
end
+ def test_or_with_large_number
+ expected = Post.where("id = 1 or id = 9223372036854775808").to_a
+ assert_equal expected, Post.where(id: 1).or(Post.where(id: 9223372036854775808)).to_a
+ end
+
def test_or_with_bind_params
assert_equal Post.find([1, 2]).sort_by(&:id), Post.where(id: 1).or(Post.where(id: 2)).sort_by(&:id)
end
diff --git a/activerecord/test/cases/relation/select_test.rb b/activerecord/test/cases/relation/select_test.rb
index 0577e6bfdb..586aaadd0a 100644
--- a/activerecord/test/cases/relation/select_test.rb
+++ b/activerecord/test/cases/relation/select_test.rb
@@ -7,9 +7,21 @@ module ActiveRecord
class SelectTest < ActiveRecord::TestCase
fixtures :posts
- def test_select_with_nil_agrument
+ def test_select_with_nil_argument
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
new file mode 100644
index 0000000000..e45531b4a9
--- /dev/null
+++ b/activerecord/test/cases/relation/update_all_test.rb
@@ -0,0 +1,324 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/author"
+require "models/category"
+require "models/comment"
+require "models/computer"
+require "models/developer"
+require "models/post"
+require "models/person"
+require "models/pet"
+require "models/toy"
+require "models/topic"
+require "models/tag"
+require "models/tagging"
+require "models/warehouse_thing"
+
+class UpdateAllTest < ActiveRecord::TestCase
+ fixtures :authors, :author_addresses, :comments, :developers, :posts, :people, :pets, :toys, :tags, :taggings, "warehouse-things"
+
+ class TopicWithCallbacks < ActiveRecord::Base
+ self.table_name = :topics
+ cattr_accessor :topic_count
+ before_update { |topic| topic.author_name = "David" if topic.author_name.blank? }
+ after_update { |topic| topic.class.topic_count = topic.class.count }
+ end
+
+ def test_update_all_with_scope
+ tag = Tag.first
+ Post.tagged_with(tag.id).update_all(title: "rofl")
+ posts = Post.tagged_with(tag.id).all.to_a
+ assert_operator posts.length, :>, 0
+ posts.each { |post| assert_equal "rofl", post.title }
+ end
+
+ def test_update_all_with_non_standard_table_name
+ assert_equal 1, WarehouseThing.where(id: 1).update_all(["value = ?", 0])
+ assert_equal 0, WarehouseThing.find(1).value
+ end
+
+ def test_update_all_with_blank_argument
+ assert_raises(ArgumentError) { Comment.update_all({}) }
+ end
+
+ def test_update_all_with_joins
+ pets = Pet.joins(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.update_all(name: "Bob")
+ end
+
+ def test_update_all_with_left_joins
+ pets = Pet.left_joins(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.update_all(name: "Bob")
+ end
+
+ def test_update_all_with_includes
+ pets = Pet.includes(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.update_all(name: "Bob")
+ end
+
+ def test_update_all_with_joins_and_limit
+ comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).limit(1)
+ assert_equal 1, comments.update_all(post_id: posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:greetings).post
+ end
+
+ def test_update_all_with_joins_and_limit_and_order
+ comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("comments.id").limit(1)
+ assert_equal 1, comments.update_all(post_id: posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:greetings).post
+ assert_equal posts(:welcome), comments(:more_greetings).post
+ end
+
+ def test_update_all_with_joins_and_offset
+ all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id)
+ count = all_comments.count
+ comments = all_comments.offset(1)
+
+ assert_equal count - 1, comments.update_all(post_id: posts(:thinking).id)
+ end
+
+ def test_update_all_with_joins_and_offset_and_order
+ all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("posts.id", "comments.id")
+ count = all_comments.count
+ comments = all_comments.offset(1)
+
+ assert_equal count - 1, comments.update_all(post_id: posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:more_greetings).post
+ assert_equal posts(:welcome), comments(:greetings).post
+ end
+
+ def test_update_counters_with_joins
+ assert_nil pets(:parrot).integer
+
+ Pet.joins(:toys).where(toys: { name: "Bone" }).update_counters(integer: 1)
+
+ assert_equal 1, pets(:parrot).reload.integer
+ end
+
+ def test_touch_all_updates_records_timestamps
+ david = developers(:david)
+ david_previously_updated_at = david.updated_at
+ jamis = developers(:jamis)
+ jamis_previously_updated_at = jamis.updated_at
+ Developer.where(name: "David").touch_all
+
+ assert_not_equal david_previously_updated_at, david.reload.updated_at
+ assert_equal jamis_previously_updated_at, jamis.reload.updated_at
+ end
+
+ def test_touch_all_with_custom_timestamp
+ developer = developers(:david)
+ previously_created_at = developer.created_at
+ previously_updated_at = developer.updated_at
+ Developer.where(name: "David").touch_all(:created_at)
+ developer.reload
+
+ assert_not_equal previously_created_at, developer.created_at
+ assert_not_equal previously_updated_at, developer.updated_at
+ end
+
+ def test_touch_all_with_given_time
+ developer = developers(:david)
+ previously_created_at = developer.created_at
+ previously_updated_at = developer.updated_at
+ new_time = Time.utc(2015, 2, 16, 4, 54, 0)
+ Developer.where(name: "David").touch_all(:created_at, time: new_time)
+ developer.reload
+
+ assert_not_equal previously_created_at, developer.created_at
+ assert_not_equal previously_updated_at, developer.updated_at
+ assert_equal new_time, developer.created_at
+ assert_equal new_time, developer.updated_at
+ end
+
+ def test_update_on_relation
+ topic1 = TopicWithCallbacks.create! title: "arel", author_name: nil
+ topic2 = TopicWithCallbacks.create! title: "activerecord", author_name: nil
+ topics = TopicWithCallbacks.where(id: [topic1.id, topic2.id])
+ topics.update(title: "adequaterecord")
+
+ assert_equal TopicWithCallbacks.count, TopicWithCallbacks.topic_count
+
+ assert_equal "adequaterecord", topic1.reload.title
+ assert_equal "adequaterecord", topic2.reload.title
+ # Testing that the before_update callbacks have run
+ assert_equal "David", topic1.reload.author_name
+ assert_equal "David", topic2.reload.author_name
+ end
+
+ def test_update_with_ids_on_relation
+ topic1 = TopicWithCallbacks.create!(title: "arel", author_name: nil)
+ topic2 = TopicWithCallbacks.create!(title: "activerecord", author_name: nil)
+ topics = TopicWithCallbacks.none
+ topics.update(
+ [topic1.id, topic2.id],
+ [{ title: "adequaterecord" }, { title: "adequaterecord" }]
+ )
+
+ assert_equal TopicWithCallbacks.count, TopicWithCallbacks.topic_count
+
+ assert_equal "adequaterecord", topic1.reload.title
+ assert_equal "adequaterecord", topic2.reload.title
+ # Testing that the before_update callbacks have run
+ assert_equal "David", topic1.reload.author_name
+ assert_equal "David", topic2.reload.author_name
+ end
+
+ def test_update_on_relation_passing_active_record_object_is_not_permitted
+ topic = Topic.create!(title: "Foo", author_name: nil)
+ assert_raises(ArgumentError) do
+ Topic.where(id: topic.id).update(topic, title: "Bar")
+ 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
+ author = authors(:david)
+ assert_nothing_raised do
+ assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ])
+ end
+ end
+
+ def test_update_all_doesnt_ignore_order
+ assert_equal authors(:david).id + 1, authors(:mary).id # make sure there is going to be a duplicate PK error
+ test_update_with_order_succeeds = lambda do |order|
+ Author.order(order).update_all("id = id + 1")
+ rescue ActiveRecord::ActiveRecordError
+ false
+ end
+
+ if test_update_with_order_succeeds.call("id DESC")
+ # test that this wasn't a fluke and using an incorrect order results in an exception
+ assert_not test_update_with_order_succeeds.call("id ASC")
+ else
+ # test that we're failing because the current Arel's engine doesn't support UPDATE ORDER BY queries is using subselects instead
+ assert_sql(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC\)\z/i) do
+ test_update_with_order_succeeds.call("id DESC")
+ end
+ end
+ end
+
+ def test_update_all_with_order_and_limit_updates_subset_only
+ author = authors(:david)
+ limited_posts = author.posts_sorted_by_id_limited
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
+ assert_equal "bulk update!", posts(:welcome).body
+ assert_not_equal "bulk update!", posts(:thinking).body
+ end
+
+ def test_update_all_with_order_and_limit_and_offset_updates_subset_only
+ author = authors(:david)
+ limited_posts = author.posts_sorted_by_id_limited.offset(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
+ assert_equal "bulk update!", posts(:thinking).body
+ assert_not_equal "bulk update!", posts(:welcome).body
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb
index 8703d238a0..0b06cec40b 100644
--- a/activerecord/test/cases/relation/where_clause_test.rb
+++ b/activerecord/test/cases/relation/where_clause_test.rb
@@ -92,12 +92,16 @@ class ActiveRecord::Relation
original = WhereClause.new([
table["id"].in([1, 2, 3]),
table["id"].eq(1),
+ table["id"].is_not_distinct_from(1),
+ table["id"].is_distinct_from(2),
"sql literal",
random_object
])
expected = WhereClause.new([
table["id"].not_in([1, 2, 3]),
table["id"].not_eq(1),
+ table["id"].is_distinct_from(1),
+ table["id"].is_not_distinct_from(2),
Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new("sql literal")),
Arel::Nodes::Not.new(random_object)
])
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index 99797528b2..b045184d7d 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -14,6 +14,7 @@ require "models/price_estimate"
require "models/topic"
require "models/treasure"
require "models/vertex"
+require "support/stubs/strong_parameters"
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
@@ -50,8 +51,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
@@ -334,31 +340,22 @@ 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
+ def test_where_with_large_number
+ assert_equal [authors(:bob)], Author.where(id: [3, 9223372036854775808])
+ assert_equal [authors(:bob)], Author.where(id: 3..9223372036854775808)
+ end
+
+ def test_to_sql_with_large_number
+ assert_equal [authors(:bob)], Author.find_by_sql(Author.where(id: [3, 9223372036854775808]).to_sql)
+ assert_equal [authors(:bob)], Author.find_by_sql(Author.where(id: 3..9223372036854775808).to_sql)
+ end
+
def test_where_with_unsupported_arguments
assert_raises(ArgumentError) { Author.where(42) }
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index fbeb617b29..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
@@ -232,7 +235,7 @@ module ActiveRecord
assert_equal 3, nb_inner_join, "Wrong amount of INNER JOIN in query"
# using `\W` as the column separator
- assert queries.any? { |sql| %r[INNER\s+JOIN\s+#{Author.quoted_table_name}\s+\Wauthors_categorizations\W]i.match?(sql) }, "Should be aliasing the child INNER JOINs in query"
+ assert queries.any? { |sql| %r[INNER\s+JOIN\s+#{Regexp.escape(Author.quoted_table_name)}\s+\Wauthors_categorizations\W]i.match?(sql) }, "Should be aliasing the child INNER JOINs in query"
end
def test_relation_with_merged_joins_aliased_works
@@ -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 952d2dd5d9..2417775ef1 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -9,10 +9,12 @@ require "models/comment"
require "models/author"
require "models/entrant"
require "models/developer"
+require "models/project"
require "models/person"
require "models/computer"
require "models/reply"
require "models/company"
+require "models/contract"
require "models/bird"
require "models/car"
require "models/engine"
@@ -28,11 +30,6 @@ require "models/subscriber"
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :author_addresses, :topics, :entrants, :developers, :people, :companies, :developers_projects, :accounts, :categories, :categorizations, :categories_posts, :posts, :comments, :tags, :taggings, :cars, :minivans
- class TopicWithCallbacks < ActiveRecord::Base
- self.table_name = :topics
- before_update { |topic| topic.author_name = "David" if topic.author_name.blank? }
- end
-
def test_do_not_double_quote_string_id
van = Minivan.last
assert van
@@ -185,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
@@ -294,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
@@ -484,21 +539,6 @@ class RelationTest < ActiveRecord::TestCase
assert_nothing_raised { Topic.reorder([]) }
end
- def test_respond_to_delegates_to_arel
- relation = Topic.all
- fake_arel = Struct.new(:responds) {
- def respond_to?(method, access = false)
- responds << [method, access]
- end
- }.new []
-
- relation.extend(Module.new { attr_accessor :arel })
- relation.arel = fake_arel
-
- relation.respond_to?(:matching_attributes)
- assert_equal [:matching_attributes, false], fake_arel.responds.first
- end
-
def test_respond_to_dynamic_finders
relation = Topic.all
@@ -562,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")
@@ -860,45 +907,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal authors(:bob), authors.last
end
- def test_destroy_all
- davids = Author.where(name: "David")
-
- # Force load
- assert_equal [authors(:david)], davids.to_a
- assert_predicate davids, :loaded?
-
- assert_difference("Author.count", -1) { davids.destroy_all }
-
- assert_equal [], davids.to_a
- assert_predicate davids, :loaded?
- end
-
- def test_delete_all
- davids = Author.where(name: "David")
-
- assert_difference("Author.count", -1) { davids.delete_all }
- assert_not_predicate davids, :loaded?
- end
-
- def test_delete_all_loaded
- davids = Author.where(name: "David")
-
- # Force load
- assert_equal [authors(:david)], davids.to_a
- assert_predicate davids, :loaded?
-
- assert_difference("Author.count", -1) { davids.delete_all }
-
- assert_equal [], davids.to_a
- assert_predicate davids, :loaded?
- end
-
- def test_delete_all_with_unpermitted_relation_raises_error
- assert_raises(ActiveRecord::ActiveRecordError) { Author.distinct.delete_all }
- assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all }
- assert_raises(ActiveRecord::ActiveRecordError) { Author.having("SUM(id) < 3").delete_all }
- end
-
def test_select_with_aggregates
posts = Post.select(:title, :body)
@@ -977,18 +985,23 @@ 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_update_all_with_scope
- tag = Tag.first
- Post.tagged_with(tag.id).update_all title: "rofl"
- list = Post.tagged_with(tag.id).all.to_a
- assert_operator list.length, :>, 0
- list.each { |post| assert_equal "rofl", post.title }
+ 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
@@ -1232,8 +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
- parrot = Bird.where(color: "green").first_or_create { |bird| bird.name = "parrot" }
+ Bird.create!(color: "yellow", name: "canary")
+ parrot = Bird.where(color: "green").first_or_create do |bird|
+ bird.name = "parrot"
+ assert_deprecated { assert_equal 0, Bird.count }
+ end
assert_kind_of Bird, parrot
assert_predicate parrot, :persisted?
assert_equal "green", parrot.color
@@ -1274,8 +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
- parrot = Bird.where(color: "green").first_or_create! { |bird| bird.name = "parrot" }
+ Bird.create!(color: "yellow", name: "canary")
+ parrot = Bird.where(color: "green").first_or_create! do |bird|
+ bird.name = "parrot"
+ assert_deprecated { assert_equal 0, Bird.count }
+ end
assert_kind_of Bird, parrot
assert_predicate parrot, :persisted?
assert_equal "green", parrot.color
@@ -1324,8 +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
- parrot = Bird.where(color: "green").first_or_initialize { |bird| bird.name = "parrot" }
+ Bird.create!(color: "yellow", name: "canary")
+ parrot = Bird.where(color: "green").first_or_initialize do |bird|
+ bird.name = "parrot"
+ assert_deprecated { assert_equal 0, Bird.count }
+ end
assert_kind_of Bird, parrot
assert_not_predicate parrot, :persisted?
assert_predicate parrot, :valid?
@@ -1366,6 +1424,13 @@ class RelationTest < ActiveRecord::TestCase
assert_not_equal subscriber, Subscriber.create_or_find_by(nick: "cat")
end
+ def test_create_or_find_by_should_not_raise_due_to_validation_errors
+ assert_nothing_raised do
+ bird = Bird.create_or_find_by(color: "green")
+ assert_predicate bird, :invalid?
+ end
+ end
+
def test_create_or_find_by_with_non_unique_attributes
Subscriber.create!(nick: "bob", name: "the builder")
@@ -1385,6 +1450,38 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_create_or_find_by_with_bang
+ assert_nil Subscriber.find_by(nick: "bob")
+
+ subscriber = Subscriber.create!(nick: "bob")
+
+ assert_equal subscriber, Subscriber.create_or_find_by!(nick: "bob")
+ assert_not_equal subscriber, Subscriber.create_or_find_by!(nick: "cat")
+ end
+
+ def test_create_or_find_by_with_bang_should_raise_due_to_validation_errors
+ assert_raises(ActiveRecord::RecordInvalid) { Bird.create_or_find_by!(color: "green") }
+ end
+
+ def test_create_or_find_by_with_bang_with_non_unique_attributes
+ Subscriber.create!(nick: "bob", name: "the builder")
+
+ assert_raises(ActiveRecord::RecordNotFound) do
+ Subscriber.create_or_find_by!(nick: "bob", name: "the cat")
+ end
+ end
+
+ def test_create_or_find_by_with_bang_within_transaction
+ assert_nil Subscriber.find_by(nick: "bob")
+
+ subscriber = Subscriber.create!(nick: "bob")
+
+ Subscriber.transaction do
+ assert_equal subscriber, Subscriber.create_or_find_by!(nick: "bob")
+ assert_not_equal subscriber, Subscriber.create_or_find_by!(nick: "cat")
+ end
+ end
+
def test_find_or_initialize_by
assert_nil Bird.find_by(name: "bob")
@@ -1403,15 +1500,27 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "cock", hens.new.name
end
+ def test_create_with_nested_attributes
+ assert_difference("Project.count", 1) do
+ developers = Developer.where(name: "Aaron")
+ developers = developers.create_with(
+ projects_attributes: [{ name: "p1" }]
+ )
+ developers.create!
+ end
+ end
+
def test_except
relation = Post.where(author_id: 1).order("id ASC").limit(1)
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
@@ -1419,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
@@ -1484,112 +1595,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal authors(:david), Author.order("id DESC , name DESC").last
end
- def test_update_all_with_blank_argument
- assert_raises(ArgumentError) { Comment.update_all({}) }
- end
-
- def test_update_all_with_joins
- comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id)
- count = comments.count
-
- assert_equal count, comments.update_all(post_id: posts(:thinking).id)
- assert_equal posts(:thinking), comments(:greetings).post
- end
-
- def test_update_all_with_joins_and_limit
- comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).limit(1)
- assert_equal 1, comments.update_all(post_id: posts(:thinking).id)
- end
-
- def test_update_all_with_joins_and_limit_and_order
- comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("comments.id").limit(1)
- assert_equal 1, comments.update_all(post_id: posts(:thinking).id)
- assert_equal posts(:thinking), comments(:greetings).post
- assert_equal posts(:welcome), comments(:more_greetings).post
- end
-
- def test_update_all_with_joins_and_offset
- all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id)
- count = all_comments.count
- comments = all_comments.offset(1)
-
- assert_equal count - 1, comments.update_all(post_id: posts(:thinking).id)
- end
-
- def test_update_all_with_joins_and_offset_and_order
- all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("posts.id", "comments.id")
- count = all_comments.count
- comments = all_comments.offset(1)
-
- assert_equal count - 1, comments.update_all(post_id: posts(:thinking).id)
- assert_equal posts(:thinking), comments(:more_greetings).post
- assert_equal posts(:welcome), comments(:greetings).post
- end
-
- def test_touch_all_updates_records_timestamps
- david = developers(:david)
- david_previously_updated_at = david.updated_at
- jamis = developers(:jamis)
- jamis_previously_updated_at = jamis.updated_at
- Developer.where(name: "David").touch_all
-
- assert_not_equal david_previously_updated_at, david.reload.updated_at
- assert_equal jamis_previously_updated_at, jamis.reload.updated_at
- end
-
- def test_touch_all_with_custom_timestamp
- developer = developers(:david)
- previously_created_at = developer.created_at
- previously_updated_at = developer.updated_at
- Developer.where(name: "David").touch_all(:created_at)
- developer = developer.reload
-
- assert_not_equal previously_created_at, developer.created_at
- assert_not_equal previously_updated_at, developer.updated_at
- end
-
- def test_touch_all_with_given_time
- developer = developers(:david)
- previously_created_at = developer.created_at
- previously_updated_at = developer.updated_at
- new_time = Time.utc(2015, 2, 16, 4, 54, 0)
- Developer.where(name: "David").touch_all(:created_at, time: new_time)
- developer = developer.reload
-
- assert_not_equal previously_created_at, developer.created_at
- assert_not_equal previously_updated_at, developer.updated_at
- assert_equal new_time, developer.created_at
- 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
- topics = TopicWithCallbacks.where(id: [topic1.id, topic2.id])
- topics.update(title: "adequaterecord")
-
- assert_equal "adequaterecord", topic1.reload.title
- assert_equal "adequaterecord", topic2.reload.title
- # Testing that the before_update callbacks have run
- assert_equal "David", topic1.reload.author_name
- assert_equal "David", topic2.reload.author_name
- end
-
- def test_update_on_relation_passing_active_record_object_is_not_permitted
- topic = Topic.create!(title: "Foo", author_name: nil)
- assert_raises(ArgumentError) do
- Topic.where(id: topic.id).update(topic, title: "Bar")
- end
- end
-
def test_distinct
tag1 = Tag.create(name: "Foo")
tag2 = Tag.create(name: "Foo")
@@ -1754,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
@@ -1923,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)
@@ -1941,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")
@@ -1959,6 +2019,16 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "Thank you for the welcome,Thank you again for the welcome", Post.first.comments.join(",")
end
+ def test_relation_with_private_kernel_method
+ accounts = Account.all
+ assert_equal [accounts(:signals37)], accounts.open
+ assert_equal [accounts(:signals37)], accounts.available
+
+ sub_accounts = SubAccount.all
+ assert_equal [accounts(:signals37)], sub_accounts.open
+ assert_equal [accounts(:signals37)], sub_accounts.available
+ end
+
test "#skip_query_cache!" do
Post.cache do
assert_queries(1) do
diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb
index 68fcafb682..825aee2423 100644
--- a/activerecord/test/cases/result_test.rb
+++ b/activerecord/test/cases/result_test.rb
@@ -21,12 +21,22 @@ module ActiveRecord
assert_equal 3, result.length
end
- test "to_hash returns row_hashes" do
+ test "to_a returns row_hashes" do
assert_equal [
{ "col_1" => "row 1 col 1", "col_2" => "row 1 col 2" },
{ "col_1" => "row 2 col 1", "col_2" => "row 2 col 2" },
{ "col_1" => "row 3 col 1", "col_2" => "row 3 col 2" },
- ], result.to_hash
+ ], result.to_a
+ end
+
+ test "to_hash (deprecated) returns row_hashes" do
+ assert_deprecated do
+ assert_equal [
+ { "col_1" => "row 1 col 1", "col_2" => "row 1 col 2" },
+ { "col_1" => "row 2 col 1", "col_2" => "row 2 col 2" },
+ { "col_1" => "row 3 col 1", "col_2" => "row 3 col 2" },
+ ], result.to_hash
+ end
end
test "first returns first row as a hash" do
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 778cf86ac3..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("?", "")
@@ -168,12 +181,6 @@ class SanitizeTest < ActiveRecord::TestCase
assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call
end
- def test_deprecated_expand_hash_conditions_for_aggregates
- assert_deprecated do
- assert_equal({ "balance" => 50 }, Customer.send(:expand_hash_conditions_for_aggregates, balance: Money.new(50)))
- end
- end
-
private
def bind(statement, *vars)
if vars.first.is_a?(Hash)
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 75b68b521c..49e9be9565 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -226,27 +226,50 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.float\s+"temperature"$}, output
end
+ if ActiveRecord::Base.connection.supports_expression_index?
+ def test_schema_dump_expression_indices
+ index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
+ index_definition.sub!(/, name: "company_expression_index"\z/, "")
+
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_match %r{CASE.+lower\(\(name\)::text\).+END\) DESC"\z}i, index_definition
+ elsif current_adapter?(:Mysql2Adapter)
+ assert_match %r{CASE.+lower\(`name`\).+END\) DESC"\z}i, index_definition
+ elsif current_adapter?(:SQLite3Adapter)
+ assert_match %r{CASE.+lower\(name\).+END\) DESC"\z}i, index_definition
+ else
+ assert false
+ end
+ end
+ end
+
if current_adapter?(:Mysql2Adapter)
def test_schema_dump_includes_length_for_mysql_binary_fields
- output = standard_dump
+ output = dump_table_schema "binary_fields"
assert_match %r{t\.binary\s+"var_binary",\s+limit: 255$}, output
assert_match %r{t\.binary\s+"var_binary_large",\s+limit: 4095$}, output
end
def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
- output = standard_dump
- assert_match %r{t\.blob\s+"tiny_blob",\s+limit: 255$}, output
+ output = dump_table_schema "binary_fields"
+ assert_match %r{t\.binary\s+"tiny_blob",\s+size: :tiny$}, output
assert_match %r{t\.binary\s+"normal_blob"$}, output
- assert_match %r{t\.binary\s+"medium_blob",\s+limit: 16777215$}, output
- assert_match %r{t\.binary\s+"long_blob",\s+limit: 4294967295$}, output
- assert_match %r{t\.text\s+"tiny_text",\s+limit: 255$}, output
+ assert_match %r{t\.binary\s+"medium_blob",\s+size: :medium$}, output
+ assert_match %r{t\.binary\s+"long_blob",\s+size: :long$}, output
+ assert_match %r{t\.text\s+"tiny_text",\s+size: :tiny$}, output
assert_match %r{t\.text\s+"normal_text"$}, output
- assert_match %r{t\.text\s+"medium_text",\s+limit: 16777215$}, output
- assert_match %r{t\.text\s+"long_text",\s+limit: 4294967295$}, output
+ assert_match %r{t\.text\s+"medium_text",\s+size: :medium$}, output
+ assert_match %r{t\.text\s+"long_text",\s+size: :long$}, output
+ assert_match %r{t\.binary\s+"tiny_blob_2",\s+size: :tiny$}, output
+ assert_match %r{t\.binary\s+"medium_blob_2",\s+size: :medium$}, output
+ assert_match %r{t\.binary\s+"long_blob_2",\s+size: :long$}, output
+ assert_match %r{t\.text\s+"tiny_text_2",\s+size: :tiny$}, output
+ assert_match %r{t\.text\s+"medium_text_2",\s+size: :medium$}, output
+ assert_match %r{t\.text\s+"long_text_2",\s+size: :long$}, output
end
def test_schema_does_not_include_limit_for_emulated_mysql_boolean_fields
- output = standard_dump
+ output = dump_table_schema "booleans"
assert_no_match %r{t\.boolean\s+"has_fun",.+limit: 1}, output
end
@@ -278,11 +301,6 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.decimal\s+"decimal_array_default",\s+default: \["1.23", "3.45"\],\s+array: true}, output
end
- def test_schema_dump_expression_indices
- index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
- assert_match %r{CASE.+lower\(\(name\)::text\)}i, index_definition
- end
-
def test_schema_dump_interval_type
output = dump_table_schema "postgresql_times"
assert_match %r{t\.interval\s+"time_interval"$}, output
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index e3a34aa50d..e7bdab58c6 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -4,6 +4,7 @@ require "cases/helper"
require "models/post"
require "models/comment"
require "models/developer"
+require "models/project"
require "models/computer"
require "models/vehicle"
require "models/cat"
@@ -366,6 +367,21 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal "Jamis", jamis.name
end
+ def test_create_with_takes_precedence_over_where
+ developer = Developer.where(name: nil).create_with(name: "Aaron").new
+ assert_equal "Aaron", developer.name
+ end
+
+ def test_create_with_nested_attributes
+ assert_difference("Project.count", 1) do
+ Developer.create_with(
+ projects_attributes: [{ name: "p1" }]
+ ).scoping do
+ Developer.create!(name: "Aaron")
+ end
+ end
+ end
+
# FIXME: I don't know if this is *desired* behavior, but it is *today's*
# behavior.
def test_create_with_empty_hash_will_not_reset
@@ -392,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 4214f347fb..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
@@ -303,13 +303,6 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal "lifo", topic.author_name
end
- def test_deprecated_delegating_private_method
- assert_deprecated do
- scope = Topic.all.by_private_lifo
- assert_not scope.instance_variable_get(:@delegate_to_klass)
- end
- end
-
def test_reserved_scope_names
klass = Class.new(ActiveRecord::Base) do
self.table_name = "topics"
@@ -454,6 +447,17 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).to_a.uniq
end
+ def test_class_method_in_scope
+ assert_deprecated do
+ assert_equal [topics(:second)], topics(:first).approved_replies.ordered
+ end
+ end
+
+ def test_nested_scoping
+ expected = Reply.approved
+ assert_equal expected.to_a, Topic.rejected.nested_scoping(expected)
+ end
+
def test_scopes_batch_finders
assert_equal 4, Topic.approved.count
@@ -489,7 +493,7 @@ class NamedScopingTest < ActiveRecord::TestCase
[:public_method, :protected_method, :private_method].each do |reserved_method|
assert Topic.respond_to?(reserved_method, true)
assert_called(ActiveRecord::Base.logger, :warn) do
- silence_warnings { Topic.scope(reserved_method, -> {}) }
+ silence_warnings { Topic.scope(reserved_method, -> { }) }
end
end
end
@@ -598,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 544adc9b39..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
@@ -236,8 +274,8 @@ class RelationScopingTest < ActiveRecord::TestCase
SpecialComment.unscoped.created
end
- assert_nil Comment.current_scope
- assert_nil SpecialComment.current_scope
+ assert_nil Comment.send(:current_scope)
+ assert_nil SpecialComment.send(:current_scope)
end
def test_scoping_respects_current_class
@@ -254,11 +292,16 @@ class RelationScopingTest < ActiveRecord::TestCase
end
end
- def test_scoping_works_in_the_scope_block
+ def test_scoping_with_klass_method_works_in_the_scope_block
expected = SpecialPostWithDefaultScope.unscoped.to_a
assert_equal expected, SpecialPostWithDefaultScope.unscoped_all
end
+ def test_scoping_with_query_method_works_in_the_scope_block
+ expected = SpecialPostWithDefaultScope.unscoped.where(author_id: 0).to_a
+ assert_equal expected, SpecialPostWithDefaultScope.authorless
+ end
+
def test_circular_joins_with_scoping_does_not_crash
posts = Post.joins(comments: :post).scoping do
Post.first(10)
@@ -368,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
@@ -409,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 4cd4515c3b..ecf81b2042 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -1,18 +1,23 @@
# 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
+ class Topic < ActiveRecord::Base
+ serialize :content
+ end
+
+ class ImportantTopic < Topic
+ serialize :important, Hash
+ end
+
teardown do
Topic.serialize("content")
end
@@ -49,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
@@ -322,7 +327,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic = Topic.create!(content: {})
topic2 = Topic.create!(content: nil)
- assert_equal [topic, topic2], Topic.where(content: nil)
+ assert_equal [topic, topic2], Topic.where(content: nil).sort_by(&:id)
end
def test_nil_is_always_persisted_as_null
@@ -367,9 +372,9 @@ 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
diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb
index e3c12f68fd..6a6d73dc38 100644
--- a/activerecord/test/cases/statement_cache_test.rb
+++ b/activerecord/test/cases/statement_cache_test.rb
@@ -4,6 +4,7 @@ require "cases/helper"
require "models/book"
require "models/liquid"
require "models/molecule"
+require "models/numeric_data"
require "models/electron"
module ActiveRecord
@@ -74,6 +75,11 @@ module ActiveRecord
assert_equal "salty", liquids[0].name
end
+ def test_statement_cache_with_strictly_cast_attribute
+ row = NumericData.create(temperature: 1.5)
+ assert_equal row, NumericData.find_by(temperature: 1.5)
+ end
+
def test_statement_cache_values_differ
cache = ActiveRecord::StatementCache.create(Book.connection) do |params|
Book.where(name: "my book")
diff --git a/activerecord/test/cases/statement_invalid_test.rb b/activerecord/test/cases/statement_invalid_test.rb
new file mode 100644
index 0000000000..16ea69c1bd
--- /dev/null
+++ b/activerecord/test/cases/statement_invalid_test.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/book"
+
+module ActiveRecord
+ class StatementInvalidTest < ActiveRecord::TestCase
+ fixtures :books
+
+ class MockDatabaseError < StandardError
+ def result
+ 0
+ end
+
+ def error_number
+ 0
+ end
+ end
+
+ test "message contains no sql" do
+ sql = Book.where(author_id: 96, cover: "hard").to_sql
+ error = assert_raises(ActiveRecord::StatementInvalid) do
+ Book.connection.send(:log, sql, Book.name) do
+ raise MockDatabaseError
+ end
+ end
+ assert_not error.message.include?("SELECT")
+ end
+
+ test "statement and binds are set on select" do
+ sql = Book.where(author_id: 96, cover: "hard").to_sql
+ binds = [Minitest::Mock.new, Minitest::Mock.new]
+ error = assert_raises(ActiveRecord::StatementInvalid) do
+ Book.connection.send(:log, sql, Book.name, binds) do
+ raise MockDatabaseError
+ end
+ end
+ assert_equal error.sql, sql
+ assert_equal error.binds, binds
+ end
+ 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/suppressor_test.rb b/activerecord/test/cases/suppressor_test.rb
index b68f0033d9..9be5356901 100644
--- a/activerecord/test/cases/suppressor_test.rb
+++ b/activerecord/test/cases/suppressor_test.rb
@@ -66,7 +66,7 @@ class SuppressorTest < ActiveRecord::TestCase
def test_suppresses_when_nested_multiple_times
assert_no_difference -> { Notification.count } do
Notification.suppress do
- Notification.suppress {}
+ Notification.suppress { }
Notification.create
Notification.create!
Notification.new.save
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index b9cc08c446..dd4a0b0455 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
@@ -46,35 +47,51 @@ module ActiveRecord
class DatabaseTasksUtilsTask < ActiveRecord::TestCase
def test_raises_an_error_when_called_with_protected_environment
- ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
-
protected_environments = ActiveRecord::Base.protected_environments
current_env = ActiveRecord::Base.connection.migration_context.current_environment
- assert_not_includes protected_environments, current_env
- # Assert no error
- ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
- ActiveRecord::Base.protected_environments = [current_env]
+ InternalMetadata[:environment] = current_env
- assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ assert_called_on_instance_of(
+ ActiveRecord::MigrationContext,
+ :current_version,
+ times: 6,
+ returns: 1
+ ) do
+ assert_not_includes protected_environments, current_env
+ # Assert no error
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+
+ ActiveRecord::Base.protected_environments = [current_env]
+
+ assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
end
ensure
ActiveRecord::Base.protected_environments = protected_environments
end
def test_raises_an_error_when_called_with_protected_environment_which_name_is_a_symbol
- ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
-
protected_environments = ActiveRecord::Base.protected_environments
current_env = ActiveRecord::Base.connection.migration_context.current_environment
- assert_not_includes protected_environments, current_env
- # Assert no error
- ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
- ActiveRecord::Base.protected_environments = [current_env.to_sym]
- assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ InternalMetadata[:environment] = current_env
+
+ assert_called_on_instance_of(
+ ActiveRecord::MigrationContext,
+ :current_version,
+ times: 6,
+ returns: 1
+ ) do
+ assert_not_includes protected_environments, current_env
+ # Assert no error
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+
+ ActiveRecord::Base.protected_environments = [current_env.to_sym]
+ assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
end
ensure
ActiveRecord::Base.protected_environments = protected_environments
@@ -82,10 +99,14 @@ module ActiveRecord
def test_raises_an_error_if_no_migrations_have_been_made
ActiveRecord::InternalMetadata.stub(:table_exists?, false) do
- ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
-
- assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do
- ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ assert_called_on_instance_of(
+ ActiveRecord::MigrationContext,
+ :current_version,
+ returns: 1
+ ) do
+ assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
end
end
end
@@ -152,7 +173,7 @@ module ActiveRecord
end
def test_ignores_configurations_without_databases
- @configurations["development"].merge!("database" => nil)
+ @configurations["development"]["database"] = nil
with_stubbed_configurations_establish_connection do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
@@ -162,7 +183,7 @@ module ActiveRecord
end
def test_ignores_remote_databases
- @configurations["development"].merge!("host" => "my.server.tld")
+ @configurations["development"]["host"] = "my.server.tld"
with_stubbed_configurations_establish_connection do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
@@ -172,7 +193,7 @@ module ActiveRecord
end
def test_warning_for_remote_databases
- @configurations["development"].merge!("host" => "my.server.tld")
+ @configurations["development"]["host"] = "my.server.tld"
with_stubbed_configurations_establish_connection do
ActiveRecord::Tasks::DatabaseTasks.create_all
@@ -183,7 +204,7 @@ module ActiveRecord
end
def test_creates_configurations_with_local_ip
- @configurations["development"].merge!("host" => "127.0.0.1")
+ @configurations["development"]["host"] = "127.0.0.1"
with_stubbed_configurations_establish_connection do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
@@ -193,7 +214,7 @@ module ActiveRecord
end
def test_creates_configurations_with_local_host
- @configurations["development"].merge!("host" => "localhost")
+ @configurations["development"]["host"] = "localhost"
with_stubbed_configurations_establish_connection do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
@@ -203,7 +224,7 @@ module ActiveRecord
end
def test_creates_configurations_with_blank_hosts
- @configurations["development"].merge!("host" => nil)
+ @configurations["development"]["host"] = nil
with_stubbed_configurations_establish_connection do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
@@ -213,18 +234,17 @@ module ActiveRecord
end
private
-
def with_stubbed_configurations_establish_connection
- ActiveRecord::Base.stub(:configurations, @configurations) do
- # To refrain from connecting to a newly created empty DB in
- # sqlite3_mem tests
- ActiveRecord::Base.connection_handler.stub(
- :establish_connection,
- nil
- ) do
- yield
- end
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ # To refrain from connecting to a newly created empty DB in
+ # sqlite3_mem tests
+ ActiveRecord::Base.connection_handler.stub(:establish_connection, nil) do
+ yield
end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
end
end
@@ -233,7 +253,7 @@ module ActiveRecord
@configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
- "production" => { "url" => "prod-db-url" }
+ "production" => { "url" => "abstract://prod-db-host/prod-db" }
}
end
@@ -256,7 +276,7 @@ module ActiveRecord
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
- ["url" => "prod-db-url"],
+ ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"],
) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new("production")
@@ -315,13 +335,15 @@ module ActiveRecord
end
private
-
def with_stubbed_configurations_establish_connection
- ActiveRecord::Base.stub(:configurations, @configurations) do
- ActiveRecord::Base.stub(:establish_connection, nil) do
- yield
- end
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ ActiveRecord::Base.connection_handler.stub(:establish_connection, nil) do
+ yield
end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
end
end
@@ -330,7 +352,7 @@ module ActiveRecord
@configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
- "production" => { "primary" => { "url" => "prod-db-url" }, "secondary" => { "url" => "secondary-prod-db-url" } }
+ "production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } }
}
end
@@ -357,8 +379,8 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks,
:create,
[
- ["url" => "prod-db-url"],
- ["url" => "secondary-prod-db-url"]
+ ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"],
+ ["adapter" => "abstract", "database" => "secondary-prod-db", "host" => "secondary-prod-db-host"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
@@ -426,13 +448,15 @@ module ActiveRecord
end
private
-
def with_stubbed_configurations_establish_connection
- ActiveRecord::Base.stub(:configurations, @configurations) do
- ActiveRecord::Base.stub(:establish_connection, nil) do
- yield
- end
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ ActiveRecord::Base.connection_handler.stub(:establish_connection, nil) do
+ yield
end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
end
end
@@ -463,9 +487,9 @@ module ActiveRecord
end
def test_ignores_configurations_without_databases
- @configurations[:development].merge!("database" => nil)
+ @configurations[:development]["database"] = nil
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
@@ -473,9 +497,9 @@ module ActiveRecord
end
def test_ignores_remote_databases
- @configurations[:development].merge!("host" => "my.server.tld")
+ @configurations[:development]["host"] = "my.server.tld"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
@@ -483,9 +507,9 @@ module ActiveRecord
end
def test_warning_for_remote_databases
- @configurations[:development].merge!("host" => "my.server.tld")
+ @configurations[:development]["host"] = "my.server.tld"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
ActiveRecord::Tasks::DatabaseTasks.drop_all
assert_match "This task only modifies local databases. my-db is on a remote host.",
@@ -494,9 +518,9 @@ module ActiveRecord
end
def test_drops_configurations_with_local_ip
- @configurations[:development].merge!("host" => "127.0.0.1")
+ @configurations[:development]["host"] = "127.0.0.1"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
@@ -504,9 +528,9 @@ module ActiveRecord
end
def test_drops_configurations_with_local_host
- @configurations[:development].merge!("host" => "localhost")
+ @configurations[:development]["host"] = "localhost"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
@@ -514,14 +538,24 @@ module ActiveRecord
end
def test_drops_configurations_with_blank_hosts
- @configurations[:development].merge!("host" => nil)
+ @configurations[:development]["host"] = nil
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
end
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 DatabaseTasksDropCurrentTest < ActiveRecord::TestCase
@@ -529,12 +563,12 @@ module ActiveRecord
@configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
- "production" => { "url" => "prod-db-url" }
+ "production" => { "url" => "abstract://prod-db-host/prod-db" }
}
end
def test_drops_current_environment_database
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
["database" => "test-db"]) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
@@ -545,9 +579,9 @@ module ActiveRecord
end
def test_drops_current_environment_database_with_url
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
- ["url" => "prod-db-url"]) do
+ ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"]) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new("production")
)
@@ -556,7 +590,7 @@ module ActiveRecord
end
def test_drops_test_and_development_databases_when_env_was_not_specified
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -576,7 +610,7 @@ module ActiveRecord
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -593,6 +627,16 @@ module ActiveRecord
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 DatabaseTasksDropCurrentThreeTierTest < ActiveRecord::TestCase
@@ -600,12 +644,12 @@ module ActiveRecord
@configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
- "production" => { "primary" => { "url" => "prod-db-url" }, "secondary" => { "url" => "secondary-prod-db-url" } }
+ "production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } }
}
end
def test_drops_current_environment_database
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -622,13 +666,13 @@ module ActiveRecord
end
def test_drops_current_environment_database_with_url
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
[
- ["url" => "prod-db-url"],
- ["url" => "secondary-prod-db-url"]
+ ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"],
+ ["adapter" => "abstract", "database" => "secondary-prod-db", "host" => "secondary-prod-db-host"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
@@ -639,7 +683,7 @@ module ActiveRecord
end
def test_drops_test_and_development_databases_when_env_was_not_specified
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -661,7 +705,7 @@ module ActiveRecord
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -680,10 +724,20 @@ module ActiveRecord
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
if current_adapter?(:SQLite3Adapter) && !in_memory_db?
- class DatabaseTasksMigrateTest < ActiveRecord::TestCase
+ class DatabaseTasksMigrationTestCase < ActiveRecord::TestCase
self.use_transactional_tests = false
# Use a memory db here to avoid having to rollback at the end
@@ -703,7 +757,9 @@ module ActiveRecord
@conn.release_connection if @conn
ActiveRecord::Base.establish_connection :arunit
end
+ end
+ class DatabaseTasksMigrateTest < DatabaseTasksMigrationTestCase
def test_migrate_set_and_unset_verbose_and_version_env_vars
verbose, version = ENV["VERBOSE"], ENV["VERSION"]
ENV["VERSION"] = "2"
@@ -764,6 +820,26 @@ module ActiveRecord
end
end
end
+
+ class DatabaseTasksMigrateStatusTest < DatabaseTasksMigrationTestCase
+ def test_migrate_status_table
+ ActiveRecord::SchemaMigration.create_table
+ output = capture_migration_status
+ assert_match(/database: :memory:/, output)
+ assert_match(/down 001 Valid people have last names/, output)
+ assert_match(/down 002 We need reminders/, output)
+ assert_match(/down 003 Innocent jointable/, output)
+ ActiveRecord::SchemaMigration.drop_table
+ end
+
+ private
+
+ def capture_migration_status
+ capture(:stdout) do
+ ActiveRecord::Tasks::DatabaseTasks.migrate_status
+ end
+ end
+ end
end
class DatabaseTasksMigrateErrorTest < ActiveRecord::TestCase
@@ -833,38 +909,215 @@ module ActiveRecord
class DatabaseTasksPurgeCurrentTest < ActiveRecord::TestCase
def test_purges_current_environment_database
+ old_configurations = ActiveRecord::Base.configurations
configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
"production" => { "database" => "prod-db" }
}
- ActiveRecord::Base.stub(:configurations, configurations) do
- assert_called_with(
- ActiveRecord::Tasks::DatabaseTasks,
- :purge,
- ["database" => "prod-db"]
- ) do
- assert_called_with(ActiveRecord::Base, :establish_connection, [:production]) do
- ActiveRecord::Tasks::DatabaseTasks.purge_current("production")
- end
+
+ ActiveRecord::Base.configurations = configurations
+
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :purge,
+ ["database" => "prod-db"]
+ ) do
+ assert_called_with(ActiveRecord::Base, :establish_connection, [:production]) do
+ ActiveRecord::Tasks::DatabaseTasks.purge_current("production")
end
end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
end
end
class DatabaseTasksPurgeAllTest < ActiveRecord::TestCase
def test_purge_all_local_configurations
+ old_configurations = ActiveRecord::Base.configurations
configurations = { development: { "database" => "my-db" } }
- ActiveRecord::Base.stub(:configurations, configurations) do
+ ActiveRecord::Base.configurations = configurations
+
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :purge,
+ ["database" => "my-db"]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge_all
+ end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
+ 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,
- :purge,
- ["database" => "my-db"]
+ :truncate_tables,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"]
+ ]
) do
- ActiveRecord::Tasks::DatabaseTasks.purge_all
+ 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
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index eeb4222d97..552e623fd4 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -152,10 +152,14 @@ if current_adapter?(:Mysql2Adapter)
end
def test_establishes_connection_to_mysql_database
- with_stubbed_connection_establish_connection do
- ActiveRecord::Base.expects(:establish_connection).with @configuration
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [@configuration]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
end
end
@@ -196,10 +200,14 @@ if current_adapter?(:Mysql2Adapter)
end
def test_establishes_connection_to_the_appropriate_database
- with_stubbed_connection_establish_connection do
- ActiveRecord::Base.expects(:establish_connection).with(@configuration)
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [@configuration]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
end
end
@@ -264,7 +272,7 @@ if current_adapter?(:Mysql2Adapter)
def test_db_retrieves_collation
ActiveRecord::Base.stub(:connection, @connection) do
- assert_called_with(@connection, :collation) do
+ assert_called(@connection, :collation) do
ActiveRecord::Tasks::DatabaseTasks.collation @configuration
end
end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index 00005e7a0d..065ba7734c 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -112,7 +112,7 @@ if current_adapter?(:PostgreSQLAdapter)
ActiveRecord::Base.stub(:connection, @connection) do
ActiveRecord::Base.stub(:establish_connection, -> * { raise Exception }) do
assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration }
- assert_match "Couldn't create database for #{@configuration.inspect}", $stderr.string
+ assert_match "Couldn't create '#{@configuration['database']}' database. Please check your configuration.", $stderr.string
end
end
end
@@ -166,12 +166,17 @@ if current_adapter?(:PostgreSQLAdapter)
def test_establishes_connection_to_postgresql_database
ActiveRecord::Base.stub(:connection, @connection) do
- ActiveRecord::Base.expects(:establish_connection).with(
- "adapter" => "postgresql",
- "database" => "postgres",
- "schema_search_path" => "public"
- )
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ "adapter" => "postgresql",
+ "database" => "postgres",
+ "schema_search_path" => "public"
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
end
end
@@ -491,7 +496,7 @@ if current_adapter?(:PostgreSQLAdapter)
assert_called_with(
Kernel,
:system,
- ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, @configuration["database"]],
+ ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-X", "-f", filename, @configuration["database"]],
returns: true
) do
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
@@ -500,7 +505,7 @@ if current_adapter?(:PostgreSQLAdapter)
def test_structure_load_with_extra_flags
filename = "awesome-file.sql"
- expected_command = ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, "--noop", @configuration["database"]]
+ expected_command = ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-X", "-f", filename, "--noop", @configuration["database"]]
assert_called_with(Kernel, :system, expected_command, returns: true) do
with_structure_load_flags(["--noop"]) do
@@ -514,7 +519,7 @@ if current_adapter?(:PostgreSQLAdapter)
assert_called_with(
Kernel,
:system,
- ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, @configuration["database"]],
+ ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-X", "-f", filename, @configuration["database"]],
returns: true
) do
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index 7eb062b456..c1092b97c1 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -47,9 +47,9 @@ if current_adapter?(:SQLite3Adapter)
def test_db_create_with_file_does_nothing
File.stub(:exist?, true) do
- ActiveRecord::Base.expects(:establish_connection).never
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ assert_not_called(ActiveRecord::Base, :establish_connection) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ end
end
end
@@ -62,7 +62,7 @@ if current_adapter?(:SQLite3Adapter)
def test_db_create_with_error_prints_message
ActiveRecord::Base.stub(:establish_connection, proc { raise Exception }) do
assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" }
- assert_match "Couldn't create database for #{@configuration.inspect}", $stderr.string
+ assert_match "Couldn't create '#{@configuration['database']}' database. Please check your configuration.", $stderr.string
end
end
end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 409b07e56c..5b25432dc0 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -31,6 +31,7 @@ module ActiveRecord
end
def capture_sql
+ ActiveRecord::Base.connection.materialize_transactions
SQLCounter.clear_log
yield
SQLCounter.log_all.dup
@@ -48,6 +49,7 @@ module ActiveRecord
def assert_queries(num = 1, options = {})
ignore_none = options.fetch(:ignore_none) { num == :any }
+ ActiveRecord::Base.connection.materialize_transactions
SQLCounter.clear_log
x = yield
the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log
@@ -77,10 +79,6 @@ module ActiveRecord
model.reset_column_information
model.column_names.include?(column_name.to_s)
end
-
- def frozen_error_class
- Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError
- end
end
class PostgreSQLTestCase < TestCase
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/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb
index 925a4609a2..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?
@@ -100,7 +100,7 @@ class TouchLaterTest < ActiveRecord::TestCase
def test_touch_later_dont_hit_the_db
invoice = Invoice.create!
- assert_queries(0) do
+ assert_no_queries do
invoice.touch_later
end
end
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index c0be45eee7..e88d20a453 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -38,6 +38,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
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 +111,17 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal [:after_commit], @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
@@ -591,6 +603,17 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase
assert_equal [:before_commit, :after_commit], @topic.history
end
+ def test_commit_run_transactions_callbacks_with_nested_transactions
+ @topic.transaction do
+ @topic.transaction(requires_new: true) do
+ @topic.content = "foo"
+ @topic.save!
+ @topic.class.connection.add_transaction_record(@topic)
+ end
+ end
+ assert_equal [:before_commit, :after_commit], @topic.history
+ end
+
def test_rollback_does_not_run_transactions_callbacks_without_enrollment
@topic.transaction do
@topic.content = "foo"
diff --git a/activerecord/test/cases/transaction_isolation_test.rb b/activerecord/test/cases/transaction_isolation_test.rb
index eaafd13360..2932969412 100644
--- a/activerecord/test/cases/transaction_isolation_test.rb
+++ b/activerecord/test/cases/transaction_isolation_test.rb
@@ -11,7 +11,7 @@ unless ActiveRecord::Base.connection.supports_transaction_isolation?
test "setting the isolation level raises an error" do
assert_raises(ActiveRecord::TransactionIsolationError) do
- Tag.transaction(isolation: :serializable) {}
+ Tag.transaction(isolation: :serializable) { Tag.connection.materialize_transactions }
end
end
end
@@ -90,7 +90,7 @@ else
test "setting isolation when joining a transaction raises an error" do
Tag.transaction do
assert_raises(ActiveRecord::TransactionIsolationError) do
- Tag.transaction(isolation: :serializable) {}
+ Tag.transaction(isolation: :serializable) { }
end
end
end
@@ -98,7 +98,7 @@ else
test "setting isolation when starting a nested transaction raises error" do
Tag.transaction do
assert_raises(ActiveRecord::TransactionIsolationError) do
- Tag.transaction(requires_new: true, isolation: :serializable) {}
+ Tag.transaction(requires_new: true, isolation: :serializable) { }
end
end
end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 46463ac414..410f07d3ab 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -34,20 +34,21 @@ class TransactionTest < ActiveRecord::TestCase
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
@@ -76,11 +77,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 +102,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 +118,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 +144,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 +157,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 +168,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 +186,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
@@ -575,7 +588,7 @@ class TransactionTest < ActiveRecord::TestCase
assert_called(Topic.connection, :rollback_db_transaction) do
e = assert_raise RuntimeError do
Topic.transaction do
- # do nothing
+ Topic.connection.materialize_transactions
end
end
assert_equal "OH NOES", e.message
@@ -587,7 +600,7 @@ class TransactionTest < ActiveRecord::TestCase
def test_rollback_when_saving_a_frozen_record
topic = Topic.new(title: "test")
topic.freeze
- e = assert_raise(frozen_error_class) { topic.save }
+ e = assert_raise(FrozenError) { topic.save }
# Not good enough, but we can't do much
# about it since there is no specific error
# for frozen objects.
@@ -884,17 +897,6 @@ class TransactionTest < ActiveRecord::TestCase
assert_predicate transaction.state, :committed?
end
- def test_set_state_method_is_deprecated
- connection = Topic.connection
- transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
-
- transaction.commit
-
- assert_deprecated do
- transaction.state.set_state(:rolledback)
- end
- end
-
def test_mark_transaction_state_as_committed
connection = Topic.connection
transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
@@ -930,7 +932,7 @@ class TransactionTest < ActiveRecord::TestCase
klass = Class.new(ActiveRecord::Base) do
self.table_name = "transaction_without_primary_keys"
- after_commit {} # necessary to trigger the has_transactional_callbacks branch
+ after_commit { } # necessary to trigger the has_transactional_callbacks branch
end
assert_no_difference(-> { klass.count }) do
@@ -943,6 +945,76 @@ class TransactionTest < ActiveRecord::TestCase
connection.drop_table "transaction_without_primary_keys", if_exists: true
end
+ def test_empty_transaction_is_not_materialized
+ assert_no_queries do
+ Topic.transaction { }
+ end
+ end
+
+ def test_unprepared_statement_materializes_transaction
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction { Topic.where("1=1").first }
+ end
+ end
+
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_prepared_statement_materializes_transaction
+ Topic.first
+
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction { Topic.first }
+ end
+ end
+ end
+
+ def test_savepoint_does_not_materialize_transaction
+ assert_no_queries do
+ Topic.transaction do
+ Topic.transaction(requires_new: true) { }
+ end
+ end
+ end
+
+ def test_raising_does_not_materialize_transaction
+ assert_raise(RuntimeError) do
+ assert_no_queries do
+ Topic.transaction { raise }
+ end
+ end
+ end
+
+ def test_accessing_raw_connection_materializes_transaction
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction { Topic.connection.raw_connection }
+ end
+ end
+
+ def test_accessing_raw_connection_disables_lazy_transactions
+ Topic.connection.raw_connection
+
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction { }
+ end
+ end
+
+ def test_checking_in_connection_reenables_lazy_transactions
+ connection = Topic.connection_pool.checkout
+ connection.raw_connection
+ Topic.connection_pool.checkin connection
+
+ assert_no_queries do
+ connection.transaction { }
+ end
+ end
+
+ def test_transactions_can_be_manually_materialized
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction do
+ Topic.connection.materialize_transactions
+ end
+ end
+ end
+
private
%w(validation save destroy).each do |filter|
diff --git a/activerecord/test/cases/type/time_test.rb b/activerecord/test/cases/type/time_test.rb
new file mode 100644
index 0000000000..1a2c47479f
--- /dev/null
+++ b/activerecord/test/cases/type/time_test.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/topic"
+
+module ActiveRecord
+ module Type
+ class TimeTest < ActiveRecord::TestCase
+ def test_default_year_is_correct
+ expected_time = ::Time.utc(2000, 1, 1, 10, 30, 0)
+ topic = Topic.new(bonus_time: { 4 => 10, 5 => 30 })
+
+ assert_equal expected_time, topic.bonus_time
+
+ topic.save!
+ topic.reload
+
+ assert_equal expected_time, topic.bonus_time
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/type/type_map_test.rb b/activerecord/test/cases/type/type_map_test.rb
index f3699c11a2..1ce515a90c 100644
--- a/activerecord/test/cases/type/type_map_test.rb
+++ b/activerecord/test/cases/type/type_map_test.rb
@@ -32,7 +32,7 @@ module ActiveRecord
end
def test_fuzzy_lookup
- string = String.new
+ string = +""
mapping = TypeMap.new
mapping.register_type(/varchar/i, string)
@@ -41,7 +41,7 @@ module ActiveRecord
end
def test_aliasing_types
- string = String.new
+ string = +""
mapping = TypeMap.new
mapping.register_type(/string/i, string)
@@ -73,7 +73,7 @@ module ActiveRecord
end
def test_register_proc
- string = String.new
+ string = +""
binary = Binary.new
mapping = TypeMap.new
diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb
index 9eefc32745..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
@@ -29,6 +35,14 @@ class TestUnconnectedAdapter < ActiveRecord::TestCase
end
end
+ def test_error_message_when_connection_not_established
+ error = assert_raise(ActiveRecord::ConnectionNotEstablished) do
+ TestRecord.find(1)
+ end
+
+ assert_equal "No connection pool with 'primary' found.", error.message
+ end
+
def test_underlying_adapter_no_longer_active
assert_not @underlying.active?, "Removed adapter should no longer be active"
end
diff --git a/activerecord/test/cases/validations/absence_validation_test.rb b/activerecord/test/cases/validations/absence_validation_test.rb
index 8235a54d8a..1982734f02 100644
--- a/activerecord/test/cases/validations/absence_validation_test.rb
+++ b/activerecord/test/cases/validations/absence_validation_test.rb
@@ -61,7 +61,7 @@ class AbsenceValidationTest < ActiveRecord::TestCase
def test_validates_absence_of_virtual_attribute_on_model
repair_validations(Interest) do
- Interest.send(:attr_accessor, :token)
+ Interest.attr_accessor(:token)
Interest.validates_absence_of(:token)
interest = Interest.create!(topic: "Thought Leadering")
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/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
index 1fbcdc271b..a7cb718043 100644
--- a/activerecord/test/cases/validations/length_validation_test.rb
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -64,7 +64,7 @@ class LengthValidationTest < ActiveRecord::TestCase
def test_validates_length_of_virtual_attribute_on_model
repair_validations(Pet) do
- Pet.send(:attr_accessor, :nickname)
+ Pet.attr_accessor(:nickname)
Pet.validates_length_of(:name, minimum: 1)
Pet.validates_length_of(:nickname, minimum: 1)
diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb
index 63c3f67da2..4b9cbe9098 100644
--- a/activerecord/test/cases/validations/presence_validation_test.rb
+++ b/activerecord/test/cases/validations/presence_validation_test.rb
@@ -69,7 +69,7 @@ class PresenceValidationTest < ActiveRecord::TestCase
def test_validates_presence_of_virtual_attribute_on_model
repair_validations(Interest) do
- Interest.send(:attr_accessor, :abbreviation)
+ Interest.attr_accessor(:abbreviation)
Interest.validates_presence_of(:topic)
Interest.validates_presence_of(:abbreviation)
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 a33877f43a..9a70934b7e 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -3,11 +3,11 @@
require "cases/helper"
require "models/topic"
require "models/reply"
-require "models/person"
require "models/developer"
require "models/computer"
require "models/parrot"
require "models/company"
+require "models/price_estimate"
class ValidationsTest < ActiveRecord::TestCase
fixtures :topics, :developers
@@ -144,6 +144,13 @@ class ValidationsTest < ActiveRecord::TestCase
assert_equal "100,000", d.salary_before_type_cast
end
+ def test_validates_acceptance_of_with_undefined_attribute_methods
+ Topic.validates_acceptance_of(:approved)
+ topic = Topic.new(approved: true)
+ Topic.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)
@@ -183,6 +190,22 @@ class ValidationsTest < ActiveRecord::TestCase
assert_not_predicate klass.new(wibble: BigDecimal("97.179")), :valid?
end
+ def test_numericality_validator_wont_be_affected_by_custom_getter
+ price_estimate = PriceEstimate.new(price: 50)
+
+ assert_equal "$50.00", price_estimate.price
+ assert_equal 50, price_estimate.price_before_type_cast
+ assert_equal 50, price_estimate.read_attribute(:price)
+
+ assert_predicate price_estimate, :price_came_from_user?
+ assert_predicate price_estimate, :valid?
+
+ price_estimate.save!
+
+ assert_not_predicate price_estimate, :price_came_from_user?
+ assert_predicate price_estimate, :valid?
+ end
+
def test_acceptance_validator_doesnt_require_db_connection
klass = Class.new(ActiveRecord::Base) do
self.table_name = "posts"
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index 7e2d66c62a..36b9df7ba5 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -20,7 +20,7 @@ module ViewBehavior
def setup
super
@connection = ActiveRecord::Base.connection
- create_view "ebooks'", <<-SQL
+ create_view "ebooks'", <<~SQL
SELECT id, name, status FROM books WHERE format = 'ebook'
SQL
end
@@ -106,7 +106,7 @@ if ActiveRecord::Base.connection.supports_views?
setup do
@connection = ActiveRecord::Base.connection
- @connection.execute <<-SQL
+ @connection.execute <<~SQL
CREATE VIEW paperbacks
AS SELECT name, status FROM books WHERE format = 'paperback'
SQL
@@ -156,8 +156,7 @@ if ActiveRecord::Base.connection.supports_views?
end
# sqlite dose not support CREATE, INSERT, and DELETE for VIEW
- if current_adapter?(:Mysql2Adapter, :SQLServerAdapter) ||
- current_adapter?(:PostgreSQLAdapter) && ActiveRecord::Base.connection.postgresql_version >= 90300
+ if current_adapter?(:Mysql2Adapter, :SQLServerAdapter, :PostgreSQLAdapter)
class UpdateableViewTest < ActiveRecord::TestCase
self.use_transactional_tests = false
@@ -169,7 +168,7 @@ if ActiveRecord::Base.connection.supports_views?
setup do
@connection = ActiveRecord::Base.connection
- @connection.execute <<-SQL
+ @connection.execute <<~SQL
CREATE VIEW printed_books
AS SELECT id, name, status, format FROM books WHERE format = 'paperback'
SQL
@@ -207,8 +206,7 @@ if ActiveRecord::Base.connection.supports_views?
end # end of `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)`
end # end of `if ActiveRecord::Base.connection.supports_views?`
-if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) &&
- ActiveRecord::Base.connection.supports_materialized_views?
+if ActiveRecord::Base.connection.supports_materialized_views?
class MaterializedViewTest < ActiveRecord::PostgreSQLTestCase
include ViewBehavior