aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md883
-rw-r--r--activerecord/lib/active_record/aggregations.rb1
-rw-r--r--activerecord/lib/active_record/association_relation.rb1
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb1
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb7
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb36
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb5
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb19
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb13
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb1
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb12
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb1
-rw-r--r--activerecord/lib/active_record/attribute_decorators.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb65
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb25
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb24
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb13
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb1
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb17
-rw-r--r--activerecord/lib/active_record/attributes.rb1
-rw-r--r--activerecord/lib/active_record/autosave_association.rb31
-rw-r--r--activerecord/lib/active_record/base.rb1
-rw-r--r--activerecord/lib/active_record/callbacks.rb1
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb105
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb63
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb61
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb72
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb67
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb41
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/deduplicable.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/quoting.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb41
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/utils.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb55
-rw-r--r--activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb41
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/statement_pool.rb1
-rw-r--r--activerecord/lib/active_record/connection_handling.rb13
-rw-r--r--activerecord/lib/active_record/core.rb33
-rw-r--r--activerecord/lib/active_record/database_configurations.rb58
-rw-r--r--activerecord/lib/active_record/database_configurations/url_config.rb1
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb7
-rw-r--r--activerecord/lib/active_record/enum.rb9
-rw-r--r--activerecord/lib/active_record/errors.rb16
-rw-r--r--activerecord/lib/active_record/explain.rb1
-rw-r--r--activerecord/lib/active_record/fixture_set/table_row.rb1
-rw-r--r--activerecord/lib/active_record/fixture_set/table_rows.rb1
-rw-r--r--activerecord/lib/active_record/fixtures.rb3
-rw-r--r--activerecord/lib/active_record/gem_version.rb4
-rw-r--r--activerecord/lib/active_record/inheritance.rb3
-rw-r--r--activerecord/lib/active_record/insert_all.rb6
-rw-r--r--activerecord/lib/active_record/integration.rb12
-rw-r--r--activerecord/lib/active_record/internal_metadata.rb4
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb5
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb2
-rw-r--r--activerecord/lib/active_record/middleware/database_selector.rb7
-rw-r--r--activerecord/lib/active_record/middleware/database_selector/resolver.rb5
-rw-r--r--activerecord/lib/active_record/migration.rb59
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb1
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb5
-rw-r--r--activerecord/lib/active_record/migration/join_table.rb1
-rw-r--r--activerecord/lib/active_record/model_schema.rb5
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb3
-rw-r--r--activerecord/lib/active_record/null_relation.rb1
-rw-r--r--activerecord/lib/active_record/persistence.rb49
-rw-r--r--activerecord/lib/active_record/railtie.rb1
-rw-r--r--activerecord/lib/active_record/railties/databases.rake141
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb4
-rw-r--r--activerecord/lib/active_record/reflection.rb4
-rw-r--r--activerecord/lib/active_record/relation.rb73
-rw-r--r--activerecord/lib/active_record/relation/batches.rb1
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb8
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb7
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb7
-rw-r--r--activerecord/lib/active_record/relation/merger.rb9
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb120
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb1
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb15
-rw-r--r--activerecord/lib/active_record/result.rb1
-rw-r--r--activerecord/lib/active_record/sanitization.rb32
-rw-r--r--activerecord/lib/active_record/schema.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb6
-rw-r--r--activerecord/lib/active_record/schema_migration.rb4
-rw-r--r--activerecord/lib/active_record/scoping.rb1
-rw-r--r--activerecord/lib/active_record/scoping/default.rb1
-rw-r--r--activerecord/lib/active_record/scoping/named.rb1
-rw-r--r--activerecord/lib/active_record/table_metadata.rb22
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb67
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb5
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb1
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb1
-rw-r--r--activerecord/lib/active_record/test_fixtures.rb1
-rw-r--r--activerecord/lib/active_record/timestamp.rb43
-rw-r--r--activerecord/lib/active_record/touch_later.rb10
-rw-r--r--activerecord/lib/active_record/transactions.rb60
-rw-r--r--activerecord/lib/active_record/type.rb1
-rw-r--r--activerecord/lib/active_record/type/adapter_specific_registry.rb3
-rw-r--r--activerecord/lib/active_record/type/hash_lookup_type_map.rb1
-rw-r--r--activerecord/lib/active_record/type/serialized.rb1
-rw-r--r--activerecord/lib/active_record/type/type_map.rb1
-rw-r--r--activerecord/lib/active_record/type/unsigned_integer.rb1
-rw-r--r--activerecord/lib/active_record/type_caster/connection.rb26
-rw-r--r--activerecord/lib/active_record/validations.rb1
-rw-r--r--activerecord/lib/active_record/validations/associated.rb1
-rw-r--r--activerecord/lib/arel.rb2
-rw-r--r--activerecord/lib/arel/attributes.rb22
-rw-r--r--activerecord/lib/arel/nodes/node.rb8
-rw-r--r--activerecord/lib/arel/predications.rb5
-rw-r--r--activerecord/lib/arel/visitors.rb1
-rw-r--r--activerecord/lib/arel/visitors/depth_first.rb204
-rw-r--r--activerecord/lib/arel/visitors/dot.rb1
-rw-r--r--activerecord/lib/arel/visitors/mssql.rb1
-rw-r--r--activerecord/lib/arel/visitors/oracle.rb49
-rw-r--r--activerecord/lib/arel/visitors/oracle12.rb58
-rw-r--r--activerecord/lib/arel/visitors/postgresql.rb1
-rw-r--r--activerecord/lib/arel/visitors/sqlite.rb1
-rw-r--r--activerecord/lib/arel/visitors/to_sql.rb114
-rw-r--r--activerecord/lib/arel/visitors/visitor.rb15
-rw-r--r--activerecord/lib/arel/visitors/where_sql.rb1
-rw-r--r--activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb1
-rw-r--r--activerecord/lib/rails/generators/active_record/migration.rb1
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb1
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb1
-rw-r--r--activerecord/test/active_record/connection_adapters/fake_adapter.rb3
-rw-r--r--activerecord/test/cases/adapter_test.rb12
-rw-r--r--activerecord/test/cases/adapters/mysql2/annotate_test.rb37
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb10
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb1
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb45
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb1
-rw-r--r--activerecord/test/cases/adapters/mysql2/set_test.rb32
-rw-r--r--activerecord/test/cases/adapters/mysql2/table_options_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/transaction_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/annotate_test.rb37
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb7
-rw-r--r--activerecord/test/cases/adapters/postgresql/extension_migration_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb1
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb34
-rw-r--r--activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb5
-rw-r--r--activerecord/test/cases/adapters/postgresql/rename_table_test.rb1
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/transaction_test.rb1
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb8
-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/collation_test.rb9
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb31
-rw-r--r--activerecord/test/cases/annotate_test.rb46
-rw-r--r--activerecord/test/cases/ar_schema_test.rb27
-rw-r--r--activerecord/test/cases/arel/attributes/attribute_test.rb24
-rw-r--r--activerecord/test/cases/arel/attributes_test.rb41
-rw-r--r--activerecord/test/cases/arel/nodes/node_test.rb19
-rw-r--r--activerecord/test/cases/arel/select_manager_test.rb10
-rw-r--r--activerecord/test/cases/arel/support/fake_record.rb4
-rw-r--r--activerecord/test/cases/arel/visitors/depth_first_test.rb276
-rw-r--r--activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb8
-rw-r--r--activerecord/test/cases/arel/visitors/oracle12_test.rb21
-rw-r--r--activerecord/test/cases/arel/visitors/oracle_test.rb21
-rw-r--r--activerecord/test/cases/arel/visitors/to_sql_test.rb10
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb24
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb1
-rw-r--r--activerecord/test/cases/associations/eager_test.rb62
-rw-r--r--activerecord/test/cases/associations/extension_test.rb7
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb16
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb86
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb34
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb44
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb4
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb1
-rw-r--r--activerecord/test/cases/associations/required_test.rb1
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb5
-rw-r--r--activerecord/test/cases/attributes_test.rb6
-rw-r--r--activerecord/test/cases/autosave_association_test.rb79
-rw-r--r--activerecord/test/cases/base_test.rb96
-rw-r--r--activerecord/test/cases/batches_test.rb2
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb2
-rw-r--r--activerecord/test/cases/calculations_test.rb45
-rw-r--r--activerecord/test/cases/collection_cache_key_test.rb34
-rw-r--r--activerecord/test/cases/comment_test.rb25
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb15
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb90
-rw-r--r--activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb3
-rw-r--r--activerecord/test/cases/connection_adapters/schema_cache_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/type_lookup_test.rb1
-rw-r--r--activerecord/test/cases/connection_pool_test.rb23
-rw-r--r--activerecord/test/cases/database_statements_test.rb1
-rw-r--r--activerecord/test/cases/enum_test.rb22
-rw-r--r--activerecord/test/cases/explain_test.rb1
-rw-r--r--activerecord/test/cases/finder_respond_to_test.rb1
-rw-r--r--activerecord/test/cases/finder_test.rb14
-rw-r--r--activerecord/test/cases/fixtures_test.rb2
-rw-r--r--activerecord/test/cases/helper.rb1
-rw-r--r--activerecord/test/cases/hot_compatibility_test.rb1
-rw-r--r--activerecord/test/cases/inheritance_test.rb4
-rw-r--r--activerecord/test/cases/insert_all_test.rb1
-rw-r--r--activerecord/test/cases/json_serialization_test.rb3
-rw-r--r--activerecord/test/cases/locking_test.rb1
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb29
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb1
-rw-r--r--activerecord/test/cases/migration/helper.rb1
-rw-r--r--activerecord/test/cases/migration/logger_test.rb9
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb1
-rw-r--r--activerecord/test/cases/migration_test.rb43
-rw-r--r--activerecord/test/cases/migrator_test.rb143
-rw-r--r--activerecord/test/cases/multi_db_migrator_test.rb218
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb1
-rw-r--r--activerecord/test/cases/persistence_test.rb4
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb1
-rw-r--r--activerecord/test/cases/primary_keys_test.rb8
-rw-r--r--activerecord/test/cases/query_cache_test.rb24
-rw-r--r--activerecord/test/cases/relation/where_clause_test.rb3
-rw-r--r--activerecord/test/cases/relation/where_test.rb69
-rw-r--r--activerecord/test/cases/relation_test.rb8
-rw-r--r--activerecord/test/cases/relations_test.rb32
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb1
-rw-r--r--activerecord/test/cases/schema_loading_test.rb1
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb7
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb14
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb3
-rw-r--r--activerecord/test/cases/test_case.rb24
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb59
-rw-r--r--activerecord/test/cases/transactions_test.rb60
-rw-r--r--activerecord/test/cases/unsafe_raw_sql_test.rb87
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb16
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb1
-rw-r--r--activerecord/test/models/author.rb2
-rw-r--r--activerecord/test/models/book.rb6
-rw-r--r--activerecord/test/models/club.rb5
-rw-r--r--activerecord/test/models/company.rb1
-rw-r--r--activerecord/test/models/company_in_module.rb1
-rw-r--r--activerecord/test/models/contact.rb18
-rw-r--r--activerecord/test/models/developer.rb16
-rw-r--r--activerecord/test/models/face.rb2
-rw-r--r--activerecord/test/models/mouse.rb6
-rw-r--r--activerecord/test/models/person.rb1
-rw-r--r--activerecord/test/models/post.rb5
-rw-r--r--activerecord/test/models/rating.rb1
-rw-r--r--activerecord/test/models/reply.rb12
-rw-r--r--activerecord/test/models/ship.rb3
-rw-r--r--activerecord/test/models/squeak.rb6
-rw-r--r--activerecord/test/models/topic.rb1
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb4
-rw-r--r--activerecord/test/schema/schema.rb13
-rw-r--r--activerecord/test/support/config.rb1
289 files changed, 3075 insertions, 3055 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 8daa6c0ce5..2af48f99db 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,885 +1,60 @@
-* Fix dirty tracking for `touch` to track saved changes.
+* Add a warning for enum elements with 'not_' prefix.
- Fixes #33429.
-
- *Ryuta Kamzono*
-
-* `change_column_comment` and `change_table_comment` are invertible only if
- `to` and `from` options are specified.
-
- *Yoshiyuki Kinjo*
-
-* Don't call commit/rollback callbacks when a record isn't saved.
-
- Fixes #29747.
-
- *Ryuta Kamizono*
-
-* Fix circular `autosave: true` causes invalid records to be saved.
-
- Prior to the fix, when there was a circular series of `autosave: true`
- associations, the callback for a `has_many` association was run while
- another instance of the same callback on the same association hadn't
- finished running. When control returned to the first instance of the
- callback, the instance variable had changed, and subsequent associated
- records weren't saved correctly. Specifically, the ID field for the
- `belongs_to` corresponding to the `has_many` was `nil`.
-
- Fixes #28080.
-
- *Larry Reid*
-
-* Raise `ArgumentError` for invalid `:limit` and `:precision` like as other options.
-
- Before:
-
- ```ruby
- add_column :items, :attr1, :binary, size: 10 # => ArgumentError
- add_column :items, :attr2, :decimal, scale: 10 # => ArgumentError
- add_column :items, :attr3, :integer, limit: 10 # => ActiveRecordError
- add_column :items, :attr4, :datetime, precision: 10 # => ActiveRecordError
- ```
-
- After:
-
- ```ruby
- add_column :items, :attr1, :binary, size: 10 # => ArgumentError
- add_column :items, :attr2, :decimal, scale: 10 # => ArgumentError
- add_column :items, :attr3, :integer, limit: 10 # => ArgumentError
- add_column :items, :attr4, :datetime, precision: 10 # => ArgumentError
- ```
-
- *Ryuta Kamizono*
-
-* Association loading isn't to be affected by scoping consistently
- whether preloaded / eager loaded or not, with the exception of `unscoped`.
-
- Before:
-
- ```ruby
- Post.where("1=0").scoping do
- Comment.find(1).post # => nil
- Comment.preload(:post).find(1).post # => #<Post id: 1, ...>
- Comment.eager_load(:post).find(1).post # => #<Post id: 1, ...>
- end
- ```
-
- After:
-
- ```ruby
- Post.where("1=0").scoping do
- Comment.find(1).post # => #<Post id: 1, ...>
- Comment.preload(:post).find(1).post # => #<Post id: 1, ...>
- Comment.eager_load(:post).find(1).post # => #<Post id: 1, ...>
- end
- ```
-
- Fixes #34638, #35398.
-
- *Ryuta Kamizono*
-
-* Add `rails db:prepare` to migrate or setup a database.
-
- Runs `db:migrate` if the database exists or `db:setup` if it doesn't.
-
- *Roberto Miranda*
-
-* Add `after_save_commit` callback as shortcut for `after_commit :hook, on: [ :create, :update ]`.
-
- *DHH*
-
-* Assign all attributes before calling `build` to ensure the child record is visible in
- `before_add` and `after_add` callbacks for `has_many :through` associations.
-
- Fixes #33249.
-
- *Ryan H. Kerr*
-
-* Add `ActiveRecord::Relation#extract_associated` for extracting associated records from a relation.
-
- ```
- account.memberships.extract_associated(:user)
- # => Returns collection of User records
- ```
-
- *DHH*
-
-* Add `ActiveRecord::Relation#annotate` for adding SQL comments to its queries.
-
- For example:
-
- ```
- Post.where(id: 123).annotate("this is a comment").to_sql
- # SELECT "posts".* FROM "posts" WHERE "posts"."id" = 123 /* this is a comment */
- ```
-
- This can be useful in instrumentation or other analysis of issued queries.
-
- *Matt Yoho*
-
-* Support Optimizer Hints.
-
- In most databases, a way to control the optimizer is by using optimizer hints,
- which can be specified within individual statements.
-
- Example (for MySQL):
-
- Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
- # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
-
- Example (for PostgreSQL with pg_hint_plan):
-
- Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
- # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
-
- See also:
-
- * https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html
- * https://pghintplan.osdn.jp/pg_hint_plan.html
- * https://docs.oracle.com/en/database/oracle/oracle-database/12.2/tgsql/influencing-the-optimizer.html
- * https://docs.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-query?view=sql-server-2017
- * https://www.ibm.com/support/knowledgecenter/en/SSEPGG_11.1.0/com.ibm.db2.luw.admin.perf.doc/doc/c0070117.html
-
- *Ryuta Kamizono*
-
-* Fix query attribute method on user-defined attribute to be aware of typecasted value.
-
- For example, the following code no longer return false as casted non-empty string:
-
- ```
- class Post < ActiveRecord::Base
- attribute :user_defined_text, :text
- end
-
- Post.new(user_defined_text: "false").user_defined_text? # => true
- ```
-
- *Yuji Kamijima*
-
-* Quote empty ranges like other empty enumerables.
-
- *Patrick Rebsch*
-
-* Add `insert_all`/`insert_all!`/`upsert_all` methods to `ActiveRecord::Persistence`,
- allowing bulk inserts akin to the bulk updates provided by `update_all` and
- bulk deletes by `delete_all`.
-
- Supports skipping or upserting duplicates through the `ON CONFLICT` syntax
- for PostgreSQL (9.5+) and SQLite (3.24+) and `ON DUPLICATE KEY UPDATE` syntax
- for MySQL.
-
- *Bob Lail*
-
-* Add `rails db:seed:replant` that truncates tables of each database
- for current environment and loads the seeds.
-
- *bogdanvlviv*, *DHH*
-
-* Add `ActiveRecord::Base.connection.truncate` for SQLite3 adapter.
-
- *bogdanvlviv*
-
-* Deprecate mismatched collation comparison for uniqueness validator.
-
- Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1.
- To continue case sensitive comparison on the case insensitive column,
- pass `case_sensitive: true` option explicitly to the uniqueness validator.
-
- *Ryuta Kamizono*
-
-* Add `reselect` method. This is a short-hand for `unscope(:select).select(fields)`.
-
- Fixes #27340.
-
- *Willian Gustavo Veiga*
-
-* Add negative scopes for all enum values.
-
- Example:
-
- class Post < ActiveRecord::Base
- enum status: %i[ drafted active trashed ]
- end
-
- Post.not_drafted # => where.not(status: :drafted)
- Post.not_active # => where.not(status: :active)
- Post.not_trashed # => where.not(status: :trashed)
-
- *DHH*
-
-* Fix different `count` calculation when using `size` with manual `select` with DISTINCT.
-
- Fixes #35214.
-
- *Juani Villarejo*
-
-
-## Rails 6.0.0.beta3 (March 11, 2019) ##
-
-* No changes.
-
-
-## Rails 6.0.0.beta2 (February 25, 2019) ##
-
-* Fix prepared statements caching to be enabled even when query caching is enabled.
-
- *Ryuta Kamizono*
-
-* Ensure `update_all` series cares about optimistic locking.
-
- *Ryuta Kamizono*
-
-* Don't allow `where` with non numeric string matches to 0 values.
-
- *Ryuta Kamizono*
-
-* Introduce `ActiveRecord::Relation#destroy_by` and `ActiveRecord::Relation#delete_by`.
-
- `destroy_by` allows relation to find all the records matching the condition and perform
- `destroy_all` on the matched records.
-
- Example:
-
- Person.destroy_by(name: 'David')
- Person.destroy_by(name: 'David', rating: 4)
-
- david = Person.find_by(name: 'David')
- david.posts.destroy_by(id: [1, 2, 3])
-
- `delete_by` allows relation to find all the records matching the condition and perform
- `delete_all` on the matched records.
-
- Example:
-
- Person.delete_by(name: 'David')
- Person.delete_by(name: 'David', rating: 4)
-
- david = Person.find_by(name: 'David')
- david.posts.delete_by(id: [1, 2, 3])
-
- *Abhay Nikam*
-
-* Don't allow `where` with invalid value matches to nil values.
-
- Fixes #33624.
-
- *Ryuta Kamizono*
-
-* SQLite3: Implement `add_foreign_key` and `remove_foreign_key`.
-
- *Ryuta Kamizono*
-
-* Deprecate using class level querying methods if the receiver scope
- regarded as leaked. Use `klass.unscoped` to avoid the leaking scope.
-
- *Ryuta Kamizono*
-
-* Allow applications to automatically switch connections.
-
- Adds a middleware and configuration options that can be used in your
- application to automatically switch between the writing and reading
- database connections.
-
- `GET` and `HEAD` requests will read from the replica unless there was
- a write in the last 2 seconds, otherwise they will read from the primary.
- Non-get requests will always write to the primary. The middleware accepts
- an argument for a Resolver class and a Operations class where you are able
- to change how the auto-switcher works to be most beneficial for your
- application.
-
- To use the middleware in your application you can use the following
- configuration options:
-
- ```
- config.active_record.database_selector = { delay: 2.seconds }
- config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
- config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
- ```
-
- To change the database selection strategy, pass a custom class to the
- configuration options:
-
- ```
- config.active_record.database_selector = { delay: 10.seconds }
- config.active_record.database_resolver = MyResolver
- config.active_record.database_resolver_context = MyResolver::MyCookies
- ```
-
- *Eileen M. Uchitelle*
-
-* MySQL: Support `:size` option to change text and blob size.
-
- *Ryuta Kamizono*
-
-* Make `t.timestamps` with precision by default.
-
- *Ryuta Kamizono*
-
-
-## Rails 6.0.0.beta1 (January 18, 2019) ##
-
-* Remove deprecated `#set_state` from the transaction object.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `#supports_statement_cache?` from the database adapters.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `#insert_fixtures` from the database adapters.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActiveRecord::ConnectionAdapters::SQLite3Adapter#valid_alter_table_type?`.
-
- *Rafael Mendonça França*
-
-* Do not allow passing the column name to `sum` when a block is passed.
-
- *Rafael Mendonça França*
-
-* Do not allow passing the column name to `count` when a block is passed.
-
- *Rafael Mendonça França*
-
-* Remove delegation of missing methods in a relation to arel.
-
- *Rafael Mendonça França*
-
-* Remove delegation of missing methods in a relation to private methods of the class.
-
- *Rafael Mendonça França*
-
-* Deprecate `config.activerecord.sqlite3.represent_boolean_as_integer`.
-
- *Rafael Mendonça França*
-
-* Change `SQLite3Adapter` to always represent boolean values as integers.
-
- *Rafael Mendonça França*
-
-* Remove ability to specify a timestamp name for `#cache_key`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActiveRecord::Migrator.migrations_path=`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `expand_hash_conditions_for_aggregates`.
-
- *Rafael Mendonça França*
-
-* Set polymorphic type column to NULL on `dependent: :nullify` strategy.
-
- On polymorphic associations both the foreign key and the foreign type columns will be set to NULL.
-
- *Laerti Papa*
-
-* Allow permitted instance of `ActionController::Parameters` as argument of `ActiveRecord::Relation#exists?`.
-
- *Gannon McGibbon*
-
-* Add support for endless ranges introduces in Ruby 2.6.
-
- *Greg Navis*
-
-* Deprecate passing `migrations_paths` to `connection.assume_migrated_upto_version`.
-
- *Ryuta Kamizono*
-
-* MySQL: `ROW_FORMAT=DYNAMIC` create table option by default.
-
- Since MySQL 5.7.9, the `innodb_default_row_format` option defines the default row
- format for InnoDB tables. The default setting is `DYNAMIC`.
- The row format is required for indexing on `varchar(255)` with `utf8mb4` columns.
-
- *Ryuta Kamizono*
-
-* Fix join table column quoting with SQLite.
-
- *Gannon McGibbon*
-
-* Allow disabling scopes generated by `ActiveRecord.enum`.
-
- *Alfred Dominic*
-
-* Ensure that `delete_all` on collection proxy returns affected count.
-
- *Ryuta Kamizono*
-
-* Reset scope after delete on collection association to clear stale offsets of removed records.
-
- *Gannon McGibbon*
-
-* Add the ability to prevent writes to a database for the duration of a block.
-
- Allows the application to prevent writes to a database. This can be useful when
- you're building out multiple databases and want to make sure you're not sending
- writes when you want a read.
-
- If `while_preventing_writes` is called and the query is considered a write
- query the database will raise an exception regardless of whether the database
- user is able to write.
-
- This is not meant to be a catch-all for write queries but rather a way to enforce
- read-only queries without opening a second connection. One purpose of this is to
- catch accidental writes, not all writes.
-
- *Eileen M. Uchitelle*
-
-* Allow aliased attributes to be used in `#update_columns` and `#update`.
-
- *Gannon McGibbon*
-
-* Allow spaces in postgres table names.
-
- Fixes issue where "user post" is misinterpreted as "\"user\".\"post\"" when quoting table names with the postgres adapter.
-
- *Gannon McGibbon*
-
-* Cached `columns_hash` fields should be excluded from `ResultSet#column_types`.
-
- PR #34528 addresses the inconsistent behaviour when attribute is defined for an ignored column. The following test
- was passing for SQLite and MySQL, but failed for PostgreSQL:
-
- ```ruby
- class DeveloperName < ActiveRecord::Type::String
- def deserialize(value)
- "Developer: #{value}"
- end
- end
-
- class AttributedDeveloper < ActiveRecord::Base
- self.table_name = "developers"
-
- attribute :name, DeveloperName.new
-
- self.ignored_columns += ["name"]
- end
-
- developer = AttributedDeveloper.create
- developer.update_column :name, "name"
-
- loaded_developer = AttributedDeveloper.where(id: developer.id).select("*").first
- puts loaded_developer.name # should be "Developer: name" but it's just "name"
- ```
-
- *Dmitry Tsepelev*
-
-* Make the implicit order column configurable.
-
- When calling ordered finder methods such as `first` or `last` without an
- explicit order clause, ActiveRecord sorts records by primary key. This can
- result in unpredictable and surprising behaviour when the primary key is
- not an auto-incrementing integer, for example when it's a UUID. This change
- makes it possible to override the column used for implicit ordering such
- that `first` and `last` will return more predictable results.
-
- Example:
-
- class Project < ActiveRecord::Base
- self.implicit_order_column = "created_at"
- end
-
- *Tekin Suleyman*
-
-* Bump minimum PostgreSQL version to 9.3.
-
- *Yasuo Honda*
-
-* Values of enum are frozen, raising an error when attempting to modify them.
-
- *Emmanuel Byrd*
-
-* Move `ActiveRecord::StatementInvalid` SQL to error property and include binds as separate error property.
-
- `ActiveRecord::ConnectionAdapters::AbstractAdapter#translate_exception_class` now requires `binds` to be passed as the last argument.
-
- `ActiveRecord::ConnectionAdapters::AbstractAdapter#translate_exception` now requires `message`, `sql`, and `binds` to be passed as keyword arguments.
-
- Subclasses of `ActiveRecord::StatementInvalid` must now provide `sql:` and `binds:` arguments to `super`.
-
- Example:
-
- ```
- class MySubclassedError < ActiveRecord::StatementInvalid
- def initialize(message, sql:, binds:)
- super(message, sql: sql, binds: binds)
- end
- end
- ```
-
- *Gannon McGibbon*
-
-* Add an `:if_not_exists` option to `create_table`.
-
- Example:
-
- create_table :posts, if_not_exists: true do |t|
- t.string :title
- end
-
- That would execute:
-
- CREATE TABLE IF NOT EXISTS posts (
- ...
- )
-
- If the table already exists, `if_not_exists: false` (the default) raises an
- exception whereas `if_not_exists: true` does nothing.
-
- *fatkodima*, *Stefan Kanev*
-
-* Defining an Enum as a Hash with blank key, or as an Array with a blank value, now raises an `ArgumentError`.
-
- *Christophe Maximin*
-
-* Adds support for multiple databases to `rails db:schema:cache:dump` and `rails db:schema:cache:clear`.
-
- *Gannon McGibbon*
-
-* `update_columns` now correctly raises `ActiveModel::MissingAttributeError`
- if the attribute does not exist.
-
- *Sean Griffin*
-
-* Add support for hash and URL configs in database hash of `ActiveRecord::Base.connected_to`.
-
- ````
- User.connected_to(database: { writing: "postgres://foo" }) do
- User.create!(name: "Gannon")
- end
-
- config = { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" }
- User.connected_to(database: { reading: config }) do
- User.count
- end
- ````
-
- *Gannon McGibbon*
-
-* Support default expression for MySQL.
-
- MySQL 8.0.13 and higher supports default value to be a function or expression.
-
- https://dev.mysql.com/doc/refman/8.0/en/create-table.html
-
- *Ryuta Kamizono*
-
-* Support expression indexes for MySQL.
-
- MySQL 8.0.13 and higher supports functional key parts that index
- expression values rather than column or column prefix values.
-
- https://dev.mysql.com/doc/refman/8.0/en/create-index.html
-
- *Ryuta Kamizono*
-
-* Fix collection cache key with limit and custom select to avoid ambiguous timestamp column error.
-
- Fixes #33056.
-
- *Federico Martinez*
-
-* Add basic API for connection switching to support multiple databases.
-
- 1) Adds a `connects_to` method for models to connect to multiple databases. Example:
-
- ```
- class AnimalsModel < ApplicationRecord
- self.abstract_class = true
-
- connects_to database: { writing: :animals_primary, reading: :animals_replica }
- end
-
- class Dog < AnimalsModel
- # connected to both the animals_primary db for writing and the animals_replica for reading
- end
- ```
-
- 2) Adds a `connected_to` block method for switching connection roles or connecting to
- a database that the model didn't connect to. Connecting to the database in this block is
- useful when you have another defined connection, for example `slow_replica` that you don't
- want to connect to by default but need in the console, or a specific code block.
-
- ```
- ActiveRecord::Base.connected_to(role: :reading) do
- Dog.first # finds dog from replica connected to AnimalsBase
- Book.first # doesn't have a reading connection, will raise an error
- end
- ```
-
- ```
- ActiveRecord::Base.connected_to(database: :slow_replica) do
- SlowReplicaModel.first # if the db config has a slow_replica configuration this will be used to do the lookup, otherwise this will throw an exception
- end
- ```
-
- *Eileen M. Uchitelle*
-
-* Enum raises on invalid definition values
-
- When defining a Hash enum it can be easy to use `[]` instead of `{}`. This
- commit checks that only valid definition values are provided, those can
- be a Hash, an array of Symbols or an array of Strings. Otherwise it
- raises an `ArgumentError`.
-
- Fixes #33961
-
- *Alberto Almagro*
-
-* Reloading associations now clears the Query Cache like `Persistence#reload` does.
-
- ```
- class Post < ActiveRecord::Base
- has_one :category
- belongs_to :author
- has_many :comments
- end
-
- # Each of the following will now clear the query cache.
- post.reload_category
- post.reload_author
- post.comments.reload
- ```
-
- *Christophe Maximin*
-
-* Added `index` option for `change_table` migration helpers.
- With this change you can create indexes while adding new
- columns into the existing tables.
-
- Example:
-
- change_table(:languages) do |t|
- t.string :country_code, index: true
+ class Foo
+ enum status: [:sent, :not_sent]
end
- *Mehmet Emin İNAÇ*
-
-* Fix `transaction` reverting for migrations.
-
- Before: Commands inside a `transaction` in a reverted migration ran uninverted.
- Now: This change fixes that by reverting commands inside `transaction` block.
+ *Edu Depetris*
- *fatkodima*, *David Verhasselt*
+* Make currency symbols optional for money column type in PostgreSQL
-* Raise an error instead of scanning the filesystem root when `fixture_path` is blank.
+ *Joel Schneider*
- *Gannon McGibbon*, *Max Albrecht*
+* Add support for beginless ranges, introduced in Ruby 2.7.
-* Allow `ActiveRecord::Base.configurations=` to be set with a symbolized hash.
+ *Josh Goodall*
- *Gannon McGibbon*
+* Add database_exists? method to connection adapters to check if a database exists.
-* Don't update counter cache unless the record is actually saved.
+ *Guilherme Mansur*
- Fixes #31493, #33113, #33117.
+* Loading the schema for a model that has no `table_name` raises a `TableNotSpecified` error.
- *Ryuta Kamizono*
-
-* Deprecate `ActiveRecord::Result#to_hash` in favor of `ActiveRecord::Result#to_a`.
-
- *Gannon McGibbon*, *Kevin Cheng*
-
-* SQLite3 adapter supports expression indexes.
-
- ```
- create_table :users do |t|
- t.string :email
- end
-
- add_index :users, 'lower(email)', name: 'index_users_on_email', unique: true
- ```
-
- *Gray Kemmey*
-
-* Allow subclasses to redefine autosave callbacks for associated records.
-
- Fixes #33305.
-
- *Andrey Subbota*
-
-* Bump minimum MySQL version to 5.5.8.
+ *Guilherme Mansur*, *Eugene Kenny*
- *Yasuo Honda*
+* PostgreSQL: Fix GROUP BY with ORDER BY virtual count attribute.
-* Use MySQL utf8mb4 character set by default.
-
- `utf8mb4` character set with 4-Byte encoding supports supplementary characters including emoji.
- The previous default 3-Byte encoding character set `utf8` is not enough to support them.
-
- *Yasuo Honda*
-
-* Fix duplicated record creation when using nested attributes with `create_with`.
-
- *Darwin Wu*
-
-* Configuration item `config.filter_parameters` could also filter out
- sensitive values of database columns when call `#inspect`.
- We also added `ActiveRecord::Base::filter_attributes`/`=` in order to
- specify sensitive attributes to specific model.
-
- ```
- Rails.application.config.filter_parameters += [:credit_card_number, /phone/]
- Account.last.inspect # => #<Account id: 123, name: "DHH", credit_card_number: [FILTERED], telephone_number: [FILTERED] ...>
- SecureAccount.filter_attributes += [:name]
- SecureAccount.last.inspect # => #<SecureAccount id: 42, name: [FILTERED], credit_card_number: [FILTERED] ...>
- ```
-
- *Zhang Kang*, *Yoshiyuki Kinjo*
-
-* Deprecate `column_name_length`, `table_name_length`, `columns_per_table`,
- `indexes_per_table`, `columns_per_multicolumn_index`, `sql_query_length`,
- and `joins_per_query` methods in `DatabaseLimits`.
+ Fixes #36022.
*Ryuta Kamizono*
-* `ActiveRecord::Base.configurations` now returns an object.
-
- `ActiveRecord::Base.configurations` used to return a hash, but this
- is an inflexible data model. In order to improve multiple-database
- handling in Rails, we've changed this to return an object. Some methods
- are provided to make the object behave hash-like in order to ease the
- transition process. Since most applications don't manipulate the hash
- we've decided to add backwards-compatible functionality that will throw
- a deprecation warning if used, however calling `ActiveRecord::Base.configurations`
- will use the new version internally and externally.
-
- For example, the following `database.yml`:
-
- ```
- development:
- adapter: sqlite3
- database: db/development.sqlite3
- ```
-
- Used to become a hash:
-
- ```
- { "development" => { "adapter" => "sqlite3", "database" => "db/development.sqlite3" } }
- ```
-
- Is now converted into the following object:
-
- ```
- #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
- #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
- @spec_name="primary", @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>
- ]
- ```
-
- Iterating over the database configurations has also changed. Instead of
- calling hash methods on the `configurations` hash directly, a new method `configs_for` has
- been provided that allows you to select the correct configuration. `env_name` and
- `spec_name` arguments are optional. For example, these return an array of
- database config objects for the requested environment and a single database config object
- will be returned for the requested environment and specification name respectively.
-
- ```
- ActiveRecord::Base.configurations.configs_for(env_name: "development")
- ActiveRecord::Base.configurations.configs_for(env_name: "development", spec_name: "primary")
- ```
-
- *Eileen M. Uchitelle*, *Aaron Patterson*
-
-* Add database configuration to disable advisory locks.
-
- ```
- production:
- adapter: postgresql
- advisory_locks: false
- ```
-
- *Guo Xiang*
-
-* SQLite3 adapter `alter_table` method restores foreign keys.
-
- *Yasuo Honda*
-
-* Allow `:to_table` option to `invert_remove_foreign_key`.
-
- Example:
-
- remove_foreign_key :accounts, to_table: :owners
-
- *Nikolay Epifanov*, *Rich Chen*
-
-* Add environment & load_config dependency to `bin/rake db:seed` to enable
- seed load in environments without Rails and custom DB configuration
-
- *Tobias Bielohlawek*
-
-* Fix default value for mysql time types with specified precision.
-
- *Nikolay Kondratyev*
-
-* Fix `touch` option to behave consistently with `Persistence#touch` method.
-
- *Ryuta Kamizono*
-
-* Migrations raise when duplicate column definition.
-
- Fixes #33024.
-
- *Federico Martinez*
-
-* Bump minimum SQLite version to 3.8
-
- *Yasuo Honda*
-
-* Fix parent record should not get saved with duplicate children records.
-
- Fixes #32940.
-
- *Santosh Wadghule*
-
-* Fix logic on disabling commit callbacks so they are not called unexpectedly when errors occur.
-
- *Brian Durand*
-
-* Ensure `Associations::CollectionAssociation#size` and `Associations::CollectionAssociation#empty?`
- use loaded association ids if present.
-
- *Graham Turner*
-
-* Add support to preload associations of polymorphic associations when not all the records have the requested associations.
-
- *Dana Sherson*
-
-* Add `touch_all` method to `ActiveRecord::Relation`.
-
- Example:
-
- Person.where(name: "David").touch_all(time: Time.new(2020, 5, 16, 0, 0, 0))
+* Make ActiveRecord `ConnectionPool.connections` method thread-safe.
- *fatkodima*, *duggiefresh*
+ Fixes #36465.
-* Add `ActiveRecord::Base.base_class?` predicate.
+ *Jeff Doering*
- *Bogdan Gusiev*
+* Add support for multiple databases to `rails db:abort_if_pending_migrations`.
-* Add custom prefix/suffix options to `ActiveRecord::Store.store_accessor`.
+ *Mark Lee*
- *Tan Huynh*, *Yukio Mizuta*
+* Fix sqlite3 collation parsing when using decimal columns.
-* Rails 6 requires Ruby 2.5.0 or newer.
+ *Martin R. Schuster*
- *Jeremy Daer*, *Kasper Timm Hansen*
+* Fix invalid schema when primary key column has a comment.
-* Deprecate `update_attributes`/`!` in favor of `update`/`!`.
+ Fixes #29966.
- *Eddie Lebow*
+ *Guilherme Goettems Schneider*
-* Add `ActiveRecord::Base.create_or_find_by`/`!` to deal with the SELECT/INSERT race condition in
- `ActiveRecord::Base.find_or_create_by`/`!` by leaning on unique constraints in the database.
+* Fix table comment also being applied to the primary key column.
- *DHH*
+ *Guilherme Goettems Schneider*
-* Add `Relation#pick` as short-hand for single-value plucks.
+* Allow generated `create_table` migrations to include or skip timestamps.
- *DHH*
+ *Michael Duchemin*
-Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activerecord/CHANGELOG.md) for previous changes.
+Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 3250e29b82..aa08124158 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -14,7 +14,6 @@ module ActiveRecord
end
private
-
def clear_aggregation_cache
@aggregation_cache.clear if persisted?
end
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index 4c538ef2bd..de9892e48d 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -29,7 +29,6 @@ module ActiveRecord
end
private
-
def exec_queries
super do |record|
@association.set_inverse_instance_from_queries(record)
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 272eede824..ac90ba0137 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -72,7 +72,6 @@ module ActiveRecord
attr_reader :aliases
private
-
def truncate(name)
name.slice(0, @connection.table_alias_length - 2)
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 3b4b243148..0c61094d6c 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -128,5 +128,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
name = reflection.name
model.before_destroy lambda { |o| o.association(name).handle_dependency }
end
+
+ private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions,
+ :define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations,
+ :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks
end
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index fc00f1e900..321ccba918 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -74,11 +74,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
def self.add_touch_callbacks(model, reflection)
foreign_key = reflection.foreign_key
- n = reflection.name
+ name = reflection.name
touch = reflection.options[:touch]
callback = lambda { |changes_method| lambda { |record|
- BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method)
+ BelongsTo.touch_record(record, record.send(changes_method), foreign_key, name, touch, belongs_to_touch_method)
}}
if reflection.counter_cache_column
@@ -123,5 +123,8 @@ module ActiveRecord::Associations::Builder # :nodoc:
model.validates_presence_of reflection.name, message: :required
end
end
+
+ private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks, :define_validations,
+ :add_counter_cache_callbacks, :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks
end
end
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 9fccfcce0c..e78d25441b 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -22,9 +22,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
def self.define_extensions(model, name, &block)
if block_given?
- extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
+ extension_module_name = "#{name.to_s.camelize}AssociationExtension"
extension = Module.new(&block)
- model.module_parent.const_set(extension_module_name, extension)
+ model.const_set(extension_module_name, extension)
end
end
@@ -66,5 +66,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
CODE
end
+
+ private_class_method :valid_options, :define_callback, :define_extensions, :define_readers, :define_writers
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 0140aa15c8..6ad4c75fb5 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -46,7 +46,6 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
private
-
def self.suppress_composite_primary_key(pk)
pk unless pk.is_a?(Array)
end
@@ -73,7 +72,6 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
private
-
def middle_options(join_model)
middle_options = {}
middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 5b9617bc6d..556e2988f5 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -13,5 +13,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
def self.valid_dependent_options
[:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
end
+
+ private_class_method :macro, :valid_options, :valid_dependent_options
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index bfb37d6eee..27ebe8cb71 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -7,7 +7,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
def self.valid_options(options)
- valid = super + [:as]
+ valid = super + [:as, :touch]
valid += [:through, :source, :source_type] if options[:through]
valid
end
@@ -16,6 +16,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
[:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
end
+ def self.define_callbacks(model, reflection)
+ super
+ add_touch_callbacks(model, reflection) if reflection.options[:touch]
+ end
+
def self.add_destroy_callbacks(model, reflection)
super unless reflection.options[:through]
end
@@ -26,5 +31,34 @@ module ActiveRecord::Associations::Builder # :nodoc:
model.validates_presence_of reflection.name, message: :required
end
end
+
+ def self.touch_record(o, name, touch)
+ record = o.send name
+
+ return unless record && record.persisted?
+
+ if touch != true
+ record.touch(touch)
+ else
+ record.touch
+ end
+ end
+
+ def self.add_touch_callbacks(model, reflection)
+ name = reflection.name
+ touch = reflection.options[:touch]
+
+ callback = lambda { |record|
+ HasOne.touch_record(record, name, touch)
+ }
+
+ model.after_create callback, if: :saved_changes?
+ model.after_update callback, if: :saved_changes?
+ model.after_destroy callback
+ model.after_touch callback
+ end
+
+ private_class_method :macro, :valid_options, :valid_dependent_options, :add_destroy_callbacks,
+ :define_callbacks, :define_validations, :add_touch_callbacks
end
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 0a02ef4cc1..0e22563b41 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -38,5 +38,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
CODE
end
+
+ private_class_method :valid_options, :define_accessors, :define_constructors
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index c3d4eab562..891b50d160 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -56,7 +56,7 @@ module ActiveRecord
def ids_writer(ids)
primary_key = reflection.association_primary_key
pk_type = klass.type_for_attribute(primary_key)
- ids = Array(ids).reject(&:blank?)
+ ids = Array(ids).compact_blank
ids.map! { |i| pk_type.cast(i) }
records = klass.where(primary_key => ids).index_by do |r|
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index edcb44f0fc..0db0ad8595 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -1002,7 +1002,7 @@ module ActiveRecord
end
# Adds one or more +records+ to the collection by setting their foreign keys
- # to the association's primary key. Since +<<+ flattens its argument list and
+ # to the association's primary key. Since <tt><<</tt> flattens its argument list and
# inserts each record, +push+ and +concat+ behave identically. Returns +self+
# so several appends may be chained together.
#
@@ -1029,7 +1029,7 @@ module ActiveRecord
alias_method :append, :<<
alias_method :concat, :<<
- def prepend(*args)
+ def prepend(*args) # :nodoc:
raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
end
@@ -1101,7 +1101,6 @@ module ActiveRecord
delegate(*delegate_methods, to: :scope)
private
-
def find_nth_with_limit(index, limit)
load_target if find_from_target?
super
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 5972846940..dd2ed55279 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -37,7 +37,6 @@ module ActiveRecord
end
private
-
# Returns the number of records in this collection.
#
# If the association has a counter cache it gets that value. Otherwise
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index b76005b587..f35a40fb2f 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -64,16 +64,17 @@ module ActiveRecord
end
end
- def initialize(base, table, associations)
+ def initialize(base, table, associations, join_type)
tree = self.class.make_tree associations
@join_root = JoinBase.new(base, table, build(tree, base))
+ @join_type = join_type
end
def reflections
join_root.drop(1).map!(&:reflection)
end
- def join_constraints(joins_to_add, join_type, alias_tracker)
+ def join_constraints(joins_to_add, alias_tracker)
@alias_tracker = alias_tracker
construct_tables!(join_root)
@@ -82,9 +83,9 @@ module ActiveRecord
joins.concat joins_to_add.flat_map { |oj|
construct_tables!(oj.join_root)
if join_root.match? oj.join_root
- walk join_root, oj.join_root
+ walk(join_root, oj.join_root, oj.join_type)
else
- make_join_constraints(oj.join_root, join_type)
+ make_join_constraints(oj.join_root, oj.join_type)
end
}
end
@@ -125,7 +126,7 @@ module ActiveRecord
end
protected
- attr_reader :join_root
+ attr_reader :join_root, :join_type
private
attr_reader :alias_tracker
@@ -151,7 +152,7 @@ module ActiveRecord
end
end
- def make_constraints(parent, child, join_type = Arel::Nodes::OuterJoin)
+ def make_constraints(parent, child, join_type)
foreign_table = parent.table
foreign_klass = parent.base_klass
joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
@@ -173,13 +174,13 @@ module ActiveRecord
join ? "#{name}_join" : name
end
- def walk(left, right)
+ def walk(left, right, join_type)
intersection, missing = right.children.map { |node1|
[left.children.find { |node2| node1.match? node2 }, node1]
}.partition(&:first)
- joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r) }
- joins.concat missing.flat_map { |_, n| make_constraints(left, n) }
+ joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r, join_type) }
+ joins.concat missing.flat_map { |_, n| make_constraints(left, n, join_type) }
end
def find_reflection(klass, name)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index ca0305abbb..6a7e92dc28 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -44,8 +44,7 @@ module ActiveRecord
unless others.empty?
joins.concat arel.join_sources
- right = joins.last.right
- right.expr.children.concat(others)
+ append_constraints(joins.last, others)
end
# The current table in this iteration becomes the foreign table in the next
@@ -65,6 +64,16 @@ module ActiveRecord
@readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value
end
+
+ private
+ def append_constraints(join, constraints)
+ if join.is_a?(Arel::Nodes::StringJoin)
+ join_string = table.create_and(constraints.unshift(join.left))
+ join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
+ else
+ join.right.expr.children.concat(constraints)
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 6b57e5093a..d4e8b364e1 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -95,7 +95,6 @@ module ActiveRecord
end
private
-
# Loads all the given data into +records+ for the +association+.
def preloaders_on(association, records, scope, polymorphic_parent = false)
case association
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 46532f651e..4c7b0e6f07 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -27,7 +27,9 @@ module ActiveRecord
end
def records_by_owner
- @records_by_owner ||= preloaded_records.each_with_object({}) do |record, result|
+ # owners can be duplicated when a relation has a collection association join
+ # #compare_by_identity makes such owners different hash keys
+ @records_by_owner ||= preloaded_records.each_with_object({}.compare_by_identity) do |record, result|
owners_by_key[convert_key(record[association_key_name])].each do |owner|
(result[owner] ||= []) << record
end
@@ -36,13 +38,7 @@ module ActiveRecord
def preloaded_records
return @preloaded_records if defined?(@preloaded_records)
- return [] if owner_keys.empty?
- # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
- # Make several smaller queries if necessary or make one query if the adapter supports it
- slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
- @preloaded_records = slices.flat_map do |slice|
- records_for(slice)
- end
+ @preloaded_records = owner_keys.empty? ? [] : records_for(owner_keys)
end
private
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 929045f29b..acb8ba7e5a 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -7,7 +7,6 @@ module ActiveRecord
include ActiveModel::AttributeAssignment
private
-
def _assign_attributes(attributes)
multi_parameter_attributes = {}
nested_parameter_attributes = {}
diff --git a/activerecord/lib/active_record/attribute_decorators.rb b/activerecord/lib/active_record/attribute_decorators.rb
index 98b7805c0a..0b66043d2a 100644
--- a/activerecord/lib/active_record/attribute_decorators.rb
+++ b/activerecord/lib/active_record/attribute_decorators.rb
@@ -46,7 +46,6 @@ module ActiveRecord
end
private
-
def load_schema!
super
attribute_types.each do |name, type|
@@ -75,7 +74,6 @@ module ActiveRecord
end
private
-
def decorators_for(name, type)
matching(name, type).map(&:last)
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index af7e46e649..21f72bb6c7 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -24,7 +24,7 @@ module ActiveRecord
RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
- class GeneratedAttributeMethodsBuilder < Module #:nodoc:
+ class GeneratedAttributeMethods < Module #:nodoc:
include Mutex_m
end
@@ -35,7 +35,7 @@ module ActiveRecord
end
def initialize_generated_modules # :nodoc:
- @generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethodsBuilder.new)
+ @generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new)
private_constant :GeneratedAttributeMethods
@attribute_methods_generated = false
include @generated_attribute_methods
@@ -89,7 +89,7 @@ module ActiveRecord
# If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
# defines its own attribute method, then we don't want to overwrite that.
defined = method_defined_within?(method_name, superclass, Base) &&
- ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethodsBuilder)
+ ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
defined || super
end
end
@@ -159,57 +159,6 @@ module ActiveRecord
end
end
- # Regexp for column names (with or without a table name prefix). Matches
- # the following:
- # "#{table_name}.#{column_name}"
- # "#{column_name}"
- COLUMN_NAME = /\A(?:\w+\.)?\w+\z/i
-
- # Regexp for column names with order (with or without a table name
- # prefix, with or without various order modifiers). Matches the following:
- # "#{table_name}.#{column_name}"
- # "#{table_name}.#{column_name} #{direction}"
- # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
- # "#{table_name}.#{column_name} NULLS LAST"
- # "#{column_name}"
- # "#{column_name} #{direction}"
- # "#{column_name} #{direction} NULLS FIRST"
- # "#{column_name} NULLS LAST"
- COLUMN_NAME_WITH_ORDER = /
- \A
- (?:\w+\.)?
- \w+
- (?:\s+asc|\s+desc)?
- (?:\s+nulls\s+(?:first|last))?
- \z
- /ix
-
- def disallow_raw_sql!(args, permit: COLUMN_NAME) # :nodoc:
- unexpected = args.reject do |arg|
- Arel.arel_node?(arg) ||
- arg.to_s.split(/\s*,\s*/).all? { |part| permit.match?(part) }
- end
-
- return if unexpected.none?
-
- if allow_unsafe_raw_sql == :deprecated
- ActiveSupport::Deprecation.warn(
- "Dangerous query method (method whose arguments are used as raw " \
- "SQL) called with non-attribute argument(s): " \
- "#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
- "arguments will be disallowed in Rails 6.0. This method should " \
- "not be called with user-provided values, such as request " \
- "parameters or model attributes. Known-safe values can be passed " \
- "by wrapping them in Arel.sql()."
- )
- else
- raise(ActiveRecord::UnknownAttributeReference,
- "Query method called with non-attribute argument(s): " +
- unexpected.map(&:inspect).join(", ")
- )
- end
- end
-
# Returns true if the given attribute exists, otherwise false.
#
# class Person < ActiveRecord::Base
@@ -437,7 +386,7 @@ module ActiveRecord
def attributes_for_update(attribute_names)
attribute_names &= self.class.column_names
attribute_names.delete_if do |name|
- readonly_attribute?(name)
+ self.class.readonly_attribute?(name)
end
end
@@ -460,12 +409,8 @@ module ActiveRecord
end
end
- def readonly_attribute?(name)
- self.class.readonly_attributes.include?(name)
- end
-
def pk_attribute?(name)
- name == self.class.primary_key
+ name == @primary_key
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index dc239ff9ea..4a7b6c60e5 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -46,6 +46,7 @@ module ActiveRecord
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
def read_attribute_before_type_cast(attr_name)
+ sync_with_transaction_state if @transaction_state&.finalized?
@attributes[attr_name.to_s].value_before_type_cast
end
@@ -60,17 +61,18 @@ module ActiveRecord
# task.attributes_before_type_cast
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
def attributes_before_type_cast
+ sync_with_transaction_state if @transaction_state&.finalized?
@attributes.values_before_type_cast
end
private
-
# Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
def attribute_before_type_cast(attribute_name)
read_attribute_before_type_cast(attribute_name)
end
def attribute_came_from_user?(attribute_name)
+ sync_with_transaction_state if @transaction_state&.finalized?
@attributes[attribute_name].came_from_user?
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 68ac8475b0..45341765c1 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -156,13 +156,19 @@ module ActiveRecord
end
private
+ def mutations_from_database
+ sync_with_transaction_state if @transaction_state&.finalized?
+ super
+ end
+
+ def mutations_before_last_save
+ sync_with_transaction_state if @transaction_state&.finalized?
+ super
+ end
+
def write_attribute_without_type_cast(attr_name, value)
- name = attr_name.to_s
- if self.class.attribute_alias?(name)
- name = self.class.attribute_alias(name)
- end
- result = super(name, value)
- clear_attribute_change(name)
+ result = super
+ clear_attribute_change(attr_name)
result
end
@@ -171,6 +177,11 @@ module ActiveRecord
affected_rows = super
+ if @_skip_dirty_tracking ||= false
+ clear_attribute_changes(@_touch_attr_names)
+ return affected_rows
+ end
+
changes = {}
@attributes.keys.each do |attr_name|
next if @_touch_attr_names.include?(attr_name)
@@ -187,7 +198,7 @@ module ActiveRecord
affected_rows
ensure
- @_touch_attr_names = nil
+ @_touch_attr_names, @_skip_dirty_tracking = nil, nil
end
def _update_record(attribute_names = attribute_names_for_partial_writes)
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 6af5346fa7..768c5f8c05 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -16,44 +16,35 @@ module ActiveRecord
# Returns the primary key column's value.
def id
- sync_with_transaction_state
- primary_key = self.class.primary_key
- _read_attribute(primary_key) if primary_key
+ _read_attribute(@primary_key)
end
# Sets the primary key column's value.
def id=(value)
- sync_with_transaction_state
- primary_key = self.class.primary_key
- _write_attribute(primary_key, value) if primary_key
+ _write_attribute(@primary_key, value)
end
# Queries the primary key column's value.
def id?
- sync_with_transaction_state
- query_attribute(self.class.primary_key)
+ query_attribute(@primary_key)
end
# Returns the primary key column's value before type cast.
def id_before_type_cast
- sync_with_transaction_state
- read_attribute_before_type_cast(self.class.primary_key)
+ read_attribute_before_type_cast(@primary_key)
end
# Returns the primary key column's previous value.
def id_was
- sync_with_transaction_state
- attribute_was(self.class.primary_key)
+ attribute_was(@primary_key)
end
# Returns the primary key column's value from the database.
def id_in_database
- sync_with_transaction_state
- attribute_in_database(self.class.primary_key)
+ attribute_in_database(@primary_key)
end
private
-
def attribute_method?(attr_name)
attr_name == "id" || super
end
@@ -122,13 +113,12 @@ module ActiveRecord
#
# Project.primary_key # => "foo_id"
def primary_key=(value)
- @primary_key = value && value.to_s
+ @primary_key = value && -value.to_s
@quoted_primary_key = nil
@attributes_builder = nil
end
private
-
def suppress_composite_primary_key(pk)
return pk unless pk.is_a?(Array)
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index ffac5313ad..0f0e721b24 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -7,16 +7,12 @@ module ActiveRecord
module ClassMethods # :nodoc:
private
-
def define_method_attribute(name)
- sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
-
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
generated_attribute_methods, name
) do |temp_method_name, attr_name_expr|
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{temp_method_name}
- #{sync_with_transaction_state}
name = #{attr_name_expr}
_read_attribute(name) { |n| missing_attribute(n, caller) }
end
@@ -30,19 +26,16 @@ module ActiveRecord
# to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name, &block)
name = attr_name.to_s
- if self.class.attribute_alias?(name)
- name = self.class.attribute_alias(name)
- end
+ name = self.class.attribute_aliases[name] || name
- primary_key = self.class.primary_key
- name = primary_key if name == "id" && primary_key
- sync_with_transaction_state if name == primary_key
+ name = @primary_key if name == "id" && @primary_key
_read_attribute(name, &block)
end
# This method exists to avoid the expensive primary_key check internally, without
# breaking compatibility with the read_attribute API
def _read_attribute(attr_name, &block) # :nodoc
+ sync_with_transaction_state if @transaction_state&.finalized?
@attributes.fetch_value(attr_name.to_s, &block)
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 6e0e90f39c..7bc03b9eed 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -79,7 +79,6 @@ module ActiveRecord
end
private
-
def type_incompatible_with_serialize?(type, class_name)
type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON ||
type.respond_to?(:type_cast_array, true) && class_name == ::Array
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 294a3dc32c..fb44232dff 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -25,7 +25,6 @@ module ActiveRecord
end
private
-
def convert_time_to_time_zone(value)
return if value.nil?
@@ -64,7 +63,6 @@ module ActiveRecord
module ClassMethods # :nodoc:
private
-
def inherited(subclass)
super
# We need to apply this decorator here, rather than on module inclusion. The closure
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index d5ba2f42cb..66536a8ddf 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -11,17 +11,13 @@ module ActiveRecord
module ClassMethods # :nodoc:
private
-
def define_method_attribute=(name)
- sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
-
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
generated_attribute_methods, name, writer: true,
) do |temp_method_name, attr_name_expr|
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{temp_method_name}(value)
name = #{attr_name_expr}
- #{sync_with_transaction_state}
_write_attribute(name, value)
end
RUBY
@@ -34,27 +30,24 @@ module ActiveRecord
# turned into +nil+.
def write_attribute(attr_name, value)
name = attr_name.to_s
- if self.class.attribute_alias?(name)
- name = self.class.attribute_alias(name)
- end
+ name = self.class.attribute_aliases[name] || name
- primary_key = self.class.primary_key
- name = primary_key if name == "id" && primary_key
- sync_with_transaction_state if name == primary_key
+ name = @primary_key if name == "id" && @primary_key
_write_attribute(name, value)
end
# This method exists to avoid the expensive primary_key check internally, without
# breaking compatibility with the write_attribute API
def _write_attribute(attr_name, value) # :nodoc:
+ sync_with_transaction_state if @transaction_state&.finalized?
@attributes.write_from_user(attr_name.to_s, value)
value
end
private
def write_attribute_without_type_cast(attr_name, value)
- name = attr_name.to_s
- @attributes.write_cast_value(name, value)
+ sync_with_transaction_state if @transaction_state&.finalized?
+ @attributes.write_cast_value(attr_name.to_s, value)
value
end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 7cf421c184..c7846dbe7a 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -255,7 +255,6 @@ module ActiveRecord
end
private
-
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
private_constant :NO_DEFAULT_PROVIDED
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 50f29a81a6..734ebb45ae 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -147,7 +147,6 @@ module ActiveRecord
module ClassMethods # :nodoc:
private
-
def define_non_cyclic_method(name, &block)
return if instance_methods(false).include?(name)
define_method(name) do |*args|
@@ -267,7 +266,6 @@ module ActiveRecord
end
private
-
# Returns the record for an association collection that should be validated
# or saved. If +autosave+ is +false+ only new records will be returned,
# unless the parent is/was a new record itself.
@@ -304,7 +302,7 @@ module ActiveRecord
def validate_single_association(reflection)
association = association_instance_get(reflection.name)
record = association && association.reader
- association_valid?(reflection, record) if record
+ association_valid?(reflection, record) if record && record.changed_for_autosave?
end
# Validate the associated records if <tt>:validate</tt> or
@@ -330,21 +328,16 @@ module ActiveRecord
if reflection.options[:autosave]
indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
- record.errors.each do |attribute, message|
+ record.errors.group_by_attribute.each { |attribute, errors|
attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
- errors[attribute] << message
- errors[attribute].uniq!
- end
-
- record.errors.details.each_key do |attribute|
- reflection_attribute =
- normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym
- record.errors.details[attribute].each do |error|
- errors.details[reflection_attribute] << error
- errors.details[reflection_attribute].uniq!
- end
- end
+ errors.each { |error|
+ self.errors.import(
+ error,
+ attribute: attribute
+ )
+ }
+ }
else
errors.add(reflection.name)
end
@@ -416,7 +409,7 @@ module ActiveRecord
saved = record.save(validate: false)
end
- raise ActiveRecord::Rollback unless saved
+ raise(RecordInvalid.new(association.owner)) unless saved
end
end
end
@@ -500,9 +493,7 @@ module ActiveRecord
end
def _ensure_no_duplicate_errors
- errors.messages.each_key do |attribute|
- errors[attribute].uniq!
- end
+ errors.uniq!
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 2af6d09b53..282c9fcf30 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -12,7 +12,6 @@ require "active_support/core_ext/hash/slice"
require "active_support/core_ext/string/behavior"
require "active_support/core_ext/kernel/singleton_class"
require "active_support/core_ext/module/introspection"
-require "active_support/core_ext/object/duplicable"
require "active_support/core_ext/class/subclasses"
require "active_record/attribute_decorators"
require "active_record/define_callbacks"
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index ef5444dfc3..a9ab9ab7a9 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -323,7 +323,6 @@ module ActiveRecord
end
private
-
def create_or_update(**)
_run_save_callbacks { super }
end
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index 11559141c7..881f0bcdb0 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -39,7 +39,6 @@ module ActiveRecord
end
private
-
def check_arity_of_constructor
load(nil)
rescue ArgumentError
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 68498b5dc5..36001efdd5 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -3,6 +3,7 @@
require "thread"
require "concurrent/map"
require "monitor"
+require "weakref"
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@@ -19,6 +20,26 @@ module ActiveRecord
end
module ConnectionAdapters
+ module AbstractPool # :nodoc:
+ def get_schema_cache(connection)
+ @schema_cache ||= SchemaCache.new(connection)
+ @schema_cache.connection = connection
+ @schema_cache
+ end
+
+ def set_schema_cache(cache)
+ @schema_cache = cache
+ end
+ end
+
+ class NullPool # :nodoc:
+ include ConnectionAdapters::AbstractPool
+
+ def initialize
+ @schema_cache = nil
+ end
+ end
+
# Connection pool base class for managing Active Record database
# connections.
#
@@ -146,7 +167,6 @@ module ActiveRecord
end
private
-
def internal_poll(timeout)
no_wait_poll || (timeout && wait_poll(timeout))
end
@@ -294,23 +314,50 @@ module ActiveRecord
@frequency = frequency
end
+ @mutex = Mutex.new
+ @pools = {}
+
+ class << self
+ def register_pool(pool, frequency) # :nodoc:
+ @mutex.synchronize do
+ unless @pools.key?(frequency)
+ @pools[frequency] = []
+ spawn_thread(frequency)
+ end
+ @pools[frequency] << WeakRef.new(pool)
+ end
+ end
+
+ private
+ def spawn_thread(frequency)
+ Thread.new(frequency) do |t|
+ loop do
+ sleep t
+ @mutex.synchronize do
+ @pools[frequency].select!(&:weakref_alive?)
+ @pools[frequency].each do |p|
+ p.reap
+ p.flush
+ rescue WeakRef::RefError
+ end
+ end
+ end
+ end
+ end
+ end
+
def run
return unless frequency && frequency > 0
- Thread.new(frequency, pool) { |t, p|
- loop do
- sleep t
- p.reap
- p.flush
- end
- }
+ self.class.register_pool(pool, frequency)
end
end
include MonitorMixin
include QueryCache::ConnectionPoolConfiguration
+ include ConnectionAdapters::AbstractPool
attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
- attr_reader :spec, :connections, :size, :reaper
+ attr_reader :spec, :size, :reaper
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
# object which describes database connection information (e.g. adapter,
@@ -379,7 +426,7 @@ module ActiveRecord
# #connection can be called any number of times; the connection is
# held in a cache keyed by a thread.
def connection
- @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
end
# Returns true if there is an open connection being used for the current thread.
@@ -388,7 +435,7 @@ module ActiveRecord
# #connection or #with_connection methods. Connections obtained through
# #checkout will not be detected by #active_connection?
def active_connection?
- @thread_cached_conns[connection_cache_key(Thread.current)]
+ @thread_cached_conns[connection_cache_key(current_thread)]
end
# Signal that the thread is finished with the current connection.
@@ -423,6 +470,21 @@ module ActiveRecord
synchronize { @connections.any? }
end
+ # Returns an array containing the connections currently in the pool.
+ # Access to the array does not require synchronization on the pool because
+ # the array is newly created and not retained by the pool.
+ #
+ # However; this method bypasses the ConnectionPool's thread-safe connection
+ # access pattern. A returned connection may be owned by another thread,
+ # unowned, or by happen-stance owned by the calling thread.
+ #
+ # Calling methods on a connection without ownership is subject to the
+ # thread-safety guarantees of the underlying method. Many of the methods
+ # on connection adapter classes are inherently multi-thread unsafe.
+ def connections
+ synchronize { @connections.dup }
+ end
+
# Disconnects all connections in the pool, and clears the pool.
#
# Raises:
@@ -668,6 +730,10 @@ module ActiveRecord
thread
end
+ def current_thread
+ @lock_thread || Thread.current
+ end
+
# Take control of all existing connections so a "group" action such as
# reload/disconnect can be performed safely. It is no longer enough to
# wrap it in +synchronize+ because some pool's actions are allowed
@@ -809,7 +875,6 @@ module ActiveRecord
def new_connection
Base.send(spec.adapter_method, spec.config).tap do |conn|
- conn.schema_cache = schema_cache.dup if schema_cache
conn.check_version
end
end
@@ -938,15 +1003,30 @@ module ActiveRecord
end
end
+ attr_reader :prevent_writes
+
def initialize
# These caches are keyed by spec.name (ConnectionSpecification#name).
@owner_to_pool = ConnectionHandler.create_owner_to_pool
+ @prevent_writes = false
# Backup finalizer: if the forked child never needed a pool, the above
# early discard has not occurred
ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool)
end
+ # Prevent writing to the database regardless of role.
+ #
+ # In some cases you may want to prevent writes to the database
+ # even if you are on a database that can write. `while_preventing_writes`
+ # will prevent writes to the database for the duration of the block.
+ def while_preventing_writes
+ original, @prevent_writes = @prevent_writes, true
+ yield
+ ensure
+ @prevent_writes = original
+ end
+
def connection_pool_list
owner_to_pool.values.compact
end
@@ -1064,7 +1144,6 @@ module ActiveRecord
end
private
-
def owner_to_pool
@owner_to_pool[Process.pid]
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
index 75e959045e..d932f068f2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_support/deprecation"
-
module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseLimits
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index ef19538447..044272ea51 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -205,8 +205,6 @@ module ActiveRecord
# In order to get around this problem, #transaction will emulate the effect
# of nested transactions, by using savepoints:
# https://dev.mysql.com/doc/refman/5.7/en/savepoint.html
- # Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8'
- # supports savepoints.
#
# It is safe to call this method if a database transaction is already open,
# i.e. if #transaction is called within another #transaction block. In case
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index a7753e3e9c..768122b4d2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -33,17 +33,17 @@ module ActiveRecord
end
def enable_query_cache!
- @query_cache_enabled[connection_cache_key(Thread.current)] = true
+ @query_cache_enabled[connection_cache_key(current_thread)] = true
connection.enable_query_cache! if active_connection?
end
def disable_query_cache!
- @query_cache_enabled.delete connection_cache_key(Thread.current)
+ @query_cache_enabled.delete connection_cache_key(current_thread)
connection.disable_query_cache! if active_connection?
end
def query_cache_enabled
- @query_cache_enabled[connection_cache_key(Thread.current)]
+ @query_cache_enabled[connection_cache_key(current_thread)]
end
end
@@ -109,7 +109,6 @@ module ActiveRecord
end
private
-
def cache_sql(sql, name, binds)
@lock.synchronize do
result =
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 2877530917..93273f6cf6 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -114,16 +114,16 @@ module ActiveRecord
# if the value is a Time responding to usec.
def quoted_date(value)
if value.acts_like?(:time)
- zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
-
- if value.respond_to?(zone_conversion_method)
- value = value.send(zone_conversion_method)
+ if ActiveRecord::Base.default_timezone == :utc
+ value = value.getutc if value.respond_to?(:getutc) && !value.utc?
+ else
+ value = value.getlocal if value.respond_to?(:getlocal)
end
end
result = value.to_s(:db)
if value.respond_to?(:usec) && value.usec > 0
- "#{result}.#{sprintf("%06d", value.usec)}"
+ result << "." << sprintf("%06d", value.usec)
else
result
end
@@ -142,6 +142,59 @@ module ActiveRecord
value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
end
+ def column_name_matcher # :nodoc:
+ COLUMN_NAME
+ end
+
+ def column_name_with_order_matcher # :nodoc:
+ COLUMN_NAME_WITH_ORDER
+ end
+
+ # Regexp for column names (with or without a table name prefix).
+ # Matches the following:
+ #
+ # "#{table_name}.#{column_name}"
+ # "#{column_name}"
+ COLUMN_NAME = /
+ \A
+ (
+ (?:
+ # table_name.column_name | function(one or no argument)
+ ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
+ )
+ (?:(?:\s+AS)?\s+\w+)?
+ )
+ (?:\s*,\s*\g<1>)*
+ \z
+ /ix
+
+ # Regexp for column names with order (with or without a table name prefix,
+ # with or without various order modifiers). Matches the following:
+ #
+ # "#{table_name}.#{column_name}"
+ # "#{table_name}.#{column_name} #{direction}"
+ # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
+ # "#{table_name}.#{column_name} NULLS LAST"
+ # "#{column_name}"
+ # "#{column_name} #{direction}"
+ # "#{column_name} #{direction} NULLS FIRST"
+ # "#{column_name} NULLS LAST"
+ COLUMN_NAME_WITH_ORDER = /
+ \A
+ (
+ (?:
+ # table_name.column_name | function(one or no argument)
+ ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
+ )
+ (?:\s+ASC|\s+DESC)?
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
+ )
+ (?:\s*,\s*\g<1>)*
+ \z
+ /ix
+
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
+
private
def type_casted_binds(binds)
if binds.first.is_a?(Array)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
index 52a796b926..d6dbef3fc8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
@@ -8,15 +8,15 @@ module ActiveRecord
end
def create_savepoint(name = current_savepoint_name)
- execute("SAVEPOINT #{name}")
+ execute("SAVEPOINT #{name}", "TRANSACTION")
end
def exec_rollback_to_savepoint(name = current_savepoint_name)
- execute("ROLLBACK TO SAVEPOINT #{name}")
+ execute("ROLLBACK TO SAVEPOINT #{name}", "TRANSACTION")
end
def release_savepoint(name = current_savepoint_name)
- execute("RELEASE SAVEPOINT #{name}")
+ execute("RELEASE SAVEPOINT #{name}", "TRANSACTION")
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index 7d20825a75..23c993cfc3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -19,7 +19,6 @@ module ActiveRecord
to: :@conn, private: true
private
-
def visit_AlterTable(o)
sql = +"ALTER TABLE #{quote_table_name(o.name)} "
sql << o.adds.map { |col| accept col }.join(" ")
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 688eea75e8..dbd533b4b3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -264,8 +264,7 @@ module ActiveRecord
if_not_exists: false,
options: nil,
as: nil,
- comment: nil,
- **
+ comment: nil
)
@conn = conn
@columns_hash = {}
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index 622e00fffb..fb56e712be 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -15,7 +15,7 @@ module ActiveRecord
def column_spec_for_primary_key(column)
return {} if default_primary_key?(column)
spec = { id: schema_type(column).inspect }
- spec.merge!(prepare_column_options(column).except!(:null))
+ spec.merge!(prepare_column_options(column).except!(:null, :comment))
spec[:default] ||= "nil" if explicit_primary_key_default?(column)
spec
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 7041d92586..88367c79a1 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -2,7 +2,6 @@
require "active_record/migration/join_table"
require "active_support/core_ext/string/access"
-require "active_support/deprecation"
require "digest/sha2"
module ActiveRecord
@@ -101,7 +100,7 @@ module ActiveRecord
def index_exists?(table_name, column_name, options = {})
column_names = Array(column_name).map(&:to_s)
checks = []
- checks << lambda { |i| i.columns == column_names }
+ checks << lambda { |i| Array(i.columns) == column_names }
checks << lambda { |i| i.unique } if options[:unique]
checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
@@ -291,25 +290,27 @@ module ActiveRecord
# SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
#
# See also TableDefinition#column for details on how to create columns.
- def create_table(table_name, **options)
- td = create_table_definition(table_name, options)
+ def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
+ td = create_table_definition(
+ table_name, options.extract!(:temporary, :if_not_exists, :options, :as, :comment)
+ )
- if options[:id] != false && !options[:as]
- pk = options.fetch(:primary_key) do
- Base.get_primary_key table_name.to_s.singularize
- end
+ if id && !td.as
+ pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
if pk.is_a?(Array)
td.primary_keys pk
else
- td.primary_key pk, options.fetch(:id, :primary_key), options
+ td.primary_key pk, id, options
end
end
yield td if block_given?
- if options[:force]
- drop_table(table_name, options.merge(if_exists: true))
+ if force
+ drop_table(table_name, force: force, if_exists: true)
+ else
+ schema_cache.clear_data_source_cache!(table_name.to_s)
end
result = execute schema_creation.accept td
@@ -321,7 +322,7 @@ module ActiveRecord
end
if supports_comments? && !supports_comments_in_create?
- if table_comment = options[:comment].presence
+ if table_comment = td.comment.presence
change_table_comment(table_name, table_comment)
end
@@ -499,6 +500,7 @@ module ActiveRecord
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
# In that case, +options+ and the block will be used by #create_table.
def drop_table(table_name, options = {})
+ schema_cache.clear_data_source_cache!(table_name.to_s)
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
end
@@ -518,14 +520,15 @@ module ActiveRecord
# Available options are (none of these exists by default):
# * <tt>:limit</tt> -
# Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
- # and number of bytes for <tt>:text</tt>, <tt>:binary</tt> and <tt>:integer</tt> columns.
+ # and number of bytes for <tt>:text</tt>, <tt>:binary</tt>, and <tt>:integer</tt> columns.
# This option is ignored by some backends.
# * <tt>:default</tt> -
# The column's default value. Use +nil+ for +NULL+.
# * <tt>:null</tt> -
# Allows or disallows +NULL+ values in the column.
# * <tt>:precision</tt> -
- # Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
+ # Specifies the precision for the <tt>:decimal</tt>, <tt>:numeric</tt>,
+ # <tt>:datetime</tt>, and <tt>:time</tt> columns.
# * <tt>:scale</tt> -
# Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
# * <tt>:collation</tt> -
@@ -735,7 +738,7 @@ module ActiveRecord
#
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
#
- # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite.
#
# ====== Creating an index with a specific method
#
@@ -770,6 +773,17 @@ module ActiveRecord
# CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
#
# Note: only supported by MySQL.
+ #
+ # ====== Creating an index with a specific algorithm
+ #
+ # add_index(:developers, :name, algorithm: :concurrently)
+ # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
+ #
+ # Note: only supported by PostgreSQL.
+ #
+ # Concurrently adding an index is not supported in a transaction.
+ #
+ # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
def add_index(table_name, column_name, options = {})
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
@@ -793,6 +807,15 @@ module ActiveRecord
#
# remove_index :accounts, name: :by_branch_party
#
+ # Removes the index named +by_branch_party+ in the +accounts+ table +concurrently+.
+ #
+ # remove_index :accounts, name: :by_branch_party, algorithm: :concurrently
+ #
+ # Note: only supported by PostgreSQL.
+ #
+ # Concurrently removing an index is not supported in a transaction.
+ #
+ # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
def remove_index(table_name, options = {})
index_name = index_name_for_remove(table_name, options)
execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
@@ -1040,8 +1063,8 @@ module ActiveRecord
options
end
- def dump_schema_information #:nodoc:
- versions = ActiveRecord::SchemaMigration.all_versions
+ def dump_schema_information # :nodoc:
+ versions = schema_migration.all_versions
insert_versions_sql(versions) if versions.any?
end
@@ -1057,7 +1080,7 @@ module ActiveRecord
end
version = version.to_i
- sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
+ sm_table = quote_table_name(schema_migration.table_name)
migrated = migration_context.get_all_versions
versions = migration_context.migrations.map(&:version)
@@ -1430,7 +1453,7 @@ module ActiveRecord
end
def insert_versions_sql(versions)
- sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
+ sm_table = quote_table_name(schema_migration.table_name)
if versions.is_a?(Array)
sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index c9e84e48cc..53ce8df491 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -5,10 +5,11 @@ module ActiveRecord
class TransactionState
def initialize(state = nil)
@state = state
- @children = []
+ @children = nil
end
def add_child(state)
+ @children ||= []
@children << state
end
@@ -41,12 +42,12 @@ module ActiveRecord
end
def rollback!
- @children.each { |c| c.rollback! }
+ @children&.each { |c| c.rollback! }
@state = :rolledback
end
def full_rollback!
- @children.each { |c| c.rollback! }
+ @children&.each { |c| c.rollback! }
@state = :fully_rolledback
end
@@ -75,18 +76,19 @@ module ActiveRecord
class Transaction #:nodoc:
attr_reader :connection, :state, :records, :savepoint_name, :isolation_level
- def initialize(connection, options, run_commit_callbacks: false)
+ def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
@connection = connection
@state = TransactionState.new
- @records = []
- @isolation_level = options[:isolation]
+ @records = nil
+ @isolation_level = isolation
@materialized = false
- @joinable = options.fetch(:joinable, true)
+ @joinable = joinable
@run_commit_callbacks = run_commit_callbacks
end
def add_record(record)
- records << record
+ @records ||= []
+ @records << record
end
def materialize!
@@ -98,32 +100,42 @@ module ActiveRecord
end
def rollback_records
- ite = records.uniq
+ return unless records
+ ite = records.uniq(&:object_id)
+ already_run_callbacks = {}
while record = ite.shift
- record.rolledback!(force_restore_state: full_rollback?)
+ trigger_callbacks = record.trigger_transactional_callbacks?
+ should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
+ already_run_callbacks[record] ||= trigger_callbacks
+ record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
end
ensure
- ite.each do |i|
+ ite&.each do |i|
i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
end
end
def before_commit_records
- records.uniq.each(&:before_committed!) if @run_commit_callbacks
+ records.uniq.each(&:before_committed!) if records && @run_commit_callbacks
end
def commit_records
- ite = records.uniq
+ return unless records
+ ite = records.uniq(&:object_id)
+ already_run_callbacks = {}
while record = ite.shift
if @run_commit_callbacks
- record.committed!
+ trigger_callbacks = record.trigger_transactional_callbacks?
+ should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
+ already_run_callbacks[record] ||= trigger_callbacks
+ record.committed!(should_run_callbacks: should_run_callbacks)
else
# if not running callbacks, only adds the record to the parent transaction
connection.add_transaction_record(record)
end
end
ensure
- ite.each { |i| i.committed!(should_run_callbacks: false) }
+ ite&.each { |i| i.committed!(should_run_callbacks: false) }
end
def full_rollback?; true; end
@@ -133,8 +145,8 @@ module ActiveRecord
end
class SavepointTransaction < Transaction
- def initialize(connection, savepoint_name, parent_transaction, *args)
- super(connection, *args)
+ def initialize(connection, savepoint_name, parent_transaction, **options)
+ super(connection, options)
parent_transaction.state.add_child(@state)
@@ -194,18 +206,29 @@ module ActiveRecord
@lazy_transactions_enabled = true
end
- def begin_transaction(options = {})
+ def begin_transaction(isolation: nil, joinable: true, _lazy: true)
@connection.lock.synchronize do
run_commit_callbacks = !current_transaction.joinable?
transaction =
if @stack.empty?
- RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
+ RealTransaction.new(
+ @connection,
+ isolation: isolation,
+ joinable: joinable,
+ run_commit_callbacks: run_commit_callbacks
+ )
else
- SavepointTransaction.new(@connection, "active_record_#{@stack.size}", @stack.last, options,
- run_commit_callbacks: run_commit_callbacks)
+ SavepointTransaction.new(
+ @connection,
+ "active_record_#{@stack.size}",
+ @stack.last,
+ isolation: isolation,
+ joinable: joinable,
+ run_commit_callbacks: run_commit_callbacks
+ )
end
- if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && options[:_lazy] != false
+ if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
@has_unmaterialized_transactions = true
else
transaction.materialize!
@@ -266,9 +289,9 @@ module ActiveRecord
end
end
- def within_new_transaction(options = {})
+ def within_new_transaction(isolation: nil, joinable: true)
@connection.lock.synchronize do
- transaction = begin_transaction options
+ transaction = begin_transaction(isolation: isolation, joinable: joinable)
yield
rescue Exception => error
if transaction
@@ -301,7 +324,6 @@ module ActiveRecord
end
private
-
NULL_TRANSACTION = NullTransaction.new
# Deallocate invalidated prepared statements outside of the transaction
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 200184c2f9..dc970c384b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -6,7 +6,6 @@ require "active_record/connection_adapters/sql_type_metadata"
require "active_record/connection_adapters/abstract/schema_dumper"
require "active_record/connection_adapters/abstract/schema_creation"
require "active_support/concurrency/load_interlock_aware_monitor"
-require "active_support/deprecation"
require "arel/collectors/bind"
require "arel/collectors/composite"
require "arel/collectors/sql_string"
@@ -78,7 +77,7 @@ module ActiveRecord
SIMPLE_INT = /\A\d+\z/
attr_accessor :pool
- attr_reader :schema_cache, :visitor, :owner, :logger, :lock, :prepared_statements, :prevent_writes
+ attr_reader :visitor, :owner, :logger, :lock, :prepared_statements
alias :in_use? :owner
set_callback :checkin, :after, :enable_lazy_transactions!
@@ -106,6 +105,14 @@ module ActiveRecord
Regexp.union(*parts)
end
+ def self.quoted_column_names # :nodoc:
+ @quoted_column_names ||= {}
+ end
+
+ def self.quoted_table_names # :nodoc:
+ @quoted_table_names ||= {}
+ end
+
def initialize(connection, logger = nil, config = {}) # :nodoc:
super()
@@ -114,11 +121,8 @@ module ActiveRecord
@instrumenter = ActiveSupport::Notifications.instrumenter
@logger = logger
@config = config
- @pool = nil
+ @pool = ActiveRecord::ConnectionAdapters::NullPool.new
@idle_since = Concurrent.monotonic_time
- @schema_cache = SchemaCache.new self
- @quoted_column_names, @quoted_table_names = {}, {}
- @prevent_writes = false
@visitor = arel_visitor
@statements = build_statement_pool
@lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
@@ -144,19 +148,7 @@ module ActiveRecord
# Returns true if the connection is a replica, or if +prevent_writes+
# is set to true.
def preventing_writes?
- replica? || prevent_writes
- end
-
- # Prevent writing to the database regardless of role.
- #
- # In some cases you may want to prevent writes to the database
- # even if you are on a database that can write. `while_preventing_writes`
- # will prevent writes to the database for the duration of the block.
- def while_preventing_writes
- original, @prevent_writes = @prevent_writes, true
- yield
- ensure
- @prevent_writes = original
+ replica? || ActiveRecord::Base.connection_handler.prevent_writes
end
def migrations_paths # :nodoc:
@@ -164,14 +156,32 @@ module ActiveRecord
end
def migration_context # :nodoc:
- MigrationContext.new(migrations_paths)
+ MigrationContext.new(migrations_paths, schema_migration)
+ end
+
+ def schema_migration # :nodoc:
+ @schema_migration ||= begin
+ conn = self
+ spec_name = conn.pool.spec.name
+ name = "#{spec_name}::SchemaMigration"
+
+ Class.new(ActiveRecord::SchemaMigration) do
+ define_singleton_method(:name) { name }
+ define_singleton_method(:to_s) { name }
+
+ self.connection_specification_name = spec_name
+ end
+ end
end
class Version
include Comparable
- def initialize(version_string)
+ attr_reader :full_version_string
+
+ def initialize(version_string, full_version_string = nil)
@version = version_string.split(".").map(&:to_i)
+ @full_version_string = full_version_string
end
def <=>(version_string)
@@ -203,9 +213,13 @@ module ActiveRecord
@owner = Thread.current
end
+ def schema_cache
+ @pool.get_schema_cache(self)
+ end
+
def schema_cache=(cache)
cache.connection = self
- @schema_cache = cache
+ @pool.set_schema_cache(cache)
end
# this method must only be called while holding connection pool's mutex
@@ -256,6 +270,11 @@ module ActiveRecord
self.class::ADAPTER_NAME
end
+ # Does the database for this adapter exist?
+ def self.database_exists?(config)
+ raise NotImplementedError
+ end
+
# Does this adapter support DDL rollbacks in transactions? That is, would
# CREATE TABLE or ALTER TABLE get rolled back by a transaction?
def supports_ddl_transactions?
@@ -484,6 +503,9 @@ module ActiveRecord
#
# Prevent @connection's finalizer from touching the socket, or
# otherwise communicating with its server, when it is collected.
+ if schema_cache.connection == self
+ schema_cache.connection = nil
+ end
end
# Reset the state of this connection, directing the DBMS to clear
@@ -584,7 +606,6 @@ module ActiveRecord
end
private
-
def type_map
@type_map ||= Type::TypeMap.new.tap do |mapping|
initialize_type_map(mapping)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 8b907759c6..ef1eef6b69 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -45,7 +45,6 @@ module ActiveRecord
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
private
-
def dealloc(stmt)
stmt.close
end
@@ -56,7 +55,9 @@ module ActiveRecord
end
def get_database_version #:nodoc:
- Version.new(version_string)
+ full_version_string = get_full_version
+ version_string = version_string(full_version_string)
+ Version.new(version_string, full_version_string)
end
def mariadb? # :nodoc:
@@ -174,15 +175,6 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
#++
- def explain(arel, binds = [])
- sql = "EXPLAIN #{to_sql(arel, binds)}"
- start = Concurrent.monotonic_time
- result = exec_query(sql, "EXPLAIN", binds)
- elapsed = Concurrent.monotonic_time - start
-
- MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
- end
-
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
materialize_transactions
@@ -202,7 +194,7 @@ module ActiveRecord
end
def begin_db_transaction
- execute "BEGIN"
+ execute("BEGIN", "TRANSACTION")
end
def begin_isolated_db_transaction(isolation)
@@ -211,11 +203,11 @@ module ActiveRecord
end
def commit_db_transaction #:nodoc:
- execute "COMMIT"
+ execute("COMMIT", "TRANSACTION")
end
def exec_rollback_db_transaction #:nodoc:
- execute "ROLLBACK"
+ execute("ROLLBACK", "TRANSACTION")
end
def empty_insert_statement_value(primary_key = nil)
@@ -296,6 +288,8 @@ module ActiveRecord
# Example:
# rename_table('octopuses', 'octopi')
def rename_table(table_name, new_name)
+ schema_cache.clear_data_source_cache!(table_name.to_s)
+ schema_cache.clear_data_source_cache!(new_name.to_s)
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
rename_table_indexes(table_name, new_name)
end
@@ -316,6 +310,7 @@ module ActiveRecord
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
# In that case, +options+ and the block will be used by create_table.
def drop_table(table_name, options = {})
+ schema_cache.clear_data_source_cache!(table_name.to_s)
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
@@ -481,12 +476,12 @@ module ActiveRecord
# distinct queries, and requires that the ORDER BY include the distinct column.
# See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
def columns_for_distinct(columns, orders) # :nodoc:
- order_columns = orders.reject(&:blank?).map { |s|
+ order_columns = orders.compact_blank.map { |s|
# Convert Arel node to string
s = s.to_sql unless s.is_a?(String)
# Remove any ASC/DESC modifiers
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
- }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
+ }.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
(order_columns << super).join(", ")
end
@@ -520,7 +515,6 @@ module ActiveRecord
end
private
-
def initialize_type_map(m = type_map)
super
@@ -625,7 +619,11 @@ module ActiveRecord
when ER_QUERY_INTERRUPTED
QueryCanceled.new(message, sql: sql, binds: binds)
else
- super
+ if exception.is_a?(Mysql2::Error::TimeoutError)
+ ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds)
+ else
+ super
+ end
end
end
@@ -743,7 +741,7 @@ module ActiveRecord
end.compact.join(", ")
# ...and send them all in one query
- execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
+ execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
end
def column_definitions(table_name) # :nodoc:
@@ -788,8 +786,8 @@ module ActiveRecord
MismatchedForeignKey.new(options)
end
- def version_string
- full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
+ def version_string(full_version_string)
+ full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
end
class MysqlString < Type::String # :nodoc:
@@ -802,7 +800,6 @@ module ActiveRecord
end
private
-
def cast_value(value)
case value
when true then "1"
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 279d0b9e84..2708d2756b 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -5,6 +5,8 @@ module ActiveRecord
module ConnectionAdapters
# An abstract definition of a column in a table.
class Column
+ include Deduplicable
+
attr_reader :name, :default, :sql_type_metadata, :null, :default_function, :collation, :comment
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
@@ -76,6 +78,7 @@ module ActiveRecord
def hash
Column.hash ^
name.hash ^
+ name.encoding.hash ^
default.hash ^
sql_type_metadata.hash ^
null.hash ^
@@ -83,6 +86,17 @@ module ActiveRecord
collation.hash ^
comment.hash
end
+
+ private
+ def deduplicated
+ @name = -name
+ @sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata
+ @default = -default if default
+ @default_function = -default_function if default_function
+ @collation = -collation if collation
+ @comment = -comment if comment
+ super
+ end
end
class NullColumn < Column
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 9eaf9d9a89..0732b69f81 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -50,13 +50,12 @@ module ActiveRecord
# Converts the given URL to a full connection hash.
def to_hash
- config = raw_config.reject { |_, value| value.blank? }
+ config = raw_config.compact_blank
config.map { |key, value| config[key] = uri_parser.unescape(value) if value.is_a? String }
config
end
private
-
attr_reader :uri
def uri_parser
diff --git a/activerecord/lib/active_record/connection_adapters/deduplicable.rb b/activerecord/lib/active_record/connection_adapters/deduplicable.rb
new file mode 100644
index 0000000000..fb2fd60bbc
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/deduplicable.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module ActiveRecord
+ module ConnectionAdapters # :nodoc:
+ module Deduplicable
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def registry
+ @registry ||= {}
+ end
+
+ def new(*)
+ super.deduplicate
+ end
+ end
+
+ def deduplicate
+ self.class.registry[self] ||= deduplicated
+ end
+ alias :-@ :deduplicate
+
+ private
+ def deduplicated
+ freeze
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb b/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb
index 1df4dea2d8..97d74df529 100644
--- a/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb
+++ b/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb
@@ -5,7 +5,7 @@ module ActiveRecord
module DetermineIfPreparableVisitor
attr_accessor :preparable
- def accept(*)
+ def accept(object, collector)
@preparable = true
super
end
@@ -20,7 +20,7 @@ module ActiveRecord
super
end
- def visit_Arel_Nodes_SqlLiteral(*)
+ def visit_Arel_Nodes_SqlLiteral(o, collector)
@preparable = false
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
index 2132e5d248..bbcdc96cdc 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -26,6 +26,15 @@ module ActiveRecord
!READ_QUERY.match?(sql)
end
+ def explain(arel, binds = [])
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
+ start = Concurrent.monotonic_time
+ result = exec_query(sql, "EXPLAIN", binds)
+ elapsed = Concurrent.monotonic_time - start
+
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
+ end
+
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
if preventing_writes? && write_query?(sql)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb
index 20c3c83664..edd5ea0542 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb
@@ -37,7 +37,6 @@ module ActiveRecord
end
private
-
def compute_column_widths(result)
[].tap do |widths|
result.columns.each_with_index do |column, i|
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
index 75564a61d6..0069f5871c 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
@@ -5,11 +5,11 @@ module ActiveRecord
module MySQL
module Quoting # :nodoc:
def quote_column_name(name)
- @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
+ self.class.quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
end
def quote_table_name(name)
- @quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
+ self.class.quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
end
def unquoted_true
@@ -32,12 +32,49 @@ module ActiveRecord
"x'#{value.hex}'"
end
- def _type_cast(value)
- case value
- when Date, Time then value
- else super
- end
+ def column_name_matcher
+ COLUMN_NAME
+ end
+
+ def column_name_with_order_matcher
+ COLUMN_NAME_WITH_ORDER
end
+
+ COLUMN_NAME = /
+ \A
+ (
+ (?:
+ # `table_name`.`column_name` | function(one or no argument)
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\)
+ )
+ (?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
+ )
+ (?:\s*,\s*\g<1>)*
+ \z
+ /ix
+
+ COLUMN_NAME_WITH_ORDER = /
+ \A
+ (
+ (?:
+ # `table_name`.`column_name` | function(one or no argument)
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\)
+ )
+ (?:\s+ASC|\s+DESC)?
+ )
+ (?:\s*,\s*\g<1>)*
+ \z
+ /ix
+
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
+
+ private
+ def _type_cast(value)
+ case value
+ when Date, Time then value
+ else super
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
index 82ed320617..0f5ab7562a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
@@ -7,7 +7,6 @@ module ActiveRecord
delegate :add_sql_comment!, :mariadb?, to: :@conn, private: true
private
-
def visit_DropForeignKey(name)
"DROP FOREIGN KEY #{name}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
index 234fb25fdf..bcd300f3db 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -41,13 +41,15 @@ module ActiveRecord
case column.sql_type
when /\Atimestamp\b/
:timestamp
+ when /\A(?:enum|set)\b/
+ column.sql_type
else
super
end
end
def schema_limit(column)
- super unless /\A(?:tiny|medium|long)?(?:text|blob)/.match?(column.sql_type)
+ super unless /\A(?:enum|set|(?:tiny|medium|long)?(?:text|blob))\b/.match?(column.sql_type)
end
def schema_precision(column)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
index 9167593064..a7232fa249 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
@@ -6,9 +6,11 @@ module ActiveRecord
class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
undef to_yaml if method_defined?(:to_yaml)
+ include Deduplicable
+
attr_reader :extra
- def initialize(type_metadata, extra: "")
+ def initialize(type_metadata, extra: nil)
super(type_metadata)
@extra = extra
end
@@ -25,6 +27,13 @@ module ActiveRecord
__getobj__.hash ^
extra.hash
end
+
+ private
+ def deduplicated
+ __setobj__(__getobj__.deduplicate)
+ @extra = -extra if extra
+ super
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 0dc880c731..1df9ac32c9 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -8,6 +8,8 @@ require "mysql2"
module ActiveRecord
module ConnectionHandling # :nodoc:
+ ER_BAD_DB_ERROR = 1049
+
# Establishes a connection to the database that's used by all Active Record objects.
def mysql2_connection(config)
config = config.symbolize_keys
@@ -22,7 +24,7 @@ module ActiveRecord
client = Mysql2::Client.new(config)
ConnectionAdapters::Mysql2Adapter.new(client, logger, nil, config)
rescue Mysql2::Error => error
- if error.message.include?("Unknown database")
+ if error.error_number == ER_BAD_DB_ERROR
raise ActiveRecord::NoDatabaseError
else
raise
@@ -42,6 +44,12 @@ module ActiveRecord
configure_connection
end
+ def self.database_exists?(config)
+ !!ActiveRecord::Base.mysql2_connection(config)
+ rescue ActiveRecord::NoDatabaseError
+ false
+ end
+
def supports_json?
!mariadb? && database_version >= "5.7.8"
end
@@ -109,12 +117,12 @@ module ActiveRecord
end
def discard! # :nodoc:
+ super
@connection.automatic_close = false
@connection = nil
end
private
-
def connect
@connection = Mysql2::Client.new(@config)
configure_connection
@@ -126,7 +134,11 @@ module ActiveRecord
end
def full_version
- @full_version ||= @connection.server_info[:version]
+ schema_cache.database_version.full_version_string
+ end
+
+ def get_full_version
+ @connection.server_info[:version]
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index ec25bb1e19..f1ecf6df30 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -23,6 +23,29 @@ module ActiveRecord
def sql_type
super.sub(/\[\]\z/, "")
end
+
+ def init_with(coder)
+ @serial = coder["serial"]
+ super
+ end
+
+ def encode_with(coder)
+ coder["serial"] = @serial
+ super
+ end
+
+ def ==(other)
+ other.is_a?(Column) &&
+ super &&
+ serial? == other.serial?
+ end
+ alias :eql? :==
+
+ def hash
+ Column.hash ^
+ super.hash ^
+ serial?.hash
+ end
end
end
PostgreSQLColumn = PostgreSQL::Column # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index d872bd662f..45ec79ca78 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -145,7 +145,7 @@ module ActiveRecord
# Begins a transaction.
def begin_db_transaction
- execute "BEGIN"
+ execute("BEGIN", "TRANSACTION")
end
def begin_isolated_db_transaction(isolation)
@@ -155,12 +155,12 @@ module ActiveRecord
# Commits a transaction.
def commit_db_transaction
- execute "COMMIT"
+ execute("COMMIT", "TRANSACTION")
end
# Aborts a transaction.
def exec_rollback_db_transaction
- execute "ROLLBACK"
+ execute("ROLLBACK", "TRANSACTION")
end
private
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
index b1dfbde86e..0bbe98145a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -77,7 +77,6 @@ module ActiveRecord
end
private
-
def type_cast_array(value, method)
if value.is_a?(::Array)
value.map { |item| type_cast_array(item, method) }
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb
index f70f09ad95..bae34472e1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb
@@ -10,7 +10,6 @@ module ActiveRecord
end
private
-
def cast_value(value)
value.to_s
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
index 7b42677101..8d4dacbd64 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
@@ -46,7 +46,6 @@ module ActiveRecord
end
private
-
HstorePair = begin
quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb
index 7f6adc351c..e52d4385ef 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb
@@ -34,7 +34,6 @@ module ActiveRecord
end
private
-
def number_for_point(number)
number.to_s.gsub(/\.0$/, "")
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
index 6434377b57..357493dfc0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
@@ -26,9 +26,9 @@ module ActiveRecord
value = value.sub(/^\((.+)\)$/, '-\1') # (4)
case value
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
+ when /^-?\D*[\d,]+\.\d{2}$/ # (1)
value.gsub!(/[^-\d.]/, "")
- when /^-?\D+[\d.]+,\d{2}$/ # (2)
+ when /^-?\D*[\d.]+,\d{2}$/ # (2)
value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
index 8c74cecc4d..e81e18ff70 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
@@ -50,7 +50,6 @@ module ActiveRecord
end
private
-
def number_for_point(number)
number.to_s.gsub(/\.0$/, "")
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
index aa7701e038..d19f1f9cf8 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -58,7 +58,6 @@ module ActiveRecord
end
private
-
def type_cast_single(value)
infinity?(value) ? value : @subtype.deserialize(value)
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
index 28abdbd073..74a28eef58 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
@@ -14,7 +14,6 @@ module ActiveRecord
end
private
-
def cast_value(value)
casted = value.to_s
casted if casted.match?(ACCEPTABLE_UUID)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index d40e0ef1f0..07b66de366 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -30,7 +30,7 @@ module ActiveRecord
# - "schema.name".table_name
# - "schema.name"."table.name"
def quote_table_name(name) # :nodoc:
- @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
+ self.class.quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
end
# Quotes schema names for use in SQL queries.
@@ -44,7 +44,7 @@ module ActiveRecord
# Quotes column names for use in SQL queries.
def quote_column_name(name) # :nodoc:
- @quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
+ self.class.quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
end
# Quote date/time values for use in SQL input.
@@ -78,6 +78,43 @@ module ActiveRecord
type_map.lookup(column.oid, column.fmod, column.sql_type)
end
+ def column_name_matcher
+ COLUMN_NAME
+ end
+
+ def column_name_with_order_matcher
+ COLUMN_NAME_WITH_ORDER
+ end
+
+ COLUMN_NAME = /
+ \A
+ (
+ (?:
+ # "table_name"."column_name"::type_name | function(one or no argument)::type_name
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
+ )
+ (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
+ )
+ (?:\s*,\s*\g<1>)*
+ \z
+ /ix
+
+ COLUMN_NAME_WITH_ORDER = /
+ \A
+ (
+ (?:
+ # "table_name"."column_name"::type_name | function(one or no argument)::type_name
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
+ )
+ (?:\s+ASC|\s+DESC)?
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
+ )
+ (?:\s*,\s*\g<1>)*
+ \z
+ /ix
+
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
+
private
def lookup_cast_type(sql_type)
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
index 84643d20da..d201e40190 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
@@ -5,7 +5,6 @@ module ActiveRecord
module PostgreSQL
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
private
-
def extensions(stream)
extensions = @connection.extensions
if extensions.any?
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 40c5e51d92..628a609521 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -55,6 +55,7 @@ module ActiveRecord
end
def drop_table(table_name, options = {}) # :nodoc:
+ schema_cache.clear_data_source_cache!(table_name.to_s)
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
@@ -376,6 +377,8 @@ module ActiveRecord
# rename_table('octopuses', 'octopi')
def rename_table(table_name, new_name)
clear_cache!
+ schema_cache.clear_data_source_cache!(table_name.to_s)
+ schema_cache.clear_data_source_cache!(new_name.to_s)
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
pk, seq = pk_and_sequence_for(new_name)
if pk
@@ -552,13 +555,13 @@ module ActiveRecord
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
# requires that the ORDER BY include the distinct column.
def columns_for_distinct(columns, orders) #:nodoc:
- order_columns = orders.reject(&:blank?).map { |s|
+ order_columns = orders.compact_blank.map { |s|
# Convert Arel node to string
s = s.to_sql unless s.is_a?(String)
# Remove any ASC/DESC modifiers
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
- }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
+ }.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
(order_columns << super).join(", ")
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
index 8bdec623af..b7f6479357 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
@@ -7,6 +7,8 @@ module ActiveRecord
class TypeMetadata < DelegateClass(SqlTypeMetadata)
undef to_yaml if method_defined?(:to_yaml)
+ include Deduplicable
+
attr_reader :oid, :fmod
def initialize(type_metadata, oid: nil, fmod: nil)
@@ -29,6 +31,12 @@ module ActiveRecord
oid.hash ^
fmod.hash
end
+
+ private
+ def deduplicated
+ __setobj__(__getobj__.deduplicate)
+ super
+ end
end
end
PostgreSQLTypeMetadata = PostgreSQL::TypeMetadata
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
index f2f4701500..e8caeb8132 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
@@ -37,7 +37,6 @@ module ActiveRecord
end
protected
-
def parts
@parts ||= [@schema, @identifier].compact
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 91318a0af1..0a7c6d8ac4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -46,7 +46,7 @@ module ActiveRecord
conn = PG.connect(conn_params)
ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config)
rescue ::PG::Error => error
- if error.message.include?("does not exist")
+ if error.message.include?(conn_params[:dbname])
raise ActiveRecord::NoDatabaseError
else
raise
@@ -259,6 +259,12 @@ module ActiveRecord
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
end
+ def self.database_exists?(config)
+ !!ActiveRecord::Base.postgresql_connection(config)
+ rescue ActiveRecord::NoDatabaseError
+ false
+ end
+
# Is this connection alive and ready for queries?
def active?
@lock.synchronize do
@@ -302,6 +308,7 @@ module ActiveRecord
end
def discard! # :nodoc:
+ super
@connection.socket_io.reopen(IO::NULL) rescue nil
@connection = nil
end
@@ -452,7 +459,6 @@ module ActiveRecord
end
private
-
# See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
VALUE_LIMIT_VIOLATION = "22001"
NUMERIC_VALUE_OUT_OF_RANGE = "22003"
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index dbfe1e4a34..5e30304864 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -27,7 +27,6 @@ module ActiveRecord
def encode_with(coder)
coder["columns"] = @columns
- coder["columns_hash"] = @columns_hash
coder["primary_keys"] = @primary_keys
coder["data_sources"] = @data_sources
coder["indexes"] = @indexes
@@ -37,16 +36,21 @@ module ActiveRecord
def init_with(coder)
@columns = coder["columns"]
- @columns_hash = coder["columns_hash"]
@primary_keys = coder["primary_keys"]
@data_sources = coder["data_sources"]
@indexes = coder["indexes"] || {}
@version = coder["version"]
@database_version = coder["database_version"]
+
+ derive_columns_hash_and_deduplicate_values
end
def primary_keys(table_name)
- @primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil
+ @primary_keys.fetch(table_name) do
+ if data_source_exists?(table_name)
+ @primary_keys[deep_deduplicate(table_name)] = deep_deduplicate(connection.primary_key(table_name))
+ end
+ end
end
# A cached lookup for table existence.
@@ -54,7 +58,7 @@ module ActiveRecord
prepare_data_sources if @data_sources.empty?
return @data_sources[name] if @data_sources.key? name
- @data_sources[name] = connection.data_source_exists?(name)
+ @data_sources[deep_deduplicate(name)] = connection.data_source_exists?(name)
end
# Add internal cache for table with +table_name+.
@@ -73,15 +77,17 @@ module ActiveRecord
# Get the columns for a table
def columns(table_name)
- @columns[table_name] ||= connection.columns(table_name)
+ @columns.fetch(table_name) do
+ @columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name))
+ end
end
# Get the columns for a table as a hash, key is the column name
# value is the column object.
def columns_hash(table_name)
- @columns_hash[table_name] ||= Hash[columns(table_name).map { |col|
- [col.name, col]
- }]
+ @columns_hash.fetch(table_name) do
+ @columns_hash[deep_deduplicate(table_name)] = columns(table_name).index_by(&:name)
+ end
end
# Checks whether the columns hash is already cached for a table.
@@ -90,7 +96,9 @@ module ActiveRecord
end
def indexes(table_name)
- @indexes[table_name] ||= connection.indexes(table_name)
+ @indexes.fetch(table_name) do
+ @indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name))
+ end
end
def database_version # :nodoc:
@@ -124,15 +132,38 @@ module ActiveRecord
def marshal_dump
# if we get current version during initialization, it happens stack over flow.
@version = connection.migration_context.current_version
- [@version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, database_version]
+ [@version, @columns, {}, @primary_keys, @data_sources, @indexes, database_version]
end
def marshal_load(array)
- @version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
- @indexes = @indexes || {}
+ @version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
+ @indexes ||= {}
+
+ derive_columns_hash_and_deduplicate_values
end
private
+ def derive_columns_hash_and_deduplicate_values
+ @columns = deep_deduplicate(@columns)
+ @columns_hash = @columns.transform_values { |columns| columns.index_by(&:name) }
+ @primary_keys = deep_deduplicate(@primary_keys)
+ @data_sources = deep_deduplicate(@data_sources)
+ @indexes = deep_deduplicate(@indexes)
+ end
+
+ def deep_deduplicate(value)
+ case value
+ when Hash
+ value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) }
+ when Array
+ value.map { |i| deep_deduplicate(i) }
+ when String, Deduplicable
+ -value
+ else
+ value
+ end
+ end
+
def prepare_data_sources
connection.data_sources.each { |source| @data_sources[source] = true }
end
diff --git a/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb
index df28df7a7c..969867e70f 100644
--- a/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb
@@ -1,9 +1,13 @@
# frozen_string_literal: true
+require "active_record/connection_adapters/deduplicable"
+
module ActiveRecord
# :stopdoc:
module ConnectionAdapters
class SqlTypeMetadata
+ include Deduplicable
+
attr_reader :sql_type, :type, :limit, :precision, :scale
def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil)
@@ -32,6 +36,12 @@ module ActiveRecord
precision.hash >> 1 ^
scale.hash >> 2
end
+
+ private
+ def deduplicated
+ @sql_type = -sql_type
+ super
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb
index 46ce1a15b5..85053acf91 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb
@@ -11,6 +11,11 @@ module ActiveRecord
!READ_QUERY.match?(sql)
end
+ def explain(arel, binds = [])
+ sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
+ SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
+ end
+
def execute(sql, name = nil) #:nodoc:
if preventing_writes? && write_query?(sql)
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
@@ -68,18 +73,17 @@ module ActiveRecord
alias :exec_update :exec_delete
def begin_db_transaction #:nodoc:
- log("begin transaction", nil) { @connection.transaction }
+ log("begin transaction", "TRANSACTION") { @connection.transaction }
end
def commit_db_transaction #:nodoc:
- log("commit transaction", nil) { @connection.commit }
+ log("commit transaction", "TRANSACTION") { @connection.commit }
end
def exec_rollback_db_transaction #:nodoc:
- log("rollback transaction", nil) { @connection.rollback }
+ log("rollback transaction", "TRANSACTION") { @connection.rollback }
end
-
private
def execute_batch(sql, name = nil)
if preventing_writes? && write_query?(sql)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
index cb9d32a577..9b74a774e5 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -13,11 +13,11 @@ module ActiveRecord
end
def quote_table_name(name)
- @quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
+ self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
end
def quote_column_name(name)
- @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
+ self.class.quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
end
def quoted_time(value)
@@ -45,8 +45,43 @@ module ActiveRecord
0
end
- private
+ def column_name_matcher
+ COLUMN_NAME
+ end
+
+ def column_name_with_order_matcher
+ COLUMN_NAME_WITH_ORDER
+ end
+ COLUMN_NAME = /
+ \A
+ (
+ (?:
+ # "table_name"."column_name" | function(one or no argument)
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
+ )
+ (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
+ )
+ (?:\s*,\s*\g<1>)*
+ \z
+ /ix
+
+ COLUMN_NAME_WITH_ORDER = /
+ \A
+ (
+ (?:
+ # "table_name"."column_name" | function(one or no argument)
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
+ )
+ (?:\s+ASC|\s+DESC)?
+ )
+ (?:\s*,\s*\g<1>)*
+ \z
+ /ix
+
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
+
+ private
def _type_cast(value)
case value
when BigDecimal
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index f5f5827d04..f4847eb6c0 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -48,8 +48,8 @@ module ActiveRecord
end
module ConnectionAdapters #:nodoc:
- # The SQLite3 adapter works SQLite 3.6.16 or newer
- # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
+ # The SQLite3 adapter works with the sqlite3-ruby drivers
+ # (available as gem from https://rubygems.org/gems/sqlite3).
#
# Options:
#
@@ -98,6 +98,16 @@ module ActiveRecord
configure_connection
end
+ def self.database_exists?(config)
+ config = config.symbolize_keys
+ if config[:database] == ":memory:"
+ return true
+ else
+ database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
+ File.exist?(database_file)
+ end
+ end
+
def supports_ddl_transactions?
true
end
@@ -201,14 +211,6 @@ module ActiveRecord
end
end
- #--
- # DATABASE STATEMENTS ======================================
- #++
- def explain(arel, binds = [])
- sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
- SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
- end
-
# SCHEMA STATEMENTS ========================================
def primary_keys(table_name) # :nodoc:
@@ -226,6 +228,8 @@ module ActiveRecord
# Example:
# rename_table('octopuses', 'octopi')
def rename_table(table_name, new_name)
+ schema_cache.clear_data_source_cache!(table_name.to_s)
+ schema_cache.clear_data_source_cache!(new_name.to_s)
exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
rename_table_indexes(table_name, new_name)
end
@@ -389,6 +393,7 @@ module ActiveRecord
if from_primary_key.is_a?(Array)
@definition.primary_keys from_primary_key
end
+
columns(from).each do |column|
column_name = options[:rename] ?
(options[:rename][column.name] ||
@@ -485,9 +490,9 @@ module ActiveRecord
result = exec_query(sql, "SCHEMA").first
if result
- # Splitting with left parentheses and picking up last will return all
+ # Splitting with left parentheses and discarding the first part will return all
# columns separated with comma(,).
- columns_string = result["sql"].split("(").last
+ columns_string = result["sql"].split("(", 2).last
columns_string.split(",").each do |column_string|
# This regex will match the column name and collation type and will save
diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
index 46bd831da7..0960feed84 100644
--- a/activerecord/lib/active_record/connection_adapters/statement_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
@@ -48,7 +48,6 @@ module ActiveRecord
end
private
-
def cache
@cache[Process.pid]
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 6782833c5a..c8cefa9906 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -109,8 +109,8 @@ module ActiveRecord
# a role. If you would like to use a different role you can pass a hash to database:
#
# ActiveRecord::Base.connected_to(database: { readonly_slow: :animals_slow_replica }) do
- # Dog.run_a_long_query # runs a long query while connected to the +animals_slow_replica+
- # using the readonly_slow role.
+ # # runs a long query while connected to the +animals_slow_replica+ using the readonly_slow role.
+ # Dog.run_a_long_query
# end
#
# When using the database key a new connection will be established every time.
@@ -173,7 +173,7 @@ module ActiveRecord
raise "Anonymous class is not allowed." unless name
config_or_env ||= DEFAULT_ENV.call.to_sym
- pool_name = self == Base ? "primary" : name
+ pool_name = primary_class? ? "primary" : name
self.connection_specification_name = pool_name
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
@@ -204,11 +204,15 @@ module ActiveRecord
# Return the specification name from the current class or its parent.
def connection_specification_name
if !defined?(@connection_specification_name) || @connection_specification_name.nil?
- return self == Base ? "primary" : superclass.connection_specification_name
+ return primary_class? ? "primary" : superclass.connection_specification_name
end
@connection_specification_name
end
+ def primary_class? # :nodoc:
+ self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
+ end
+
# Returns the configuration of the associated connection as a hash:
#
# ActiveRecord::Base.connection_config
@@ -252,7 +256,6 @@ module ActiveRecord
:clear_all_connections!, :flush_idle_connections!, to: :connection_handler
private
-
def swap_connection_handler(handler, &blk) # :nodoc:
old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
yield
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 6fed3e5c19..595ef4ee25 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -101,7 +101,6 @@ module ActiveRecord
# environment where dumping schema is rarely needed.
mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
- mattr_accessor :database_selector, instance_writer: false
##
# :singleton-method:
# Specifies which database schemas to dump when calling db:structure:dump.
@@ -175,8 +174,7 @@ module ActiveRecord
record = statement.execute([id], connection)&.first
unless record
- raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
- name, primary_key, id)
+ raise RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
end
record
end
@@ -270,7 +268,8 @@ module ActiveRecord
end
def arel_attribute(name, table = arel_table) # :nodoc:
- name = attribute_alias(name) if attribute_alias?(name)
+ name = name.to_s
+ name = attribute_aliases[name] || name
table[name]
end
@@ -287,7 +286,6 @@ module ActiveRecord
end
private
-
def cached_find_by_statement(key, &block)
cache = @find_by_statement_cache[connection.prepared_statements]
cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
@@ -318,7 +316,7 @@ module ActiveRecord
# # Instantiates a single new object
# User.new(first_name: 'Jamie')
def initialize(attributes = nil)
- self.class.define_attribute_methods
+ @new_record = true
@attributes = self.class._default_attributes.deep_dup
init_internals
@@ -355,12 +353,10 @@ module ActiveRecord
# +attributes+ should be an attributes object, and unlike the
# `initialize` method, no assignment calls are made per attribute.
def init_with_attributes(attributes, new_record = false) # :nodoc:
- init_internals
-
@new_record = new_record
@attributes = attributes
- self.class.define_attribute_methods
+ init_internals
yield self if block_given?
@@ -399,13 +395,13 @@ module ActiveRecord
##
def initialize_dup(other) # :nodoc:
@attributes = @attributes.deep_dup
- @attributes.reset(self.class.primary_key)
+ @attributes.reset(@primary_key)
_run_initialize_callbacks
@new_record = true
@destroyed = false
- @_start_transaction_state = {}
+ @_start_transaction_state = nil
@transaction_state = nil
super
@@ -466,7 +462,7 @@ module ActiveRecord
# Returns +true+ if the attributes hash has been frozen.
def frozen?
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
@attributes.frozen?
end
@@ -557,7 +553,6 @@ module ActiveRecord
end
private
-
# +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
# the array, and then rescues from the possible +NoMethodError+. If those elements are
# +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have,
@@ -571,22 +566,18 @@ module ActiveRecord
end
def init_internals
+ @primary_key = self.class.primary_key
@readonly = false
@destroyed = false
@marked_for_destruction = false
@destroyed_by_association = nil
- @new_record = true
- @_start_transaction_state = {}
+ @_start_transaction_state = nil
@transaction_state = nil
- end
- def initialize_internals_callback
+ self.class.define_attribute_methods
end
- def thaw
- if @attributes.frozen?
- @attributes = @attributes.dup
- end
+ def initialize_internals_callback
end
def custom_inspect_method_defined?
diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb
index 44b5cfc738..8baa0f5af6 100644
--- a/activerecord/lib/active_record/database_configurations.rb
+++ b/activerecord/lib/active_record/database_configurations.rb
@@ -104,18 +104,30 @@ module ActiveRecord
return configs.configurations if configs.is_a?(DatabaseConfigurations)
return configs if configs.is_a?(Array)
- build_db_config = configs.each_pair.flat_map do |env_name, config|
- walk_configs(env_name.to_s, "primary", config)
- end.flatten.compact
+ db_configs = configs.flat_map do |env_name, config|
+ if config.is_a?(Hash) && config.all? { |_, v| v.is_a?(Hash) }
+ walk_configs(env_name.to_s, config)
+ else
+ build_db_config_from_raw_config(env_name.to_s, "primary", config)
+ end
+ end
- if url = ENV["DATABASE_URL"]
- build_url_config(url, build_db_config)
- else
- build_db_config
+ current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
+
+ unless db_configs.find(&:for_current_env?)
+ db_configs << environment_url_config(current_env, "primary", {})
+ end
+
+ merge_db_environment_variables(current_env, db_configs.compact)
+ end
+
+ def walk_configs(env_name, config)
+ config.map do |spec_name, sub_config|
+ build_db_config_from_raw_config(env_name, spec_name.to_s, sub_config)
end
end
- def walk_configs(env_name, spec_name, config)
+ def build_db_config_from_raw_config(env_name, spec_name, config)
case config
when String
build_db_config_from_string(env_name, spec_name, config)
@@ -141,31 +153,27 @@ module ActiveRecord
config_without_url.delete "url"
ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url, config_without_url)
- elsif config["database"] || (config.size == 1 && config.values.all? { |v| v.is_a? String })
- ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
else
- config.each_pair.map do |sub_spec_name, sub_config|
- walk_configs(env_name, sub_spec_name, sub_config)
- end
+ ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
end
end
- def build_url_config(url, configs)
- env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
+ def merge_db_environment_variables(current_env, configs)
+ configs.map do |config|
+ next config if config.url_config? || config.env_name != current_env
- if original_config = configs.find(&:for_current_env?)
- if original_config.url_config?
- configs
- else
- configs.map do |config|
- ActiveRecord::DatabaseConfigurations::UrlConfig.new(config.env_name, config.spec_name, url, config.config)
- end
- end
- else
- configs + [ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, "primary", url)]
+ url_config = environment_url_config(current_env, config.spec_name, config.config)
+ url_config || config
end
end
+ def environment_url_config(env, spec_name, config)
+ url = ENV["DATABASE_URL"]
+ return unless url
+
+ ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, spec_name, url, config)
+ end
+
def method_missing(method, *args, &blk)
case method
when :each, :first
diff --git a/activerecord/lib/active_record/database_configurations/url_config.rb b/activerecord/lib/active_record/database_configurations/url_config.rb
index e2d30ae416..e6b4acc647 100644
--- a/activerecord/lib/active_record/database_configurations/url_config.rb
+++ b/activerecord/lib/active_record/database_configurations/url_config.rb
@@ -56,7 +56,6 @@ module ActiveRecord
end
private
-
def build_url_hash(url)
if url.nil? || /^jdbc:/.match?(url)
{ "url" => url }
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index 3bb8c6f4e3..7d9e221faa 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -49,11 +49,11 @@ module ActiveRecord
attr_reader :model, :name, :attribute_names
- def initialize(model, name)
+ def initialize(model, method_name)
@model = model
- @name = name.to_s
+ @name = method_name.to_s
@attribute_names = @name.match(self.class.pattern)[1].split("_and_")
- @attribute_names.map! { |n| @model.attribute_aliases[n] || n }
+ @attribute_names.map! { |name| @model.attribute_aliases[name] || name }
end
def valid?
@@ -69,7 +69,6 @@ module ActiveRecord
end
private
-
def body
"#{finder}(#{attributes_hash})"
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 8077630aeb..fc49f752aa 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -200,6 +200,8 @@ module ActiveRecord
# scope :active, -> { where(status: 0) }
# scope :not_active, -> { where.not(status: 0) }
if enum_scopes != false
+ klass.send(:detect_negative_condition!, value_method_name)
+
klass.send(:detect_enum_conflict!, name, value_method_name, true)
klass.scope value_method_name, -> { where(attr => value) }
@@ -261,5 +263,12 @@ module ActiveRecord
source: source
}
end
+
+ def detect_negative_condition!(method_name)
+ if method_name.start_with?("not_") && logger
+ logger.warn "An enum element in #{self.name} uses the prefix 'not_'." \
+ " This will cause a conflict with auto generated negative scopes."
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 60cf9818c1..20cc987d6e 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -38,6 +38,10 @@ module ActiveRecord
class AdapterNotSpecified < ActiveRecordError
end
+ # Raised when a model makes a query but it has not specified an associated table.
+ class TableNotSpecified < ActiveRecordError
+ end
+
# Raised when Active Record cannot find database adapter specified in
# +config/database.yml+ or programmatically.
class AdapterNotFound < ActiveRecordError
@@ -349,16 +353,24 @@ module ActiveRecord
class IrreversibleOrderError < ActiveRecordError
end
+ # Superclass for errors that have been aborted (either by client or server).
+ class QueryAborted < StatementInvalid
+ end
+
# LockWaitTimeout will be raised when lock wait timeout exceeded.
class LockWaitTimeout < StatementInvalid
end
# StatementTimeout will be raised when statement timeout exceeded.
- class StatementTimeout < StatementInvalid
+ class StatementTimeout < QueryAborted
end
# QueryCanceled will be raised when canceling statement due to user request.
- class QueryCanceled < StatementInvalid
+ class QueryCanceled < QueryAborted
+ end
+
+ # AdapterTimeout will be raised when database clients times out while waiting from the server.
+ class AdapterTimeout < QueryAborted
end
# UnknownAttributeReference is raised when an unknown and potentially unsafe
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 919e96cd7a..5dca75c539 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -36,7 +36,6 @@ module ActiveRecord
end
private
-
def render_bind(attr)
value = if attr.type.binary? && attr.value
"<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
diff --git a/activerecord/lib/active_record/fixture_set/table_row.rb b/activerecord/lib/active_record/fixture_set/table_row.rb
index cb4726f1ee..f65329f91d 100644
--- a/activerecord/lib/active_record/fixture_set/table_row.rb
+++ b/activerecord/lib/active_record/fixture_set/table_row.rb
@@ -48,7 +48,6 @@ module ActiveRecord
end
private
-
def model_metadata
@table_rows.model_metadata
end
diff --git a/activerecord/lib/active_record/fixture_set/table_rows.rb b/activerecord/lib/active_record/fixture_set/table_rows.rb
index 23814b6cb5..df1cd63963 100644
--- a/activerecord/lib/active_record/fixture_set/table_rows.rb
+++ b/activerecord/lib/active_record/fixture_set/table_rows.rb
@@ -29,7 +29,6 @@ module ActiveRecord
end
private
-
def build_table_rows_from(table_name, fixtures, config)
now = config.default_timezone == :utc ? Time.now.utc : Time.now
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 327121a2a2..046ed0e95c 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -464,7 +464,6 @@ module ActiveRecord
end
private
-
def insert_class(class_names, name, klass)
# We only want to deal with AR objects.
if klass && klass < ActiveRecord::Base
@@ -570,7 +569,6 @@ module ActiveRecord
end
private
-
def read_and_insert(fixtures_directory, fixture_files, class_names, connection) # :nodoc:
fixtures_map = {}
fixture_sets = fixture_files.map do |fixture_set_name|
@@ -661,7 +659,6 @@ module ActiveRecord
end
private
-
def model_class=(class_name)
if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
@model_class = class_name
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index f77bc2e3c1..7f92174f87 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -8,9 +8,9 @@ module ActiveRecord
module VERSION
MAJOR = 6
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta3"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 9570bc6f86..5ca48fa18c 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -176,7 +176,6 @@ module ActiveRecord
end
protected
-
# Returns the class type of the record using the current module as a prefix. So descendants of
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
def compute_type(type_name)
@@ -208,7 +207,6 @@ module ActiveRecord
end
private
-
# Called by +instantiate+ to decide which class to use for a new
# record instance. For single-table inheritance, we check the record
# for a +type+ column and return the corresponding class.
@@ -272,7 +270,6 @@ module ActiveRecord
end
private
-
def initialize_internals_callback
super
ensure_proper_type
diff --git a/activerecord/lib/active_record/insert_all.rb b/activerecord/lib/active_record/insert_all.rb
index 959e5bd4d7..f6577dcbc4 100644
--- a/activerecord/lib/active_record/insert_all.rb
+++ b/activerecord/lib/active_record/insert_all.rb
@@ -21,9 +21,9 @@ module ActiveRecord
end
def execute
- message = "#{model} "
- message += "Bulk " if inserts.many?
- message += (on_duplicate == :update ? "Upsert" : "Insert")
+ message = +"#{model} "
+ message << "Bulk " if inserts.many?
+ message << (on_duplicate == :update ? "Upsert" : "Insert")
connection.exec_query to_sql, message
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index b769541e95..4a97061731 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -22,6 +22,14 @@ module ActiveRecord
#
# This is +true+, by default on Rails 5.2 and above.
class_attribute :cache_versioning, instance_writer: false, default: false
+
+ ##
+ # :singleton-method:
+ # Indicates whether to use a stable #cache_key method that is accompanied
+ # by a changing version in the #cache_version method on collections.
+ #
+ # This is +false+, by default until Rails 6.1.
+ class_attribute :collection_cache_versioning, instance_writer: false, default: false
end
# Returns a +String+, which Action Pack uses for constructing a URL to this
@@ -85,7 +93,7 @@ module ActiveRecord
# cache_version, but this method can be overwritten to return something else.
#
# Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to
- # +false+ (which it is by default until Rails 6.0).
+ # +false+.
def cache_version
return unless cache_versioning
@@ -154,7 +162,7 @@ module ActiveRecord
end
def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
- collection.compute_cache_key(timestamp_column)
+ collection.send(:compute_cache_key, timestamp_column)
end
end
diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb
index e6166581f1..8f3c6d0ee3 100644
--- a/activerecord/lib/active_record/internal_metadata.rb
+++ b/activerecord/lib/active_record/internal_metadata.rb
@@ -28,10 +28,6 @@ module ActiveRecord
where(key: key).pluck(:value).first
end
- def table_exists?
- connection.table_exists?(table_name)
- end
-
# Creates an internal metadata table with columns +key+ and +value+
def create_table
unless table_exists?
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index b7eecda59e..c2a083bf3b 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -87,7 +87,7 @@ module ActiveRecord
affected_rows = self.class._update_record(
attributes_with_values(attribute_names),
- self.class.primary_key => id_in_database,
+ @primary_key => id_in_database,
locking_column => previous_lock_value
)
@@ -110,7 +110,7 @@ module ActiveRecord
locking_column = self.class.locking_column
affected_rows = self.class._delete_record(
- self.class.primary_key => id_in_database,
+ @primary_key => id_in_database,
locking_column => read_attribute_before_type_cast(locking_column)
)
@@ -156,7 +156,6 @@ module ActiveRecord
end
private
-
# We need to apply this decorator here, rather than on module inclusion. The closure
# created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
# sub class being decorated. As such, changes to `lock_optimistically`, or
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 6b84431343..6248c2f578 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -110,7 +110,7 @@ module ActiveRecord
end
def extract_query_source_location(locations)
- backtrace_cleaner.clean(locations).first
+ backtrace_cleaner.clean(locations.lazy).first
end
end
end
diff --git a/activerecord/lib/active_record/middleware/database_selector.rb b/activerecord/lib/active_record/middleware/database_selector.rb
index b5b5df074c..7374107048 100644
--- a/activerecord/lib/active_record/middleware/database_selector.rb
+++ b/activerecord/lib/active_record/middleware/database_selector.rb
@@ -35,10 +35,10 @@ module ActiveRecord
# config.active_record.database_resolver = MyResolver
# config.active_record.database_resolver_context = MyResolver::MySession
class DatabaseSelector
- def initialize(app, resolver_klass = Resolver, context_klass = Resolver::Session, options = {})
+ def initialize(app, resolver_klass = nil, context_klass = nil, options = {})
@app = app
- @resolver_klass = resolver_klass
- @context_klass = context_klass
+ @resolver_klass = resolver_klass || Resolver
+ @context_klass = context_klass || Resolver::Session
@options = options
end
@@ -55,7 +55,6 @@ module ActiveRecord
end
private
-
def select_database(request, &blk)
context = context_klass.call(request)
resolver = resolver_klass.call(context, options)
diff --git a/activerecord/lib/active_record/middleware/database_selector/resolver.rb b/activerecord/lib/active_record/middleware/database_selector/resolver.rb
index 80b8cd7cae..3eb1039c50 100644
--- a/activerecord/lib/active_record/middleware/database_selector/resolver.rb
+++ b/activerecord/lib/active_record/middleware/database_selector/resolver.rb
@@ -44,10 +44,9 @@ module ActiveRecord
end
private
-
def read_from_primary(&blk)
- ActiveRecord::Base.connection.while_preventing_writes do
- ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
+ ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
instrumenter.instrument("database_selector.active_record.read_from_primary") do
yield
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index ed0c6d48b8..7edfec9903 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -4,6 +4,7 @@ require "benchmark"
require "set"
require "zlib"
require "active_support/core_ext/module/attribute_accessors"
+require "active_support/actionable_error"
module ActiveRecord
class MigrationError < ActiveRecordError #:nodoc:
@@ -128,6 +129,12 @@ module ActiveRecord
end
class PendingMigrationError < MigrationError #:nodoc:
+ include ActiveSupport::ActionableError
+
+ action "Run pending migrations" do
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+ end
+
def initialize(message = nil)
if !message && defined?(Rails.env)
super("Migrations are pending. To resolve this issue, run:\n\n rails db:migrate RAILS_ENV=#{::Rails.env}")
@@ -487,9 +494,9 @@ module ActiveRecord
# This migration will create the horses table for you on the way up, and
# automatically figure out how to drop the table on the way down.
#
- # Some commands like +remove_column+ cannot be reversed. If you care to
- # define how to move up and down in these cases, you should define the +up+
- # and +down+ methods as before.
+ # Some commands cannot be reversed. If you care to define how to move up
+ # and down in these cases, you should define the +up+ and +down+ methods
+ # as before.
#
# If a command cannot be reversed, an
# <tt>ActiveRecord::IrreversibleMigration</tt> exception will be raised when
@@ -561,7 +568,6 @@ module ActiveRecord
end
private
-
def connection
ActiveRecord::Base.connection
end
@@ -878,13 +884,14 @@ module ActiveRecord
def copy(destination, sources, options = {})
copied = []
+ schema_migration = options[:schema_migration] || ActiveRecord::SchemaMigration
FileUtils.mkdir_p(destination) unless File.exist?(destination)
- destination_migrations = ActiveRecord::MigrationContext.new(destination).migrations
+ destination_migrations = ActiveRecord::MigrationContext.new(destination, schema_migration).migrations
last = destination_migrations.last
sources.each do |scope, path|
- source_migrations = ActiveRecord::MigrationContext.new(path).migrations
+ source_migrations = ActiveRecord::MigrationContext.new(path, schema_migration).migrations
source_migrations.each do |migration|
source = File.binread(migration.filename)
@@ -985,7 +992,6 @@ module ActiveRecord
delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration
private
-
def migration
@migration ||= load_migration
end
@@ -1007,10 +1013,11 @@ module ActiveRecord
end
class MigrationContext #:nodoc:
- attr_reader :migrations_paths
+ attr_reader :migrations_paths, :schema_migration
- def initialize(migrations_paths)
+ def initialize(migrations_paths, schema_migration)
@migrations_paths = migrations_paths
+ @schema_migration = schema_migration
end
def migrate(target_version = nil, &block)
@@ -1041,7 +1048,7 @@ module ActiveRecord
migrations
end
- Migrator.new(:up, selected_migrations, target_version).migrate
+ Migrator.new(:up, selected_migrations, schema_migration, target_version).migrate
end
def down(target_version = nil)
@@ -1051,20 +1058,20 @@ module ActiveRecord
migrations
end
- Migrator.new(:down, selected_migrations, target_version).migrate
+ Migrator.new(:down, selected_migrations, schema_migration, target_version).migrate
end
def run(direction, target_version)
- Migrator.new(direction, migrations, target_version).run
+ Migrator.new(direction, migrations, schema_migration, target_version).run
end
def open
- Migrator.new(:up, migrations, nil)
+ Migrator.new(:up, migrations, schema_migration)
end
def get_all_versions
- if SchemaMigration.table_exists?
- SchemaMigration.all_versions.map(&:to_i)
+ if schema_migration.table_exists?
+ schema_migration.all_versions.map(&:to_i)
else
[]
end
@@ -1101,12 +1108,12 @@ module ActiveRecord
end
def migrations_status
- db_list = ActiveRecord::SchemaMigration.normalized_versions
+ db_list = schema_migration.normalized_versions
file_list = migration_files.map do |file|
version, name, scope = parse_migration_filename(file)
raise IllegalMigrationNameError.new(file) unless version
- version = ActiveRecord::SchemaMigration.normalize_migration_number(version)
+ version = schema_migration.normalize_migration_number(version)
status = db_list.delete(version) ? "up" : "down"
[status, version, (name + scope).humanize]
end.compact
@@ -1146,7 +1153,7 @@ module ActiveRecord
end
def move(direction, steps)
- migrator = Migrator.new(direction, migrations)
+ migrator = Migrator.new(direction, migrations, schema_migration)
if current_version != 0 && !migrator.current_migration
raise UnknownMigrationVersionError.new(current_version)
@@ -1165,27 +1172,28 @@ module ActiveRecord
end
end
- class Migrator #:nodoc:
+ class Migrator # :nodoc:
class << self
attr_accessor :migrations_paths
# For cases where a table doesn't exist like loading from schema cache
def current_version
- MigrationContext.new(migrations_paths).current_version
+ MigrationContext.new(migrations_paths, SchemaMigration).current_version
end
end
self.migrations_paths = ["db/migrate"]
- def initialize(direction, migrations, target_version = nil)
+ def initialize(direction, migrations, schema_migration, target_version = nil)
@direction = direction
@target_version = target_version
@migrated_versions = nil
@migrations = migrations
+ @schema_migration = schema_migration
validate(@migrations)
- ActiveRecord::SchemaMigration.create_table
+ @schema_migration.create_table
ActiveRecord::InternalMetadata.create_table
end
@@ -1239,11 +1247,10 @@ module ActiveRecord
end
def load_migrated
- @migrated_versions = Set.new(Base.connection.migration_context.get_all_versions)
+ @migrated_versions = Set.new(@schema_migration.all_versions.map(&:to_i))
end
private
-
# Used for running a specific migration.
def run_without_lock
migration = migrations.detect { |m| m.version == @target_version }
@@ -1323,10 +1330,10 @@ module ActiveRecord
def record_version_state_after_migrating(version)
if down?
migrated.delete(version)
- ActiveRecord::SchemaMigration.delete_by(version: version.to_s)
+ @schema_migration.delete_by(version: version.to_s)
else
migrated << version
- ActiveRecord::SchemaMigration.create!(version: version.to_s)
+ @schema_migration.create!(version: version.to_s)
end
end
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index efed4b0e26..67172ef395 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -118,7 +118,6 @@ module ActiveRecord
end
private
-
module StraightReversions # :nodoc:
private
{
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index ff91218696..ef78a9161e 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -13,7 +13,10 @@ module ActiveRecord
const_get(name)
end
- V6_0 = Current
+ V6_1 = Current
+
+ class V6_0 < V6_1
+ end
class V5_2 < V6_0
module TableDefinition
diff --git a/activerecord/lib/active_record/migration/join_table.rb b/activerecord/lib/active_record/migration/join_table.rb
index 9abb289bb0..45169617c1 100644
--- a/activerecord/lib/active_record/migration/join_table.rb
+++ b/activerecord/lib/active_record/migration/join_table.rb
@@ -4,7 +4,6 @@ module ActiveRecord
class Migration
module JoinTable #:nodoc:
private
-
def find_join_table_name(table_1, table_2, options = {})
options.delete(:table_name) || join_table_name(table_1, table_2)
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 55fc58e339..18f19af6be 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -456,13 +456,11 @@ module ActiveRecord
end
protected
-
def initialize_load_schema_monitor
@load_schema_monitor = Monitor.new
end
private
-
def inherited(child_class)
super
child_class.initialize_load_schema_monitor
@@ -484,6 +482,9 @@ module ActiveRecord
end
def load_schema!
+ unless table_name
+ raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name="
+ end
@columns_hash = connection.schema_cache.columns_hash(table_name).except(*ignored_columns)
@columns_hash.each do |name, column|
define_attribute(
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 8b9098df6c..ab107742ed 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -2,7 +2,6 @@
require "active_support/core_ext/hash/except"
require "active_support/core_ext/module/redefine_method"
-require "active_support/core_ext/object/try"
require "active_support/core_ext/hash/indifferent_access"
module ActiveRecord
@@ -354,7 +353,6 @@ module ActiveRecord
end
private
-
# Generates a writer method for this association. Serves as a point for
# accessing the objects in the association. For example, this method
# could generate the following:
@@ -386,7 +384,6 @@ module ActiveRecord
end
private
-
# Attribute hash keys that should not be assigned as normal attributes.
# These hash keys are nested attributes implementation details.
UNASSIGNABLE_KEYS = %w( id _destroy )
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index cf0de0fdeb..bee5b5f24a 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -60,7 +60,6 @@ module ActiveRecord
end
private
-
def exec_queries
@records = [].freeze
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 8bade8cd28..323b01ab2d 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -353,6 +353,7 @@ module ActiveRecord
end
def _insert_record(values) # :nodoc:
+ primary_key = self.primary_key
primary_key_value = nil
if primary_key && Hash === values
@@ -423,20 +424,20 @@ module ActiveRecord
# Returns true if this object hasn't been saved yet -- that is, a record
# for the object doesn't exist in the database yet; otherwise, returns false.
def new_record?
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
@new_record
end
# Returns true if this object has been destroyed, otherwise returns false.
def destroyed?
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
@destroyed
end
# Returns true if the record is persisted, i.e. it's not a new record and it was
# not destroyed, otherwise returns false.
def persisted?
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
!(@new_record || @destroyed)
end
@@ -663,8 +664,13 @@ module ActiveRecord
raise ActiveRecordError, "cannot update a new record" if new_record?
raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
+ attributes = attributes.transform_keys do |key|
+ name = key.to_s
+ self.class.attribute_aliases[name] || name
+ end
+
attributes.each_key do |key|
- verify_readonly_attribute(key.to_s)
+ verify_readonly_attribute(key)
end
id_in_database = self.id_in_database
@@ -674,7 +680,7 @@ module ActiveRecord
affected_rows = self.class._update_record(
attributes,
- self.class.primary_key => id_in_database
+ @primary_key => id_in_database
)
affected_rows == 1
@@ -843,16 +849,11 @@ module ActiveRecord
# ball.touch(:updated_at) # => raises ActiveRecordError
#
def touch(*names, time: nil)
- unless persisted?
- raise ActiveRecordError, <<-MSG.squish
- cannot touch on a new or destroyed record object. Consider using
- persisted?, new_record?, or destroyed? before touching
- MSG
- end
+ _raise_record_not_touched_error unless persisted?
attribute_names = timestamp_attributes_for_update_in_model
attribute_names |= names.map!(&:to_s).map! { |name|
- self.class.attribute_alias?(name) ? self.class.attribute_alias(name) : name
+ self.class.attribute_aliases[name] || name
}
unless attribute_names.empty?
@@ -864,7 +865,6 @@ module ActiveRecord
end
private
-
# A hook to be overridden by association modules.
def destroy_associations
end
@@ -874,7 +874,7 @@ module ActiveRecord
end
def _delete_row
- self.class._delete_record(self.class.primary_key => id_in_database)
+ self.class._delete_record(@primary_key => id_in_database)
end
def _touch_row(attribute_names, time)
@@ -890,7 +890,7 @@ module ActiveRecord
def _update_row(attribute_names, attempted_action = "update")
self.class._update_record(
attributes_with_values(attribute_names),
- self.class.primary_key => id_in_database
+ @primary_key => id_in_database
)
end
@@ -928,7 +928,7 @@ module ActiveRecord
attributes_with_values(attribute_names)
)
- self.id ||= new_id if self.class.primary_key
+ self.id ||= new_id if @primary_key
@new_record = false
@@ -938,7 +938,7 @@ module ActiveRecord
end
def verify_readonly_attribute(name)
- raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
+ raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name)
end
def _raise_record_not_destroyed
@@ -948,14 +948,21 @@ module ActiveRecord
@_association_destroy_exception = nil
end
+ def _raise_readonly_record_error
+ raise ReadOnlyRecord, "#{self.class} is marked as readonly"
+ end
+
+ def _raise_record_not_touched_error
+ raise ActiveRecordError, <<~MSG.squish
+ Cannot touch on a new or destroyed record object. Consider using
+ persisted?, new_record?, or destroyed? before touching.
+ MSG
+ end
+
# The name of the method used to touch a +belongs_to+ association when the
# +:touch+ option is used.
def belongs_to_touch_method
:touch
end
-
- def _raise_readonly_record_error
- raise ReadOnlyRecord, "#{self.class} is marked as readonly"
- end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index a1d7c893bf..d5375390c7 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -134,7 +134,6 @@ end_error
cache = YAML.load(File.read(filename))
if cache.version == current_version
- connection.schema_cache = cache
connection_pool.schema_cache = cache.dup
else
warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}."
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 447def8d77..4d9acc911b 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -2,6 +2,8 @@
require "active_record"
+databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
+
db_namespace = namespace :db do
desc "Set the environment value for the database"
task "environment:set" => :load_config do
@@ -23,7 +25,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.create_all
end
- ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
desc "Create #{spec_name} database for current environment"
task spec_name => :load_config do
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
@@ -42,7 +44,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
- ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
desc "Drop #{spec_name} database for current environment"
task spec_name => [:load_config, :check_protected_environments] do
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
@@ -78,7 +80,7 @@ db_namespace = namespace :db do
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
task migrate: :load_config do
- ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
+ ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
ActiveRecord::Base.establish_connection(db_config.config)
ActiveRecord::Tasks::DatabaseTasks.migrate
end
@@ -101,7 +103,7 @@ db_namespace = namespace :db do
end
namespace :migrate do
- ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
desc "Migrate #{spec_name} database for current environment"
task spec_name => :load_config do
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
@@ -110,7 +112,7 @@ db_namespace = namespace :db do
end
end
- # desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).'
+ desc "Rolls back the database one migration and re-migrates up (options: STEP=x, VERSION=x)."
task redo: :load_config do
raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
@@ -126,8 +128,10 @@ db_namespace = namespace :db do
# desc 'Resets your database using your migrations for the current environment'
task reset: ["db:drop", "db:create", "db:migrate"]
- # desc 'Runs the "up" for a given migration VERSION.'
+ desc 'Runs the "up" for a given migration VERSION.'
task up: :load_config do
+ ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:up")
+
raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
ActiveRecord::Tasks::DatabaseTasks.check_target_version
@@ -139,8 +143,29 @@ db_namespace = namespace :db do
db_namespace["_dump"].invoke
end
- # desc 'Runs the "down" for a given migration VERSION.'
+ namespace :up do
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
+ task spec_name => :load_config do
+ raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
+
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
+
+ ActiveRecord::Base.establish_connection(db_config.config)
+ ActiveRecord::Tasks::DatabaseTasks.check_target_version
+ ActiveRecord::Base.connection.migration_context.run(
+ :up,
+ ActiveRecord::Tasks::DatabaseTasks.target_version
+ )
+
+ db_namespace["_dump"].invoke
+ end
+ end
+ end
+
+ desc 'Runs the "down" for a given migration VERSION.'
task down: :load_config do
+ ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:down")
+
raise "VERSION is required - To go down one migration, use db:rollback" if !ENV["VERSION"] || ENV["VERSION"].empty?
ActiveRecord::Tasks::DatabaseTasks.check_target_version
@@ -152,16 +177,35 @@ db_namespace = namespace :db do
db_namespace["_dump"].invoke
end
+ namespace :down do
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
+ task spec_name => :load_config do
+ raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
+
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
+
+ ActiveRecord::Base.establish_connection(db_config.config)
+ ActiveRecord::Tasks::DatabaseTasks.check_target_version
+ ActiveRecord::Base.connection.migration_context.run(
+ :down,
+ ActiveRecord::Tasks::DatabaseTasks.target_version
+ )
+
+ db_namespace["_dump"].invoke
+ end
+ end
+ end
+
desc "Display status of migrations"
task status: :load_config do
- ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
+ ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
ActiveRecord::Base.establish_connection(db_config.config)
ActiveRecord::Tasks::DatabaseTasks.migrate_status
end
end
namespace :status do
- ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
desc "Display status of migrations for #{spec_name} database"
task spec_name => :load_config do
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
@@ -186,7 +230,7 @@ db_namespace = namespace :db do
db_namespace["_dump"].invoke
end
- # desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.'
+ desc "Drops and recreates the database from db/schema.rb for the current environment and loads the seeds."
task reset: [ "db:drop", "db:setup" ]
# desc "Retrieves the charset for the current environment's database"
@@ -208,7 +252,11 @@ db_namespace = namespace :db do
# desc "Raises an error if there are pending migrations"
task abort_if_pending_migrations: :load_config do
- pending_migrations = ActiveRecord::Base.connection.migration_context.open.pending_migrations
+ pending_migrations = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).flat_map do |db_config|
+ ActiveRecord::Base.establish_connection(db_config.config)
+
+ ActiveRecord::Base.connection.migration_context.open.pending_migrations
+ end
if pending_migrations.any?
puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
@@ -219,17 +267,57 @@ db_namespace = namespace :db do
end
end
+ namespace :abort_if_pending_migrations do
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
+ # desc "Raises an error if there are pending migrations for #{spec_name} database"
+ task spec_name => :load_config do
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
+ ActiveRecord::Base.establish_connection(db_config.config)
+
+ pending_migrations = ActiveRecord::Base.connection.migration_context.open.pending_migrations
+
+ if pending_migrations.any?
+ puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
+ pending_migrations.each do |pending_migration|
+ puts " %4d %s" % [pending_migration.version, pending_migration.name]
+ end
+ abort %{Run `rails db:migrate:#{spec_name}` to update your database then try again.}
+ end
+ end
+ end
+ end
+
desc "Creates the database, loads the schema, and initializes with the seed data (use db:reset to also drop the database first)"
task setup: ["db:schema:load_if_ruby", "db:structure:load_if_sql", :seed]
desc "Runs setup if database does not exist, or runs migrations if it does"
task prepare: :load_config do
- ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
+ seed = false
+
+ ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
ActiveRecord::Base.establish_connection(db_config.config)
- db_namespace["migrate"].invoke
+
+ # Skipped when no database
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+ if ActiveRecord::Base.dump_schema_after_migration
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config.config, ActiveRecord::Base.schema_format, db_config.spec_name)
+ end
+
rescue ActiveRecord::NoDatabaseError
- db_namespace["setup"].invoke
+ ActiveRecord::Tasks::DatabaseTasks.create_current(db_config.env_name, db_config.spec_name)
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(
+ db_config.config,
+ ActiveRecord::Base.schema_format,
+ nil,
+ db_config.env_name,
+ db_config.spec_name
+ )
+
+ seed = true
end
+
+ ActiveRecord::Base.establish_connection
+ ActiveRecord::Tasks::DatabaseTasks.load_seed if seed
end
desc "Loads the seed data from db/seeds.rb"
@@ -294,13 +382,9 @@ db_namespace = namespace :db do
namespace :schema do
desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record"
task dump: :load_config do
- require "active_record/schema_dumper"
- ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
- filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :ruby)
- File.open(filename, "w:utf-8") do |file|
- ActiveRecord::Base.establish_connection(db_config.config)
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
- end
+ ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
+ ActiveRecord::Base.establish_connection(db_config.config)
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config.config, :ruby, db_config.spec_name)
end
db_namespace["schema:dump"].reenable
@@ -318,7 +402,7 @@ db_namespace = namespace :db do
namespace :cache do
desc "Creates a db/schema_cache.yml file."
task dump: :load_config do
- ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
+ ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
ActiveRecord::Base.establish_connection(db_config.config)
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(db_config.spec_name)
ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(
@@ -330,7 +414,7 @@ db_namespace = namespace :db do
desc "Clears a db/schema_cache.yml file."
task clear: :load_config do
- ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
+ ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(db_config.spec_name)
rm_f filename, verbose: false
end
@@ -341,16 +425,9 @@ db_namespace = namespace :db do
namespace :structure do
desc "Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql"
task dump: :load_config do
- ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
+ ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
ActiveRecord::Base.establish_connection(db_config.config)
- filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :sql)
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(db_config.config, filename)
- if ActiveRecord::SchemaMigration.table_exists?
- File.open(filename, "a") do |f|
- f.puts ActiveRecord::Base.connection.dump_schema_information
- f.print "\n"
- end
- end
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config.config, :sql, db_config.spec_name)
end
db_namespace["structure:dump"].reenable
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index 7bc26993d5..c851ed52c3 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -19,6 +19,10 @@ module ActiveRecord
def readonly_attributes
_attr_readonly
end
+
+ def readonly_attribute?(name) # :nodoc:
+ _attr_readonly.include?(name)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 1312bf6f91..cbfa60d4d9 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -477,7 +477,7 @@ module ActiveRecord
def check_preloadable!
return unless scope
- if scope.arity > 0
+ unless scope.arity == 0
raise ArgumentError, <<-MSG.squish
The association scope '#{name}' is instance dependent (the scope
block takes an argument). Preloading instance dependent scopes is
@@ -590,7 +590,6 @@ module ActiveRecord
end
private
-
def calculate_constructable(macro, options)
true
end
@@ -704,7 +703,6 @@ module ActiveRecord
end
private
-
def calculate_constructable(macro, options)
!options[:through]
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index cd62b0b881..ea8f44752b 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -291,36 +291,58 @@ module ActiveRecord
limit_value ? records.many? : size > 1
end
- # Returns a cache key that can be used to identify the records fetched by
- # this query. The cache key is built with a fingerprint of the sql query,
- # the number of records matched by the query and a timestamp of the last
- # updated record. When a new record comes to match the query, or any of
- # the existing records is updated or deleted, the cache key changes.
+ # Returns a stable cache key that can be used to identify this query.
+ # The cache key is built with a fingerprint of the SQL query.
#
- # Product.where("name like ?", "%Cosmic Encounter%").cache_key
- # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
+ # # => "products/query-1850ab3d302391b85b8693e941286659"
#
- # If the collection is loaded, the method will iterate through the records
- # to generate the timestamp, otherwise it will trigger one SQL query like:
+ # If ActiveRecord::Base.collection_cache_versioning is turned off, as it was
+ # in Rails 6.0 and earlier, the cache key will also include a version.
#
- # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
+ # ActiveRecord::Base.collection_cache_versioning = false
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
+ # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
#
# You can also pass a custom timestamp column to fetch the timestamp of the
# last updated record.
#
# Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
- #
- # You can customize the strategy to generate the key on a per model basis
- # overriding ActiveRecord::Base#collection_cache_key.
def cache_key(timestamp_column = :updated_at)
@cache_keys ||= {}
- @cache_keys[timestamp_column] ||= @klass.collection_cache_key(self, timestamp_column)
+ @cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
end
def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
query_signature = ActiveSupport::Digest.hexdigest(to_sql)
key = "#{klass.model_name.cache_key}/query-#{query_signature}"
+ if cache_version(timestamp_column)
+ key
+ else
+ "#{key}-#{compute_cache_version(timestamp_column)}"
+ end
+ end
+ private :compute_cache_key
+
+ # Returns a cache version that can be used together with the cache key to form
+ # a recyclable caching scheme. The cache version is built with the number of records
+ # matching the query, and the timestamp of the last updated record. When a new record
+ # comes to match the query, or any of the existing records is updated or deleted,
+ # the cache version changes.
+ #
+ # If the collection is loaded, the method will iterate through the records
+ # to generate the timestamp, otherwise it will trigger one SQL query like:
+ #
+ # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
+ def cache_version(timestamp_column = :updated_at)
+ if collection_cache_versioning
+ @cache_versions ||= {}
+ @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
+ end
+ end
+
+ def compute_cache_version(timestamp_column) # :nodoc:
if loaded? || distinct_value
size = records.size
if size > 0
@@ -356,11 +378,12 @@ module ActiveRecord
end
if timestamp
- "#{key}-#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
+ "#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
else
- "#{key}-#{size}"
+ "#{size}"
end
end
+ private :compute_cache_version
# Scope all queries to the current scope.
#
@@ -445,7 +468,19 @@ module ActiveRecord
end
end
- def update_counters(counters) # :nodoc:
+ # Updates the counters of the records in the current relation.
+ #
+ # === Parameters
+ #
+ # * +counter+ - A Hash containing the names of the fields to update as keys and the amount to update as values.
+ # * <tt>:touch</tt> option - Touch the timestamp columns when updating.
+ # * If attributes names are passed, they are updated along with update_at/on attributes.
+ #
+ # === Examples
+ #
+ # # For Posts by a given author increment the comment_count by 1.
+ # Post.where(author_id: author.id).update_counters(comment_count: 1)
+ def update_counters(counters)
touch = counters.delete(:touch)
updates = {}
@@ -530,8 +565,8 @@ module ActiveRecord
# # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
def delete_all
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
- value = get_value(method)
- SINGLE_VALUE_METHODS.include?(method) ? value : value.any?
+ value = @values[method]
+ method == :distinct ? value : value&.any?
end
if invalid_methods.any?
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 9c579843b1..30b8edd0bd 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -258,7 +258,6 @@ module ActiveRecord
end
private
-
def apply_limits(relation, start, finish)
relation = apply_start_limit(relation, start) if start
relation = apply_finish_limit(relation, finish) if finish
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 801e312658..0a14a33c1d 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -260,10 +260,8 @@ module ActiveRecord
def aggregate_column(column_name)
return column_name if Arel::Expressions === column_name
- if @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name)
- @klass.arel_attribute(column_name)
- else
- Arel.sql(column_name == :all ? "*" : column_name.to_s)
+ arel_column(column_name.to_s) do |name|
+ Arel.sql(column_name == :all ? "*" : name)
end
end
@@ -342,7 +340,7 @@ module ActiveRecord
}
relation = except(:group).distinct!(false)
- relation.group_values = group_aliases
+ relation.group_values = group_fields
relation.select_values = select_values
calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 7a53a9d1c7..2f61c05eca 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -45,7 +45,10 @@ module ActiveRecord
private
def generated_relation_methods
- @generated_relation_methods ||= GeneratedRelationMethods.new
+ @generated_relation_methods ||= GeneratedRelationMethods.new.tap do |mod|
+ const_set(:GeneratedRelationMethods, mod)
+ private_constant :GeneratedRelationMethods
+ end
end
end
@@ -96,7 +99,6 @@ module ActiveRecord
end
private
-
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
@klass.generate_relation_method(method)
@@ -113,7 +115,6 @@ module ActiveRecord
end
private
-
def relation_class_for(klass)
klass.relation_delegate_class(self)
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 9450e4d3c5..1dbf4808fd 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -346,7 +346,6 @@ module ActiveRecord
end
private
-
def offset_index
offset_value || 0
end
@@ -355,7 +354,7 @@ module ActiveRecord
conditions = sanitize_forbidden_attributes(conditions)
if distinct_value && offset_value
- relation = limit(1)
+ relation = except(:order).limit!(1)
else
relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
end
@@ -371,7 +370,9 @@ module ActiveRecord
end
def apply_join_dependency(eager_loading: group_values.empty?)
- join_dependency = construct_join_dependency(eager_load_values + includes_values)
+ join_dependency = construct_join_dependency(
+ eager_load_values + includes_values, Arel::Nodes::OuterJoin
+ )
relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
if eager_loading && !using_limitable_reflections?(join_dependency.reflections)
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 6bb77b355c..e1735c0522 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -89,7 +89,6 @@ module ActiveRecord
end
private
-
def merge_preloads
return if other.preload_values.empty? && other.includes_values.empty?
@@ -123,7 +122,9 @@ module ActiveRecord
end
end
- join_dependency = other.construct_join_dependency(associations)
+ join_dependency = other.construct_join_dependency(
+ associations, Arel::Nodes::InnerJoin
+ )
relation.joins!(join_dependency, *others)
end
end
@@ -135,7 +136,9 @@ module ActiveRecord
relation.left_outer_joins!(*other.left_outer_joins_values)
else
associations = other.left_outer_joins_values
- join_dependency = other.construct_join_dependency(associations)
+ join_dependency = other.construct_join_dependency(
+ associations, Arel::Nodes::OuterJoin
+ )
relation.joins!(join_dependency)
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 90b5e9a118..6957ba052b 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -41,18 +41,31 @@ module ActiveRecord
#
# User.where.not(name: %w(Ko1 Nobu))
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
- #
- # User.where.not(name: "Jon", role: "admin")
- # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
def not(opts, *rest)
opts = sanitize_forbidden_attributes(opts)
where_clause = @scope.send(:where_clause_factory).build(opts, rest)
@scope.references!(PredicateBuilder.references(opts)) if Hash === opts
- @scope.where_clause += where_clause.invert
+
+ if not_behaves_as_nor?(opts)
+ ActiveSupport::Deprecation.warn(<<~MSG.squish)
+ NOT conditions will no longer behave as NOR in Rails 6.1.
+ To continue using NOR conditions, NOT each conditions manually
+ (`#{ opts.keys.map { |key| ".where.not(#{key.inspect} => ...)" }.join }`).
+ MSG
+ @scope.where_clause += where_clause.invert(:nor)
+ else
+ @scope.where_clause += where_clause.invert
+ end
+
@scope
end
+
+ private
+ def not_behaves_as_nor?(opts)
+ opts.is_a?(Hash) && opts.size > 1
+ end
end
FROZEN_EMPTY_ARRAY = [].freeze
@@ -67,11 +80,13 @@ module ActiveRecord
end
class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{method_name} # def includes_values
- get_value(#{name.inspect}) # get_value(:includes)
+ default = DEFAULT_VALUES[:#{name}] # default = DEFAULT_VALUES[:includes]
+ @values.fetch(:#{name}, default) # @values.fetch(:includes, default)
end # end
def #{method_name}=(value) # def includes_values=(value)
- set_value(#{name.inspect}, value) # set_value(:includes, value)
+ assert_mutability! # assert_mutability!
+ @values[:#{name}] = value # @values[:includes] = value
end # end
CODE
end
@@ -123,7 +138,7 @@ module ActiveRecord
end
def includes!(*args) # :nodoc:
- args.reject!(&:blank?)
+ args.compact_blank!
args.flatten!
self.includes_values |= args
@@ -250,7 +265,7 @@ module ActiveRecord
end
def _select!(*fields) # :nodoc:
- fields.reject!(&:blank?)
+ fields.compact_blank!
fields.flatten!
self.select_values += fields
self
@@ -417,7 +432,8 @@ module ActiveRecord
if !VALID_UNSCOPING_VALUES.include?(scope)
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
end
- set_value(scope, DEFAULT_VALUES[scope])
+ assert_mutability!
+ @values[scope] = DEFAULT_VALUES[scope]
when Hash
scope.each do |key, target_value|
if key != :where
@@ -936,7 +952,7 @@ module ActiveRecord
def optimizer_hints!(*args) # :nodoc:
args.flatten!
- self.optimizer_hints_values += args
+ self.optimizer_hints_values |= args
self
end
@@ -949,7 +965,7 @@ module ActiveRecord
def reverse_order! # :nodoc:
orders = order_values.uniq
- orders.reject!(&:blank?)
+ orders.compact_blank!
self.order_values = reverse_sql_order(orders)
self
end
@@ -989,9 +1005,9 @@ module ActiveRecord
@arel ||= build_arel(aliases)
end
- def construct_join_dependency(associations) # :nodoc:
+ def construct_join_dependency(associations, join_type) # :nodoc:
ActiveRecord::Associations::JoinDependency.new(
- klass, table, associations
+ klass, table, associations, join_type
)
end
@@ -1005,17 +1021,6 @@ module ActiveRecord
end
private
- # Returns a relation value with a given name
- def get_value(name)
- @values.fetch(name, DEFAULT_VALUES[name])
- end
-
- # Sets the relation value with the given name
- def set_value(name, value)
- assert_mutability!
- @values[name] = value
- end
-
def assert_mutability!
raise ImmutableRelation if @loaded
raise ImmutableRelation if defined?(@arel) && @arel
@@ -1048,7 +1053,7 @@ module ActiveRecord
)
arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
end
- arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
+ arel.group(*arel_columns(group_values.uniq.compact_blank)) unless group_values.empty?
build_order(arel)
@@ -1097,7 +1102,7 @@ module ActiveRecord
def build_joins(manager, joins, aliases)
unless left_outer_joins_values.empty?
left_joins = valid_association_list(left_outer_joins_values.flatten)
- joins << construct_join_dependency(left_joins)
+ joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
end
buckets = joins.group_by do |join|
@@ -1123,27 +1128,21 @@ module ActiveRecord
association_joins = buckets[:association_join]
stashed_joins = buckets[:stashed_join]
- join_nodes = buckets[:join_node].uniq
- string_joins = buckets[:string_join].map(&:strip).uniq
+ join_nodes = buckets[:join_node].tap(&:uniq!)
+ string_joins = buckets[:string_join].compact_blank!.map!(&:strip).tap(&:uniq!)
- join_list = join_nodes + convert_join_strings_to_ast(string_joins)
- alias_tracker = alias_tracker(join_list, aliases)
+ string_joins.map! { |join| table.create_string_join(Arel.sql(join)) }
- join_dependency = construct_join_dependency(association_joins)
+ join_sources = manager.join_sources
+ join_sources.concat(join_nodes) unless join_nodes.empty?
- joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
- joins.each { |join| manager.from(join) }
-
- manager.join_sources.concat(join_list)
-
- alias_tracker.aliases
- end
+ unless association_joins.empty? && stashed_joins.empty?
+ alias_tracker = alias_tracker(join_nodes + string_joins, aliases)
+ join_dependency = construct_join_dependency(association_joins, join_type)
+ join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
+ end
- def convert_join_strings_to_ast(joins)
- joins
- .flatten
- .reject(&:blank?)
- .map { |join| table.create_string_join(Arel.sql(join)) }
+ join_sources.concat(string_joins) unless string_joins.empty?
end
def build_select(arel)
@@ -1160,8 +1159,9 @@ module ActiveRecord
columns.flat_map do |field|
case field
when Symbol
- field = field.to_s
- arel_column(field, &connection.method(:quote_table_name))
+ arel_column(field.to_s) do |attr_name|
+ connection.quote_table_name(attr_name)
+ end
when String
arel_column(field, &:itself)
when Proc
@@ -1173,7 +1173,7 @@ module ActiveRecord
end
def arel_column(field)
- field = klass.attribute_alias(field) if klass.attribute_alias?(field)
+ field = klass.attribute_aliases[field] || field
from = from_clause.name || from_clause.value
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
@@ -1227,7 +1227,7 @@ module ActiveRecord
def build_order(arel)
orders = order_values.uniq
- orders.reject!(&:blank?)
+ orders.compact_blank!
arel.order(*orders) unless orders.empty?
end
@@ -1248,6 +1248,7 @@ module ActiveRecord
end
def preprocess_order_args(order_args)
+ order_args.reject!(&:blank?)
order_args.map! do |arg|
klass.sanitize_sql_for_order(arg)
end
@@ -1255,7 +1256,7 @@ module ActiveRecord
@klass.disallow_raw_sql!(
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
- permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
+ permit: connection.column_name_with_order_matcher
)
validate_order_args(order_args)
@@ -1268,20 +1269,14 @@ module ActiveRecord
order_args.map! do |arg|
case arg
when Symbol
- arg = arg.to_s
- arel_column(arg) {
- Arel.sql(connection.quote_table_name(arg))
- }.asc
+ order_column(arg.to_s).asc
when Hash
arg.map { |field, dir|
case field
when Arel::Nodes::SqlLiteral
field.send(dir.downcase)
else
- field = field.to_s
- arel_column(field) {
- Arel.sql(connection.quote_table_name(field))
- }.send(dir.downcase)
+ order_column(field.to_s).send(dir.downcase)
end
}
else
@@ -1290,6 +1285,16 @@ module ActiveRecord
end.flatten!
end
+ def order_column(field)
+ arel_column(field) do |attr_name|
+ if attr_name == "count" && !group_values.empty?
+ arel_attribute(attr_name)
+ else
+ Arel.sql(connection.quote_table_name(attr_name))
+ end
+ end
+ end
+
# Checks to make sure that the arguments are not blank. Note that if some
# blank-like object were initially passed into the query method, then this
# method will not raise an error.
@@ -1316,7 +1321,8 @@ module ActiveRecord
def structurally_incompatible_values_for_or(other)
values = other.values
STRUCTURAL_OR_METHODS.reject do |method|
- get_value(method) == values.fetch(method, DEFAULT_VALUES[method])
+ default = DEFAULT_VALUES[method]
+ @values.fetch(method, default) == values.fetch(method, default)
end
end
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index efc4b447aa..3f6dd50139 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -67,7 +67,6 @@ module ActiveRecord
end
private
-
def relation_with(values)
result = Relation.create(klass, values: values)
result.extend(*extending_values) if extending_values.any?
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 47728aac30..8fae380b0a 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -70,7 +70,15 @@ module ActiveRecord
predicates == other.predicates
end
- def invert
+ def invert(as = :nand)
+ if predicates.size == 1
+ inverted_predicates = [ invert_predicate(predicates.first) ]
+ elsif as == :nor
+ inverted_predicates = predicates.map { |node| invert_predicate(node) }
+ else
+ inverted_predicates = [ Arel::Nodes::Not.new(ast) ]
+ end
+
WhereClause.new(inverted_predicates)
end
@@ -79,7 +87,6 @@ module ActiveRecord
end
protected
-
attr_reader :predicates
def referenced_columns
@@ -115,10 +122,6 @@ module ActiveRecord
node.respond_to?(:operator) && node.operator == :==
end
- def inverted_predicates
- predicates.map { |node| invert_predicate(node) }
- end
-
def invert_predicate(node)
case node
when NilClass
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index da6d10b6ec..3b615f29a3 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -132,7 +132,6 @@ module ActiveRecord
end
private
-
def column_type(name, type_overrides = {})
type_overrides.fetch(name) do
column_types.fetch(name, Type.default_value)
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 750766714d..b16cbb0f84 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -61,8 +61,9 @@ module ActiveRecord
# # => "id ASC"
def sanitize_sql_for_order(condition)
if condition.is_a?(Array) && condition.first.to_s.include?("?")
- disallow_raw_sql!([condition.first],
- permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
+ disallow_raw_sql!(
+ [condition.first],
+ permit: connection.column_name_with_order_matcher
)
# Ensure we aren't dealing with a subclass of String that might
@@ -133,6 +134,33 @@ module ActiveRecord
end
end
+ def disallow_raw_sql!(args, permit: connection.column_name_matcher) # :nodoc:
+ unexpected = nil
+ args.each do |arg|
+ next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s)
+ (unexpected ||= []) << arg
+ end
+
+ return unless unexpected
+
+ if allow_unsafe_raw_sql == :deprecated
+ ActiveSupport::Deprecation.warn(
+ "Dangerous query method (method whose arguments are used as raw " \
+ "SQL) called with non-attribute argument(s): " \
+ "#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
+ "arguments will be disallowed in Rails 6.1. This method should " \
+ "not be called with user-provided values, such as request " \
+ "parameters or model attributes. Known-safe values can be passed " \
+ "by wrapping them in Arel.sql()."
+ )
+ else
+ raise(ActiveRecord::UnknownAttributeReference,
+ "Query method called with non-attribute argument(s): " +
+ unexpected.map(&:inspect).join(", ")
+ )
+ end
+ end
+
private
def replace_bind_variables(statement, values)
raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 76bf53387d..aba25fb375 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -50,7 +50,7 @@ module ActiveRecord
instance_eval(&block)
if info[:version].present?
- ActiveRecord::SchemaMigration.create_table
+ connection.schema_migration.create_table
connection.assume_migrated_upto_version(info[:version])
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 2f7cc07221..f4b1f536b3 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -146,7 +146,11 @@ HEADER
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
next if column.name == pk
type, colspec = column_spec(column)
- tbl.print " t.#{type} #{column.name.inspect}"
+ if type.is_a?(Symbol)
+ tbl.print " t.#{type} #{column.name.inspect}"
+ else
+ tbl.print " t.column #{column.name.inspect}, #{type.inspect}"
+ end
tbl.print ", #{format_colspec(colspec)}" if colspec.present?
tbl.puts
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index 74547de862..dec7fee986 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -22,10 +22,6 @@ module ActiveRecord
"#{table_name_prefix}#{schema_migrations_table_name}#{table_name_suffix}"
end
- def table_exists?
- connection.table_exists?(table_name)
- end
-
def create_table
unless table_exists?
version_options = connection.internal_string_options_for_primary_key
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index 35e9dcbffc..62c7988bd8 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -95,7 +95,6 @@ module ActiveRecord
end
private
-
def raise_invalid_scope_type!(scope_type)
if !VALID_SCOPE_TYPES.include?(scope_type)
raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 87bcfd5181..151eef362b 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -44,7 +44,6 @@ module ActiveRecord
end
private
-
# Use this macro in your model to set a default scope for all operations on
# the model.
#
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index cd9801b7a0..7baef99e83 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -204,7 +204,6 @@ module ActiveRecord
end
private
-
def valid_scope_name?(name)
if respond_to?(name, true) && logger
logger.warn "Creating scope :#{name}. " \
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index b67479fb6a..9a1176db6a 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -4,17 +4,18 @@ module ActiveRecord
class TableMetadata # :nodoc:
delegate :foreign_type, :foreign_key, :join_primary_key, :join_foreign_key, to: :association, prefix: true
- def initialize(klass, arel_table, association = nil)
+ def initialize(klass, arel_table, association = nil, types = klass)
@klass = klass
+ @types = types
@arel_table = arel_table
@association = association
end
def resolve_column_aliases(hash)
new_hash = hash.dup
- hash.each do |key, _|
- if (key.is_a?(Symbol)) && klass.attribute_alias?(key)
- new_hash[klass.attribute_alias(key)] = new_hash.delete(key)
+ hash.each_key do |key|
+ if key.is_a?(Symbol) && new_key = klass.attribute_aliases[key.to_s]
+ new_hash[new_key] = new_hash.delete(key)
end
end
new_hash
@@ -29,11 +30,7 @@ module ActiveRecord
end
def type(column_name)
- if klass
- klass.type_for_attribute(column_name)
- else
- Type.default_value
- end
+ types.type_for_attribute(column_name)
end
def has_column?(column_name)
@@ -52,13 +49,12 @@ module ActiveRecord
elsif association && !association.polymorphic?
association_klass = association.klass
arel_table = association_klass.arel_table.alias(table_name)
+ TableMetadata.new(association_klass, arel_table, association)
else
type_caster = TypeCaster::Connection.new(klass, table_name)
- association_klass = nil
arel_table = Arel::Table.new(table_name, type_caster: type_caster)
+ TableMetadata.new(nil, arel_table, association, type_caster)
end
-
- TableMetadata.new(association_klass, arel_table, association)
end
def polymorphic_association?
@@ -74,6 +70,6 @@ module ActiveRecord
end
private
- attr_reader :klass, :arel_table, :association
+ attr_reader :klass, :types, :arel_table, :association
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 7285c15477..5d1ce19829 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -141,8 +141,21 @@ module ActiveRecord
end
end
- def for_each
- databases = Rails.application.config.load_database_yaml
+ def setup_initial_database_yaml
+ return {} unless defined?(Rails)
+
+ begin
+ Rails.application.config.load_database_yaml
+ rescue
+ $stderr.puts "Rails couldn't infer whether you are using multiple databases from your database.yml and can't generate the tasks for the non-primary databases. If you'd like to use this feature, please simplify your ERB."
+
+ {}
+ end
+ end
+
+ def for_each(databases)
+ return {} unless defined?(Rails)
+
database_configs = ActiveRecord::DatabaseConfigurations.new(databases).configs_for(env_name: Rails.env)
# if this is a single database application we don't want tasks for each primary database
@@ -153,8 +166,22 @@ module ActiveRecord
end
end
- def create_current(environment = env)
- each_current_configuration(environment) { |configuration|
+ def raise_for_multi_db(environment = env, command:)
+ db_configs = ActiveRecord::Base.configurations.configs_for(env_name: environment)
+
+ if db_configs.count > 1
+ dbs_list = []
+
+ db_configs.each do |db|
+ dbs_list << "#{command}:#{db.spec_name}"
+ end
+
+ raise "You're using a multiple database application. To use `#{command}` you must run the namespaced task with a VERSION. Available tasks are #{dbs_list.to_sentence}."
+ end
+ end
+
+ def create_current(environment = env, spec_name = nil)
+ each_current_configuration(environment, spec_name) { |configuration|
create configuration
}
ActiveRecord::Base.establish_connection(environment.to_sym)
@@ -184,9 +211,10 @@ module ActiveRecord
def truncate_tables(configuration)
ActiveRecord::Base.connected_to(database: { truncation: configuration }) do
- table_names = ActiveRecord::Base.connection.tables
+ conn = ActiveRecord::Base.connection
+ table_names = conn.tables
table_names -= [
- SchemaMigration.table_name,
+ conn.schema_migration.table_name,
InternalMetadata.table_name
]
@@ -217,7 +245,7 @@ module ActiveRecord
end
def migrate_status
- unless ActiveRecord::SchemaMigration.table_exists?
+ unless ActiveRecord::Base.connection.schema_migration.table_exists?
Kernel.abort "Schema migrations table does not exist yet."
end
@@ -309,6 +337,27 @@ module ActiveRecord
Migration.verbose = verbose_was
end
+ def dump_schema(configuration, format = ActiveRecord::Base.schema_format, spec_name = "primary") # :nodoc:
+ require "active_record/schema_dumper"
+ filename = dump_filename(spec_name, format)
+ connection = ActiveRecord::Base.connection
+
+ case format
+ when :ruby
+ File.open(filename, "w:utf-8") do |file|
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
+ end
+ when :sql
+ structure_dump(configuration, filename)
+ if connection.schema_migration.table_exists?
+ File.open(filename, "a") do |f|
+ f.puts connection.dump_schema_information
+ f.print "\n"
+ end
+ end
+ end
+ end
+
def schema_file(format = ActiveRecord::Base.schema_format)
File.join(db_dir, schema_file_type(format))
end
@@ -390,12 +439,14 @@ module ActiveRecord
task.is_a?(String) ? task.constantize : task
end
- def each_current_configuration(environment)
+ def each_current_configuration(environment, spec_name = nil)
environments = [environment]
environments << "test" if environment == "development"
environments.each do |env|
ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config|
+ next if spec_name && spec_name != db_config.spec_name
+
yield db_config.config, db_config.spec_name, env
end
end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 1c1b29b5e1..e3efeb75b5 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -3,6 +3,8 @@
module ActiveRecord
module Tasks # :nodoc:
class MySQLDatabaseTasks # :nodoc:
+ ER_DB_CREATE_EXISTS = 1007
+
delegate :connection, :establish_connection, to: ActiveRecord::Base
def initialize(configuration)
@@ -14,7 +16,7 @@ module ActiveRecord
connection.create_database configuration["database"], creation_options
establish_connection configuration
rescue ActiveRecord::StatementInvalid => error
- if error.message.include?("database exists")
+ if connection.error_number(error.cause) == ER_DB_CREATE_EXISTS
raise DatabaseAlreadyExists
else
raise
@@ -67,7 +69,6 @@ module ActiveRecord
end
private
-
attr_reader :configuration
def configuration_without_database
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 8acb11f75f..626ffdfdf9 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -89,7 +89,6 @@ module ActiveRecord
end
private
-
attr_reader :configuration
def encoding
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
index a82cea80ca..f67a3498b6 100644
--- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -59,7 +59,6 @@ module ActiveRecord
end
private
-
attr_reader :configuration, :root
def run_cmd(cmd, args, out)
diff --git a/activerecord/lib/active_record/test_fixtures.rb b/activerecord/lib/active_record/test_fixtures.rb
index 8c60d71669..1d6fef1eb9 100644
--- a/activerecord/lib/active_record/test_fixtures.rb
+++ b/activerecord/lib/active_record/test_fixtures.rb
@@ -179,7 +179,6 @@ module ActiveRecord
end
private
-
# Shares the writing connection pool with connections on
# other handlers.
#
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 04a1c03474..c883d368b5 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -59,19 +59,26 @@ module ActiveRecord
attribute_names.index_with(time || current_time_from_proper_timezone)
end
- private
- def timestamp_attributes_for_create_in_model
- timestamp_attributes_for_create.select { |c| column_names.include?(c) }
- end
+ def timestamp_attributes_for_create_in_model
+ @timestamp_attributes_for_create_in_model ||=
+ (timestamp_attributes_for_create & column_names).freeze
+ end
- def timestamp_attributes_for_update_in_model
- timestamp_attributes_for_update.select { |c| column_names.include?(c) }
- end
+ def timestamp_attributes_for_update_in_model
+ @timestamp_attributes_for_update_in_model ||=
+ (timestamp_attributes_for_update & column_names).freeze
+ end
- def all_timestamp_attributes_in_model
- timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
- end
+ def all_timestamp_attributes_in_model
+ @all_timestamp_attributes_in_model ||=
+ (timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model).freeze
+ end
+
+ def current_time_from_proper_timezone
+ default_timezone == :utc ? Time.now.utc : Time.now
+ end
+ private
def timestamp_attributes_for_create
["created_at", "created_on"]
end
@@ -80,13 +87,15 @@ module ActiveRecord
["updated_at", "updated_on"]
end
- def current_time_from_proper_timezone
- default_timezone == :utc ? Time.now.utc : Time.now
+ def reload_schema_from_cache
+ @timestamp_attributes_for_create_in_model = nil
+ @timestamp_attributes_for_update_in_model = nil
+ @all_timestamp_attributes_in_model = nil
+ super
end
end
private
-
def _create_record
if record_timestamps
current_time = current_time_from_proper_timezone
@@ -124,19 +133,19 @@ module ActiveRecord
end
def timestamp_attributes_for_create_in_model
- self.class.send(:timestamp_attributes_for_create_in_model)
+ self.class.timestamp_attributes_for_create_in_model
end
def timestamp_attributes_for_update_in_model
- self.class.send(:timestamp_attributes_for_update_in_model)
+ self.class.timestamp_attributes_for_update_in_model
end
def all_timestamp_attributes_in_model
- self.class.send(:all_timestamp_attributes_in_model)
+ self.class.all_timestamp_attributes_in_model
end
def current_time_from_proper_timezone
- self.class.send(:current_time_from_proper_timezone)
+ self.class.current_time_from_proper_timezone
end
def max_updated_column_timestamp
diff --git a/activerecord/lib/active_record/touch_later.rb b/activerecord/lib/active_record/touch_later.rb
index 980e42664b..3981bd46ad 100644
--- a/activerecord/lib/active_record/touch_later.rb
+++ b/activerecord/lib/active_record/touch_later.rb
@@ -10,12 +10,7 @@ module ActiveRecord
end
def touch_later(*names) # :nodoc:
- unless persisted?
- raise ActiveRecordError, <<-MSG.squish
- cannot touch on a new or destroyed record object. Consider using
- persisted?, new_record?, or destroyed? before touching
- MSG
- end
+ _raise_record_not_touched_error unless persisted?
@_defer_touch_attrs ||= timestamp_attributes_for_update_in_model
@_defer_touch_attrs |= names
@@ -23,6 +18,7 @@ module ActiveRecord
surreptitiously_touch @_defer_touch_attrs
add_to_transaction
+ @_new_record_before_last_commit ||= false
# touch the parents as we are not calling the after_save callbacks
self.class.reflect_on_all_associations(:belongs_to).each do |r|
@@ -40,7 +36,6 @@ module ActiveRecord
end
private
-
def surreptitiously_touch(attrs)
attrs.each { |attr| write_attribute attr, @_touch_time }
clear_attribute_changes attrs
@@ -48,6 +43,7 @@ module ActiveRecord
def touch_deferred_attributes
if has_defer_touch_attrs? && persisted?
+ @_skip_dirty_tracking = true
touch(*@_defer_touch_attrs, time: @_touch_time)
@_defer_touch_attrs, @_touch_time = nil, nil
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 03a373f0af..5113e08e8e 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -164,12 +164,12 @@ module ActiveRecord
# end
# end
#
- # only "Kotori" is created. This works on MySQL and PostgreSQL. SQLite3 version >= '3.6.8' also supports it.
+ # only "Kotori" is created.
#
# Most databases don't support true nested transactions. At the time of
# writing, the only database that we're aware of that supports true nested
# transactions, is MS-SQL. Because of this, Active Record emulates nested
- # transactions by using savepoints on MySQL and PostgreSQL. See
+ # transactions by using savepoints. See
# https://dev.mysql.com/doc/refman/5.7/en/savepoint.html
# for more information about savepoints.
#
@@ -282,7 +282,6 @@ module ActiveRecord
end
private
-
def set_options_for_callbacks!(args, enforced_options = {})
options = args.extract_options!.merge!(enforced_options)
args << options
@@ -333,7 +332,7 @@ module ActiveRecord
# Ensure that it is not called if the object was never persisted (failed create),
# but call it after the commit of a destroyed object.
def committed!(should_run_callbacks: true) #:nodoc:
- if should_run_callbacks && (destroyed? || persisted?)
+ if should_run_callbacks
@_committed_already_called = true
_run_commit_without_transaction_enrollment_callbacks
_run_commit_callbacks
@@ -364,39 +363,40 @@ module ActiveRecord
def with_transaction_returning_status
status = nil
self.class.transaction do
- unless has_transactional_callbacks?
- sync_with_transaction_state
+ if has_transactional_callbacks?
+ add_to_transaction
+ else
+ sync_with_transaction_state if @transaction_state&.finalized?
@transaction_state = self.class.connection.transaction_state
end
remember_transaction_record_state
status = yield
raise ActiveRecord::Rollback unless status
- ensure
- if has_transactional_callbacks? &&
- (@_new_record_before_last_commit && !new_record? || _trigger_update_callback || _trigger_destroy_callback)
- add_to_transaction
- end
end
status
end
+ def trigger_transactional_callbacks? # :nodoc:
+ (@_new_record_before_last_commit || _trigger_update_callback) && persisted? ||
+ _trigger_destroy_callback && destroyed?
+ end
+
private
attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback
# Save the new record state and id of a record so it can be restored later if a transaction fails.
def remember_transaction_record_state
- @_start_transaction_state.reverse_merge!(
+ @_start_transaction_state ||= {
id: id,
new_record: @new_record,
destroyed: @destroyed,
+ attributes: @attributes,
frozen?: frozen?,
- )
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
- remember_new_record_before_last_commit
- end
+ level: 0
+ }
+ @_start_transaction_state[:level] += 1
- def remember_new_record_before_last_commit
if _committed_already_called
@_new_record_before_last_commit = false
else
@@ -406,28 +406,32 @@ module ActiveRecord
# Clear the new record state and id of a record.
def clear_transaction_record_state
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
+ return unless @_start_transaction_state
+ @_start_transaction_state[:level] -= 1
force_clear_transaction_record_state if @_start_transaction_state[:level] < 1
end
# Force to clear the transaction record state.
def force_clear_transaction_record_state
- @_start_transaction_state.clear
+ @_start_transaction_state = nil
@transaction_state = nil
end
# Restore the new record state and id of a record that was previously saved by a call to save_record_state.
def restore_transaction_record_state(force_restore_state = false)
- unless @_start_transaction_state.empty?
- transaction_level = (@_start_transaction_state[:level] || 0) - 1
- if transaction_level < 1 || force_restore_state
- restore_state = @_start_transaction_state
- thaw
+ if restore_state = @_start_transaction_state
+ if force_restore_state || restore_state[:level] <= 1
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
- pk = self.class.primary_key
- if pk && _read_attribute(pk) != restore_state[:id]
- _write_attribute(pk, restore_state[:id])
+ @attributes = restore_state[:attributes].map do |attr|
+ value = @attributes.fetch_value(attr.name)
+ attr = attr.with_value_from_user(value) if attr.value != value
+ attr
+ end
+ @mutations_from_database = nil
+ @mutations_before_last_save = nil
+ if @attributes.fetch_value(@primary_key) != restore_state[:id]
+ @attributes.write_from_user(@primary_key, restore_state[:id])
end
freeze if restore_state[:frozen?]
end
@@ -472,7 +476,7 @@ module ActiveRecord
# the TransactionState, and rolls back or commits the Active Record object
# as appropriate.
def sync_with_transaction_state
- if (transaction_state = @transaction_state)&.finalized?
+ if transaction_state = @transaction_state
if transaction_state.fully_committed?
force_clear_transaction_record_state
elsif transaction_state.committed?
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index 03d00006b7..4c1ef1a7e4 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -47,7 +47,6 @@ module ActiveRecord
end
private
-
def current_adapter_name
ActiveRecord::Base.connection.adapter_name.downcase.to_sym
end
diff --git a/activerecord/lib/active_record/type/adapter_specific_registry.rb b/activerecord/lib/active_record/type/adapter_specific_registry.rb
index b300fdfa05..c8c16635b1 100644
--- a/activerecord/lib/active_record/type/adapter_specific_registry.rb
+++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb
@@ -11,7 +11,6 @@ module ActiveRecord
end
private
-
def registration_klass
Registration
end
@@ -53,7 +52,6 @@ module ActiveRecord
end
protected
-
attr_reader :name, :block, :adapter, :override
def priority
@@ -72,7 +70,6 @@ module ActiveRecord
end
private
-
def matches_adapter?(adapter: nil, **)
(self.adapter.nil? || adapter == self.adapter)
end
diff --git a/activerecord/lib/active_record/type/hash_lookup_type_map.rb b/activerecord/lib/active_record/type/hash_lookup_type_map.rb
index db9853fbcc..b260464df5 100644
--- a/activerecord/lib/active_record/type/hash_lookup_type_map.rb
+++ b/activerecord/lib/active_record/type/hash_lookup_type_map.rb
@@ -16,7 +16,6 @@ module ActiveRecord
end
private
-
def perform_fetch(type, *args, &block)
@mapping.fetch(type, block).call(type, *args)
end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 0a2f6cb9fb..a34b2fe702 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -56,7 +56,6 @@ module ActiveRecord
end
private
-
def default_value?(value)
value == coder.load(nil)
end
diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb
index fc40b460f0..58f25ba075 100644
--- a/activerecord/lib/active_record/type/type_map.rb
+++ b/activerecord/lib/active_record/type/type_map.rb
@@ -45,7 +45,6 @@ module ActiveRecord
end
private
-
def perform_fetch(lookup_key, *args)
matching_pair = @mapping.reverse_each.detect do |key, _|
key === lookup_key
diff --git a/activerecord/lib/active_record/type/unsigned_integer.rb b/activerecord/lib/active_record/type/unsigned_integer.rb
index 4619528f81..535369e630 100644
--- a/activerecord/lib/active_record/type/unsigned_integer.rb
+++ b/activerecord/lib/active_record/type/unsigned_integer.rb
@@ -4,7 +4,6 @@ module ActiveRecord
module Type
class UnsignedInteger < ActiveModel::Type::Integer # :nodoc:
private
-
def max_value
super * 2
end
diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb
index 7cf8181d8e..f43559f4cb 100644
--- a/activerecord/lib/active_record/type_caster/connection.rb
+++ b/activerecord/lib/active_record/type_caster/connection.rb
@@ -8,21 +8,27 @@ module ActiveRecord
@table_name = table_name
end
- def type_cast_for_database(attribute_name, value)
+ def type_cast_for_database(attr_name, value)
return value if value.is_a?(Arel::Nodes::BindParam)
- column = column_for(attribute_name)
- connection.type_cast_from_column(column, value)
+ type = type_for_attribute(attr_name)
+ type.serialize(value)
end
- private
- attr_reader :table_name
- delegate :connection, to: :@klass
+ def type_for_attribute(attr_name)
+ schema_cache = connection.schema_cache
- def column_for(attribute_name)
- if connection.schema_cache.data_source_exists?(table_name)
- connection.schema_cache.columns_hash(table_name)[attribute_name.to_s]
- end
+ if schema_cache.data_source_exists?(table_name)
+ column = schema_cache.columns_hash(table_name)[attr_name.to_s]
+ type = connection.lookup_cast_type_from_column(column) if column
end
+
+ type || Type.default_value
+ end
+
+ delegate :connection, to: :@klass, private: true
+
+ private
+ attr_reader :table_name
end
end
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index ca27a3f0ab..23e8d53168 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -71,7 +71,6 @@ module ActiveRecord
alias_method :validate, :valid?
private
-
def default_validation_context
new_record? ? :create : :update
end
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index 3538aeec22..dc89df4be7 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -10,7 +10,6 @@ module ActiveRecord
end
private
-
def valid_object?(record)
(record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid?
end
diff --git a/activerecord/lib/arel.rb b/activerecord/lib/arel.rb
index 361cd915cc..0fc07e1ede 100644
--- a/activerecord/lib/arel.rb
+++ b/activerecord/lib/arel.rb
@@ -12,7 +12,7 @@ require "arel/math"
require "arel/alias_predication"
require "arel/order_predications"
require "arel/table"
-require "arel/attributes"
+require "arel/attributes/attribute"
require "arel/visitors"
require "arel/collectors/sql_string"
diff --git a/activerecord/lib/arel/attributes.rb b/activerecord/lib/arel/attributes.rb
deleted file mode 100644
index 35d586c948..0000000000
--- a/activerecord/lib/arel/attributes.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-require "arel/attributes/attribute"
-
-module Arel # :nodoc: all
- module Attributes
- ###
- # Factory method to wrap a raw database +column+ to an Arel Attribute.
- def self.for(column)
- case column.type
- when :string, :text, :binary then String
- when :integer then Integer
- when :float then Float
- when :decimal then Decimal
- when :date, :datetime, :timestamp, :time then Time
- when :boolean then Boolean
- else
- Undefined
- end
- end
- end
-end
diff --git a/activerecord/lib/arel/nodes/node.rb b/activerecord/lib/arel/nodes/node.rb
index 8086102bde..0416ff58de 100644
--- a/activerecord/lib/arel/nodes/node.rb
+++ b/activerecord/lib/arel/nodes/node.rb
@@ -6,7 +6,6 @@ module Arel # :nodoc: all
# Abstract base class for all AST nodes
class Node
include Arel::FactoryMethods
- include Enumerable
###
# Factory method to create a Nodes::Not node that has the recipient of
@@ -38,13 +37,6 @@ module Arel # :nodoc: all
collector = engine.connection.visitor.accept self, collector
collector.value
end
-
- # Iterate through AST, nodes will be yielded depth-first
- def each(&block)
- return enum_for(:each) unless block_given?
-
- ::Arel::Visitors::DepthFirst.new(block).accept self
- end
end
end
end
diff --git a/activerecord/lib/arel/predications.rb b/activerecord/lib/arel/predications.rb
index 7dafde4952..895d394363 100644
--- a/activerecord/lib/arel/predications.rb
+++ b/activerecord/lib/arel/predications.rb
@@ -37,7 +37,7 @@ module Arel # :nodoc: all
def between(other)
if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1
self.in([])
- elsif open_ended?(other.begin)
+ elsif other.begin.nil? || open_ended?(other.begin)
if other.end.nil? || open_ended?(other.end)
not_in([])
elsif other.exclude_end?
@@ -85,7 +85,7 @@ Passing a range to `#in` is deprecated. Call `#between`, instead.
def not_between(other)
if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1
not_in([])
- elsif open_ended?(other.begin)
+ elsif other.begin.nil? || open_ended?(other.begin)
if other.end.nil? || open_ended?(other.end)
self.in([])
elsif other.exclude_end?
@@ -221,7 +221,6 @@ Passing a range to `#not_in` is deprecated. Call `#not_between`, instead.
end
private
-
def grouping_any(method_id, others, *extras)
nodes = others.map { |expr| send(method_id, expr, *extras) }
Nodes::Grouping.new nodes.inject { |memo, node|
diff --git a/activerecord/lib/arel/visitors.rb b/activerecord/lib/arel/visitors.rb
index e350f52e65..a1097f6750 100644
--- a/activerecord/lib/arel/visitors.rb
+++ b/activerecord/lib/arel/visitors.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "arel/visitors/visitor"
-require "arel/visitors/depth_first"
require "arel/visitors/to_sql"
require "arel/visitors/sqlite"
require "arel/visitors/postgresql"
diff --git a/activerecord/lib/arel/visitors/depth_first.rb b/activerecord/lib/arel/visitors/depth_first.rb
deleted file mode 100644
index d696edc507..0000000000
--- a/activerecord/lib/arel/visitors/depth_first.rb
+++ /dev/null
@@ -1,204 +0,0 @@
-# frozen_string_literal: true
-
-module Arel # :nodoc: all
- module Visitors
- class DepthFirst < Arel::Visitors::Visitor
- def initialize(block = nil)
- @block = block || Proc.new
- super()
- end
-
- private
-
- def visit(o)
- super
- @block.call o
- end
-
- def unary(o)
- visit o.expr
- end
- alias :visit_Arel_Nodes_Else :unary
- alias :visit_Arel_Nodes_Group :unary
- alias :visit_Arel_Nodes_Cube :unary
- alias :visit_Arel_Nodes_RollUp :unary
- alias :visit_Arel_Nodes_GroupingSet :unary
- alias :visit_Arel_Nodes_GroupingElement :unary
- alias :visit_Arel_Nodes_Grouping :unary
- alias :visit_Arel_Nodes_Having :unary
- alias :visit_Arel_Nodes_Lateral :unary
- alias :visit_Arel_Nodes_Limit :unary
- alias :visit_Arel_Nodes_Not :unary
- alias :visit_Arel_Nodes_Offset :unary
- alias :visit_Arel_Nodes_On :unary
- alias :visit_Arel_Nodes_Ordering :unary
- alias :visit_Arel_Nodes_Ascending :unary
- alias :visit_Arel_Nodes_Descending :unary
- alias :visit_Arel_Nodes_UnqualifiedColumn :unary
- alias :visit_Arel_Nodes_OptimizerHints :unary
- alias :visit_Arel_Nodes_ValuesList :unary
-
- def function(o)
- visit o.expressions
- visit o.alias
- visit o.distinct
- end
- alias :visit_Arel_Nodes_Avg :function
- alias :visit_Arel_Nodes_Exists :function
- alias :visit_Arel_Nodes_Max :function
- alias :visit_Arel_Nodes_Min :function
- alias :visit_Arel_Nodes_Sum :function
-
- def visit_Arel_Nodes_NamedFunction(o)
- visit o.name
- visit o.expressions
- visit o.distinct
- visit o.alias
- end
-
- def visit_Arel_Nodes_Count(o)
- visit o.expressions
- visit o.alias
- visit o.distinct
- end
-
- def visit_Arel_Nodes_Case(o)
- visit o.case
- visit o.conditions
- visit o.default
- end
-
- def nary(o)
- o.children.each { |child| visit child }
- end
- alias :visit_Arel_Nodes_And :nary
-
- def binary(o)
- visit o.left
- visit o.right
- end
- alias :visit_Arel_Nodes_As :binary
- alias :visit_Arel_Nodes_Assignment :binary
- alias :visit_Arel_Nodes_Between :binary
- alias :visit_Arel_Nodes_Concat :binary
- alias :visit_Arel_Nodes_DeleteStatement :binary
- alias :visit_Arel_Nodes_DoesNotMatch :binary
- alias :visit_Arel_Nodes_Equality :binary
- alias :visit_Arel_Nodes_FullOuterJoin :binary
- alias :visit_Arel_Nodes_GreaterThan :binary
- alias :visit_Arel_Nodes_GreaterThanOrEqual :binary
- alias :visit_Arel_Nodes_In :binary
- alias :visit_Arel_Nodes_InfixOperation :binary
- alias :visit_Arel_Nodes_JoinSource :binary
- alias :visit_Arel_Nodes_InnerJoin :binary
- alias :visit_Arel_Nodes_LessThan :binary
- alias :visit_Arel_Nodes_LessThanOrEqual :binary
- alias :visit_Arel_Nodes_Matches :binary
- alias :visit_Arel_Nodes_NotEqual :binary
- alias :visit_Arel_Nodes_NotIn :binary
- alias :visit_Arel_Nodes_NotRegexp :binary
- alias :visit_Arel_Nodes_IsNotDistinctFrom :binary
- alias :visit_Arel_Nodes_IsDistinctFrom :binary
- alias :visit_Arel_Nodes_Or :binary
- alias :visit_Arel_Nodes_OuterJoin :binary
- alias :visit_Arel_Nodes_Regexp :binary
- alias :visit_Arel_Nodes_RightOuterJoin :binary
- alias :visit_Arel_Nodes_TableAlias :binary
- alias :visit_Arel_Nodes_When :binary
-
- def visit_Arel_Nodes_StringJoin(o)
- visit o.left
- end
-
- def visit_Arel_Attribute(o)
- visit o.relation
- visit o.name
- end
- alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute
- alias :visit_Arel_Attributes_Float :visit_Arel_Attribute
- alias :visit_Arel_Attributes_String :visit_Arel_Attribute
- alias :visit_Arel_Attributes_Time :visit_Arel_Attribute
- alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute
- alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute
- alias :visit_Arel_Attributes_Decimal :visit_Arel_Attribute
-
- def visit_Arel_Table(o)
- visit o.name
- end
-
- def terminal(o)
- end
- alias :visit_ActiveSupport_Multibyte_Chars :terminal
- alias :visit_ActiveSupport_StringInquirer :terminal
- alias :visit_Arel_Nodes_Lock :terminal
- alias :visit_Arel_Nodes_Node :terminal
- alias :visit_Arel_Nodes_SqlLiteral :terminal
- alias :visit_Arel_Nodes_BindParam :terminal
- alias :visit_Arel_Nodes_Window :terminal
- alias :visit_Arel_Nodes_True :terminal
- alias :visit_Arel_Nodes_False :terminal
- alias :visit_BigDecimal :terminal
- alias :visit_Class :terminal
- alias :visit_Date :terminal
- alias :visit_DateTime :terminal
- alias :visit_FalseClass :terminal
- alias :visit_Float :terminal
- alias :visit_Integer :terminal
- alias :visit_NilClass :terminal
- alias :visit_String :terminal
- alias :visit_Symbol :terminal
- alias :visit_Time :terminal
- alias :visit_TrueClass :terminal
-
- def visit_Arel_Nodes_InsertStatement(o)
- visit o.relation
- visit o.columns
- visit o.values
- end
-
- def visit_Arel_Nodes_SelectCore(o)
- visit o.projections
- visit o.source
- visit o.wheres
- visit o.groups
- visit o.windows
- visit o.havings
- end
-
- def visit_Arel_Nodes_SelectStatement(o)
- visit o.cores
- visit o.orders
- visit o.limit
- visit o.lock
- visit o.offset
- end
-
- def visit_Arel_Nodes_UpdateStatement(o)
- visit o.relation
- visit o.values
- visit o.wheres
- visit o.orders
- visit o.limit
- end
-
- def visit_Arel_Nodes_Comment(o)
- visit o.values
- end
-
- def visit_Array(o)
- o.each { |i| visit i }
- end
- alias :visit_Set :visit_Array
-
- def visit_Hash(o)
- o.each { |k, v| visit(k); visit(v) }
- end
-
- DISPATCH = dispatch_cache
-
- def get_dispatch_cache
- DISPATCH
- end
- end
- end
-end
diff --git a/activerecord/lib/arel/visitors/dot.rb b/activerecord/lib/arel/visitors/dot.rb
index ecc386de07..c4ea07bcfe 100644
--- a/activerecord/lib/arel/visitors/dot.rb
+++ b/activerecord/lib/arel/visitors/dot.rb
@@ -31,7 +31,6 @@ module Arel # :nodoc: all
end
private
-
def visit_Arel_Nodes_Ordering(o)
visit_edge o, "expr"
end
diff --git a/activerecord/lib/arel/visitors/mssql.rb b/activerecord/lib/arel/visitors/mssql.rb
index 8475139870..92eb94f802 100644
--- a/activerecord/lib/arel/visitors/mssql.rb
+++ b/activerecord/lib/arel/visitors/mssql.rb
@@ -11,7 +11,6 @@ module Arel # :nodoc: all
end
private
-
def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
right = o.right
diff --git a/activerecord/lib/arel/visitors/oracle.rb b/activerecord/lib/arel/visitors/oracle.rb
index 500974dff5..aab66301ef 100644
--- a/activerecord/lib/arel/visitors/oracle.rb
+++ b/activerecord/lib/arel/visitors/oracle.rb
@@ -4,7 +4,6 @@ module Arel # :nodoc: all
module Visitors
class Oracle < Arel::Visitors::ToSql
private
-
def visit_Arel_Nodes_SelectStatement(o, collector)
o = order_hacks(o)
@@ -87,50 +86,6 @@ module Arel # :nodoc: all
collector << " )"
end
- def visit_Arel_Nodes_In(o, collector)
- if Array === o.right && !o.right.empty?
- o.right.delete_if { |value| unboundable?(value) }
- end
-
- if Array === o.right && o.right.empty?
- collector << "1=0"
- else
- first = true
- o.right.each_slice(in_clause_length) do |sliced_o_right|
- collector << " OR " unless first
- first = false
-
- collector = visit o.left, collector
- collector << " IN ("
- visit(sliced_o_right, collector)
- collector << ")"
- end
- end
- collector
- end
-
- def visit_Arel_Nodes_NotIn(o, collector)
- if Array === o.right && !o.right.empty?
- o.right.delete_if { |value| unboundable?(value) }
- end
-
- if Array === o.right && o.right.empty?
- collector << "1=1"
- else
- first = true
- o.right.each_slice(in_clause_length) do |sliced_o_right|
- collector << " AND " unless first
- first = false
-
- collector = visit o.left, collector
- collector << " NOT IN ("
- visit(sliced_o_right, collector)
- collector << ")"
- end
- end
- collector
- end
-
def visit_Arel_Nodes_UpdateStatement(o, collector)
# Oracle does not allow ORDER BY/LIMIT in UPDATEs.
if o.orders.any? && o.limit.nil?
@@ -198,10 +153,6 @@ module Arel # :nodoc: all
collector = visit [o.left, o.right, 0, 1], collector
collector << ")"
end
-
- def in_clause_length
- 1000
- end
end
end
end
diff --git a/activerecord/lib/arel/visitors/oracle12.rb b/activerecord/lib/arel/visitors/oracle12.rb
index 8e0f07fca9..36783243b5 100644
--- a/activerecord/lib/arel/visitors/oracle12.rb
+++ b/activerecord/lib/arel/visitors/oracle12.rb
@@ -4,15 +4,13 @@ module Arel # :nodoc: all
module Visitors
class Oracle12 < Arel::Visitors::ToSql
private
-
def visit_Arel_Nodes_SelectStatement(o, collector)
# Oracle does not allow LIMIT clause with select for update
if o.limit && o.lock
- raise ArgumentError, <<-MSG
- 'Combination of limit and lock is not supported.
- because generated SQL statements
- `SELECT FOR UPDATE and FETCH FIRST n ROWS` generates ORA-02014.`
- MSG
+ raise ArgumentError, <<~MSG
+ Combination of limit and lock is not supported. Because generated SQL statements
+ `SELECT FOR UPDATE and FETCH FIRST n ROWS` generates ORA-02014.
+ MSG
end
super
end
@@ -41,50 +39,6 @@ module Arel # :nodoc: all
collector << " )"
end
- def visit_Arel_Nodes_In(o, collector)
- if Array === o.right && !o.right.empty?
- o.right.delete_if { |value| unboundable?(value) }
- end
-
- if Array === o.right && o.right.empty?
- collector << "1=0"
- else
- first = true
- o.right.each_slice(in_clause_length) do |sliced_o_right|
- collector << " OR " unless first
- first = false
-
- collector = visit o.left, collector
- collector << " IN ("
- visit(sliced_o_right, collector)
- collector << ")"
- end
- end
- collector
- end
-
- def visit_Arel_Nodes_NotIn(o, collector)
- if Array === o.right && !o.right.empty?
- o.right.delete_if { |value| unboundable?(value) }
- end
-
- if Array === o.right && o.right.empty?
- collector << "1=1"
- else
- first = true
- o.right.each_slice(in_clause_length) do |sliced_o_right|
- collector << " AND " unless first
- first = false
-
- collector = visit o.left, collector
- collector << " NOT IN ("
- visit(sliced_o_right, collector)
- collector << ")"
- end
- end
- collector
- end
-
def visit_Arel_Nodes_UpdateStatement(o, collector)
# Oracle does not allow ORDER BY/LIMIT in UPDATEs.
if o.orders.any? && o.limit.nil?
@@ -106,10 +60,6 @@ module Arel # :nodoc: all
collector = visit [o.left, o.right, 0, 1], collector
collector << ")"
end
-
- def in_clause_length
- 1000
- end
end
end
end
diff --git a/activerecord/lib/arel/visitors/postgresql.rb b/activerecord/lib/arel/visitors/postgresql.rb
index 8296f1cdc1..d4f21ff93e 100644
--- a/activerecord/lib/arel/visitors/postgresql.rb
+++ b/activerecord/lib/arel/visitors/postgresql.rb
@@ -4,7 +4,6 @@ module Arel # :nodoc: all
module Visitors
class PostgreSQL < Arel::Visitors::ToSql
private
-
def visit_Arel_Nodes_Matches(o, collector)
op = o.case_sensitive ? " LIKE " : " ILIKE "
collector = infix_value o, collector, op
diff --git a/activerecord/lib/arel/visitors/sqlite.rb b/activerecord/lib/arel/visitors/sqlite.rb
index af6f7e856a..62ec74ad82 100644
--- a/activerecord/lib/arel/visitors/sqlite.rb
+++ b/activerecord/lib/arel/visitors/sqlite.rb
@@ -4,7 +4,6 @@ module Arel # :nodoc: all
module Visitors
class SQLite < Arel::Visitors::ToSql
private
-
# Locks are not supported in SQLite
def visit_Arel_Nodes_Lock(o, collector)
collector
diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb
index 277d553e6c..eff7a0d036 100644
--- a/activerecord/lib/arel/visitors/to_sql.rb
+++ b/activerecord/lib/arel/visitors/to_sql.rb
@@ -19,7 +19,6 @@ module Arel # :nodoc: all
end
private
-
def visit_Arel_Nodes_DeleteStatement(o, collector)
o = prepare_delete_statement(o)
@@ -52,10 +51,14 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_InsertStatement(o, collector)
collector << "INSERT INTO "
collector = visit o.relation, collector
- if o.columns.any?
- collector << " (#{o.columns.map { |x|
- quote_column_name x.name
- }.join ', '})"
+
+ unless o.columns.empty?
+ collector << " ("
+ o.columns.each_with_index do |x, i|
+ collector << ", " unless i == 0
+ collector << quote_column_name(x.name)
+ end
+ collector << ")"
end
if o.values
@@ -97,22 +100,20 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_ValuesList(o, collector)
collector << "VALUES "
- len = o.rows.length - 1
- o.rows.each_with_index { |row, i|
+ o.rows.each_with_index do |row, i|
+ collector << ", " unless i == 0
collector << "("
- row_len = row.length - 1
row.each_with_index do |value, k|
+ collector << ", " unless k == 0
case value
when Nodes::SqlLiteral, Nodes::BindParam
collector = visit(value, collector)
else
collector << quote(value).to_s
end
- collector << ", " unless k == row_len
end
collector << ")"
- collector << ", " unless i == len
- }
+ end
collector
end
@@ -128,11 +129,10 @@ module Arel # :nodoc: all
unless o.orders.empty?
collector << " ORDER BY "
- len = o.orders.length - 1
- o.orders.each_with_index { |x, i|
+ o.orders.each_with_index do |x, i|
+ collector << ", " unless i == 0
collector = visit(x, collector)
- collector << ", " unless len == i
- }
+ end
end
visit_Arel_Nodes_SelectOptions(o, collector)
@@ -506,41 +506,73 @@ module Arel # :nodoc: all
def visit_Arel_Table(o, collector)
if o.table_alias
- collector << "#{quote_table_name o.name} #{quote_table_name o.table_alias}"
+ collector << quote_table_name(o.name) << " " << quote_table_name(o.table_alias)
else
collector << quote_table_name(o.name)
end
end
def visit_Arel_Nodes_In(o, collector)
- if Array === o.right && !o.right.empty?
+ unless Array === o.right
+ return collect_in_clause(o.left, o.right, collector)
+ end
+
+ unless o.right.empty?
o.right.delete_if { |value| unboundable?(value) }
end
- if Array === o.right && o.right.empty?
- collector << "1=0"
+ return collector << "1=0" if o.right.empty?
+
+ in_clause_length = @connection.in_clause_length
+
+ if !in_clause_length || o.right.length <= in_clause_length
+ collect_in_clause(o.left, o.right, collector)
else
- collector = visit o.left, collector
- collector << " IN ("
- visit(o.right, collector) << ")"
+ collector << "("
+ o.right.each_slice(in_clause_length).each_with_index do |right, i|
+ collector << " OR " unless i == 0
+ collect_in_clause(o.left, right, collector)
+ end
+ collector << ")"
end
end
+ def collect_in_clause(left, right, collector)
+ collector = visit left, collector
+ collector << " IN ("
+ visit(right, collector) << ")"
+ end
+
def visit_Arel_Nodes_NotIn(o, collector)
- if Array === o.right && !o.right.empty?
+ unless Array === o.right
+ return collect_not_in_clause(o.left, o.right, collector)
+ end
+
+ unless o.right.empty?
o.right.delete_if { |value| unboundable?(value) }
end
- if Array === o.right && o.right.empty?
- collector << "1=1"
+ return collector << "1=1" if o.right.empty?
+
+ in_clause_length = @connection.in_clause_length
+
+ if !in_clause_length || o.right.length <= in_clause_length
+ collect_not_in_clause(o.left, o.right, collector)
else
- collector = visit o.left, collector
- collector << " NOT IN ("
- collector = visit o.right, collector
- collector << ")"
+ o.right.each_slice(in_clause_length).each_with_index do |right, i|
+ collector << " AND " unless i == 0
+ collect_not_in_clause(o.left, right, collector)
+ end
+ collector
end
end
+ def collect_not_in_clause(left, right, collector)
+ collector = visit left, collector
+ collector << " NOT IN ("
+ visit(right, collector) << ")"
+ end
+
def visit_Arel_Nodes_And(o, collector)
inject_join o.children, collector, " AND "
end
@@ -650,20 +682,13 @@ module Arel # :nodoc: all
end
def visit_Arel_Nodes_UnqualifiedColumn(o, collector)
- collector << "#{quote_column_name o.name}"
- collector
+ collector << quote_column_name(o.name)
end
def visit_Arel_Attributes_Attribute(o, collector)
join_name = o.relation.table_alias || o.relation.name
- collector << "#{quote_table_name join_name}.#{quote_column_name o.name}"
+ collector << quote_table_name(join_name) << "." << quote_column_name(o.name)
end
- alias :visit_Arel_Attributes_Integer :visit_Arel_Attributes_Attribute
- alias :visit_Arel_Attributes_Float :visit_Arel_Attributes_Attribute
- alias :visit_Arel_Attributes_Decimal :visit_Arel_Attributes_Attribute
- alias :visit_Arel_Attributes_String :visit_Arel_Attributes_Attribute
- alias :visit_Arel_Attributes_Time :visit_Arel_Attributes_Attribute
- alias :visit_Arel_Attributes_Boolean :visit_Arel_Attributes_Attribute
def literal(o, collector); collector << o.to_s; end
@@ -753,14 +778,11 @@ module Arel # :nodoc: all
end
def inject_join(list, collector, join_str)
- len = list.length - 1
- list.each_with_index.inject(collector) { |c, (x, i)|
- if i == len
- visit x, c
- else
- visit(x, c) << join_str
- end
- }
+ list.each_with_index do |x, i|
+ collector << join_str unless i == 0
+ collector = visit(x, collector)
+ end
+ collector
end
def unboundable?(value)
diff --git a/activerecord/lib/arel/visitors/visitor.rb b/activerecord/lib/arel/visitors/visitor.rb
index 1c17184e86..9066307aed 100644
--- a/activerecord/lib/arel/visitors/visitor.rb
+++ b/activerecord/lib/arel/visitors/visitor.rb
@@ -7,16 +7,15 @@ module Arel # :nodoc: all
@dispatch = get_dispatch_cache
end
- def accept(object, *args)
- visit object, *args
+ def accept(object, collector = nil)
+ visit object, collector
end
private
-
attr_reader :dispatch
def self.dispatch_cache
- Hash.new do |hash, klass|
+ @dispatch_cache ||= Hash.new do |hash, klass|
hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
end
end
@@ -25,9 +24,13 @@ module Arel # :nodoc: all
self.class.dispatch_cache
end
- def visit(object, *args)
+ def visit(object, collector = nil)
dispatch_method = dispatch[object.class]
- send dispatch_method, object, *args
+ if collector
+ send dispatch_method, object, collector
+ else
+ send dispatch_method, object
+ end
rescue NoMethodError => e
raise e if respond_to?(dispatch_method, true)
superklass = object.class.ancestors.find { |klass|
diff --git a/activerecord/lib/arel/visitors/where_sql.rb b/activerecord/lib/arel/visitors/where_sql.rb
index c6caf5e7c9..8fb299d1c8 100644
--- a/activerecord/lib/arel/visitors/where_sql.rb
+++ b/activerecord/lib/arel/visitors/where_sql.rb
@@ -9,7 +9,6 @@ module Arel # :nodoc: all
end
private
-
def visit_Arel_Nodes_SelectCore(o, collector)
collector << "WHERE "
wheres = o.wheres.map do |where|
diff --git a/activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb b/activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb
index 35d5664400..56b9628a92 100644
--- a/activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb
@@ -13,7 +13,6 @@ module ActiveRecord
end
private
-
def application_record_file_name
@application_record_file_name ||=
if namespaced?
diff --git a/activerecord/lib/rails/generators/active_record/migration.rb b/activerecord/lib/rails/generators/active_record/migration.rb
index cbb88d571d..af753071a9 100644
--- a/activerecord/lib/rails/generators/active_record/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration.rb
@@ -17,7 +17,6 @@ module ActiveRecord
end
private
-
def primary_key_type
key_type = options[:primary_key_type]
", id: :#{key_type}" if key_type
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index cb2c74f1ca..0620a515bd 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -7,6 +7,7 @@ module ActiveRecord
class MigrationGenerator < Base # :nodoc:
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
+ class_option :timestamps, type: :boolean
class_option :primary_key_type, type: :string, desc: "The type for primary key"
class_option :database, type: :string, aliases: %i(--db), desc: "The database for your migration. By default, the current environment's primary database is used."
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index c71bbdcab8..d4733f948f 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -35,7 +35,6 @@ module ActiveRecord
hook_for :test_framework
private
-
def attributes_with_index
attributes.select { |a| !a.reference? && a.has_index? }
end
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
index f977b2997b..f1f457aedd 100644
--- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb
+++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
@@ -32,7 +32,8 @@ module ActiveRecord
name.to_s,
options[:default],
fetch_type_metadata(sql_type),
- options[:null])
+ options[:null],
+ )
end
def columns(table_name)
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index ce2ed06c1d..0bc617edbe 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -12,6 +12,7 @@ module ActiveRecord
def setup
@connection = ActiveRecord::Base.connection
@connection.materialize_transactions
+ @connection_handler = ActiveRecord::Base.connection_handler
end
##
@@ -166,7 +167,7 @@ module ActiveRecord
def test_preventing_writes_predicate
assert_not_predicate @connection, :preventing_writes?
- @connection.while_preventing_writes do
+ @connection_handler.while_preventing_writes do
assert_predicate @connection, :preventing_writes?
end
@@ -176,7 +177,7 @@ module ActiveRecord
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_handler.while_preventing_writes do
@connection.transaction do
@connection.insert("INSERT INTO subscribers(nick) VALUES ('138853948594')", nil, false)
end
@@ -190,7 +191,7 @@ module ActiveRecord
assert_no_queries do
assert_raises(ActiveRecord::ReadOnlyError) do
- @connection.while_preventing_writes do
+ @connection_handler.while_preventing_writes do
@connection.transaction do
@connection.update("UPDATE subscribers SET nick = '9989' WHERE nick = '138853948594'")
end
@@ -204,7 +205,7 @@ module ActiveRecord
assert_no_queries do
assert_raises(ActiveRecord::ReadOnlyError) do
- @connection.while_preventing_writes do
+ @connection_handler.while_preventing_writes do
@connection.transaction do
@connection.delete("DELETE FROM subscribers WHERE nick = '138853948594'")
end
@@ -216,7 +217,7 @@ module ActiveRecord
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
+ @connection_handler.while_preventing_writes do
result = @connection.select_all("SELECT subscribers.* FROM subscribers WHERE nick = '138853948594'")
assert_equal 1, result.length
end
@@ -559,7 +560,6 @@ module ActiveRecord
end
private
-
def reset_fixtures(*fixture_names)
ActiveRecord::FixtureSet.reset_cache
diff --git a/activerecord/test/cases/adapters/mysql2/annotate_test.rb b/activerecord/test/cases/adapters/mysql2/annotate_test.rb
deleted file mode 100644
index b512540073..0000000000
--- a/activerecord/test/cases/adapters/mysql2/annotate_test.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# 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 c32475c683..3756f74c95 100644
--- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -22,7 +22,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
CollationTest.validates_uniqueness_of(:string_ci_column, case_sensitive: false)
CollationTest.create!(string_ci_column: "A")
invalid = CollationTest.new(string_ci_column: "a")
- queries = assert_sql { invalid.save }
+ queries = capture_sql { invalid.save }
ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) }
assert_no_match(/lower/i, ci_uniqueness_query)
end
@@ -31,7 +31,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
CollationTest.validates_uniqueness_of(:string_cs_column, case_sensitive: false)
CollationTest.create!(string_cs_column: "A")
invalid = CollationTest.new(string_cs_column: "a")
- queries = assert_sql { invalid.save }
+ queries = capture_sql { invalid.save }
cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
assert_match(/lower/i, cs_uniqueness_query)
end
@@ -40,7 +40,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
CollationTest.validates_uniqueness_of(:string_ci_column, case_sensitive: true)
CollationTest.create!(string_ci_column: "A")
invalid = CollationTest.new(string_ci_column: "A")
- queries = assert_sql { invalid.save }
+ queries = capture_sql { invalid.save }
ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) }
assert_match(/binary/i, ci_uniqueness_query)
end
@@ -49,7 +49,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
CollationTest.validates_uniqueness_of(:string_cs_column, case_sensitive: true)
CollationTest.create!(string_cs_column: "A")
invalid = CollationTest.new(string_cs_column: "A")
- queries = assert_sql { invalid.save }
+ queries = capture_sql { invalid.save }
cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
assert_no_match(/binary/i, cs_uniqueness_query)
end
@@ -58,7 +58,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
CollationTest.validates_uniqueness_of(:binary_column, case_sensitive: true)
CollationTest.create!(binary_column: "A")
invalid = CollationTest.new(binary_column: "A")
- queries = assert_sql { invalid.save }
+ queries = capture_sql { invalid.save }
bin_uniqueness_query = queries.detect { |q| q.match(/binary_column/) }
assert_no_match(/\bBINARY\b/, bin_uniqueness_query)
end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 9c6566106a..cb7461a8d5 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -197,7 +197,6 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
private
-
def test_lock_free(lock_name)
@connection.select_value("SELECT IS_FREE_LOCK(#{@connection.quote(lock_name)})") == 1
end
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index 832f5d61d1..1168b3677e 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -1,11 +1,20 @@
# frozen_string_literal: true
require "cases/helper"
+require "support/schema_dumping_helper"
class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+
class EnumTest < ActiveRecord::Base
end
+ def setup
+ EnumTest.connection.create_table :enum_tests, id: false, force: true do |t|
+ t.column :enum_column, "enum('text','blob','tiny','medium','long','unsigned','bigint')"
+ end
+ end
+
def test_enum_limit
column = EnumTest.columns_hash["enum_column"]
assert_equal 8, column.limit
@@ -20,4 +29,9 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
column = EnumTest.columns_hash["enum_column"]
assert_not_predicate column, :bigint?
end
+
+ def test_schema_dumping
+ schema = dump_table_schema "enum_tests"
+ assert_match %r{t\.column "enum_column", "enum\('text','blob','tiny','medium','long','unsigned','bigint'\)"$}, schema
+ 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 6ade2eec24..cfc1823773 100644
--- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
@@ -8,6 +8,7 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
def setup
@conn = ActiveRecord::Base.connection
+ @connection_handler = ActiveRecord::Base.connection_handler
end
def test_exec_query_nothing_raises_with_no_result_queries
@@ -19,6 +20,18 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
end
end
+ def test_database_exists_returns_false_if_database_does_not_exist
+ config = ActiveRecord::Base.configurations["arunit"].merge(database: "inexistent_activerecord_unittest")
+ assert_not ActiveRecord::ConnectionAdapters::Mysql2Adapter.database_exists?(config),
+ "expected database to not exist"
+ end
+
+ def test_database_exists_returns_true_when_the_database_exists
+ config = ActiveRecord::Base.configurations["arunit"]
+ assert ActiveRecord::ConnectionAdapters::Mysql2Adapter.database_exists?(config),
+ "expected database #{config[:database]} to exist"
+ end
+
def test_columns_for_distinct_zero_orders
assert_equal "posts.id",
@conn.columns_for_distinct("posts.id", [])
@@ -148,7 +161,7 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
def test_errors_when_an_insert_query_is_called_while_preventing_writes
assert_raises(ActiveRecord::ReadOnlyError) do
- @conn.while_preventing_writes do
+ @connection_handler.while_preventing_writes do
@conn.insert("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
end
end
@@ -158,7 +171,7 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
@conn.insert("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
assert_raises(ActiveRecord::ReadOnlyError) do
- @conn.while_preventing_writes do
+ @connection_handler.while_preventing_writes do
@conn.update("UPDATE `engines` SET `engines`.`car_id` = '9989' WHERE `engines`.`car_id` = '138853948594'")
end
end
@@ -168,7 +181,7 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
@conn.execute("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
assert_raises(ActiveRecord::ReadOnlyError) do
- @conn.while_preventing_writes do
+ @connection_handler.while_preventing_writes do
@conn.execute("DELETE FROM `engines` where `engines`.`car_id` = '138853948594'")
end
end
@@ -178,7 +191,7 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
@conn.execute("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
assert_raises(ActiveRecord::ReadOnlyError) do
- @conn.while_preventing_writes do
+ @connection_handler.while_preventing_writes do
@conn.execute("REPLACE INTO `engines` SET `engines`.`car_id` = '249823948'")
end
end
@@ -187,19 +200,19 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
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
+ @connection_handler.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
+ @connection_handler.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
+ @connection_handler.while_preventing_writes do
assert_nil @conn.execute("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci")
end
end
@@ -207,13 +220,27 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
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
+ @connection_handler.while_preventing_writes do
assert_equal 1, @conn.execute("(\n( SELECT `engines`.* FROM `engines` WHERE `engines`.`car_id` = '138853948594' ) )").entries.count
end
end
- private
+ def test_read_timeout_exception
+ ActiveRecord::Base.establish_connection(
+ ActiveRecord::Base.configurations[:arunit].merge("read_timeout" => 1)
+ )
+ error = assert_raises(ActiveRecord::AdapterTimeout) do
+ ActiveRecord::Base.connection.execute("SELECT SLEEP(2)")
+ end
+ assert_kind_of ActiveRecord::QueryAborted, error
+
+ assert_equal Mysql2::Error::TimeoutError, error.cause.class
+ ensure
+ ActiveRecord::Base.establish_connection :arunit
+ end
+
+ private
def with_example_table(definition = "id int auto_increment primary key, number int, data varchar(255)", &block)
super(@conn, "ex", definition, &block)
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index d7d9a2d732..182d5a3e58 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -40,7 +40,6 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
end
private
-
def with_encoding_utf8mb4
database_name = connection.current_database
database_info = connection.select_one("SELECT * FROM information_schema.schemata WHERE schema_name = '#{database_name}'")
diff --git a/activerecord/test/cases/adapters/mysql2/set_test.rb b/activerecord/test/cases/adapters/mysql2/set_test.rb
new file mode 100644
index 0000000000..89107e142f
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/set_test.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "support/schema_dumping_helper"
+
+class Mysql2SetTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+
+ class SetTest < ActiveRecord::Base
+ end
+
+ def setup
+ SetTest.connection.create_table :set_tests, id: false, force: true do |t|
+ t.column :set_column, "set('text','blob','tiny','medium','long','unsigned','bigint')"
+ end
+ end
+
+ def test_should_not_be_unsigned
+ column = SetTest.columns_hash["set_column"]
+ assert_not_predicate column, :unsigned?
+ end
+
+ def test_should_not_be_bigint
+ column = SetTest.columns_hash["set_column"]
+ assert_not_predicate column, :bigint?
+ end
+
+ def test_schema_dumping
+ schema = dump_table_schema "set_tests"
+ assert_match %r{t\.column "set_column", "set\('text','blob','tiny','medium','long','unsigned','bigint'\)"$}, schema
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/table_options_test.rb b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
index 1c92df940f..13cf1daa08 100644
--- a/activerecord/test/cases/adapters/mysql2/table_options_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
@@ -73,7 +73,7 @@ class Mysql2DefaultEngineOptionSchemaDumpTest < ActiveRecord::Mysql2TestCase
end
end.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], ActiveRecord::Base.connection.schema_migration).migrate
output = dump_table_schema("mysql_table_options")
options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
@@ -112,7 +112,7 @@ class Mysql2DefaultEngineOptionSqlOutputTest < ActiveRecord::Mysql2TestCase
end
end.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], ActiveRecord::Base.connection.schema_migration).migrate
assert_match %r{ENGINE=InnoDB}, @log.string
end
diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
index 52e283f247..2041cc308f 100644
--- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
@@ -92,7 +92,7 @@ module ActiveRecord
test "raises StatementTimeout when statement timeout exceeded" do
skip unless ActiveRecord::Base.connection.show_variable("max_execution_time")
- assert_raises(ActiveRecord::StatementTimeout) do
+ error = assert_raises(ActiveRecord::StatementTimeout) do
s = Sample.create!(value: 1)
latch1 = Concurrent::CountDownLatch.new
latch2 = Concurrent::CountDownLatch.new
@@ -117,10 +117,11 @@ module ActiveRecord
thread.join
end
end
+ assert_kind_of ActiveRecord::QueryAborted, error
end
test "raises QueryCanceled when canceling statement due to user request" do
- assert_raises(ActiveRecord::QueryCanceled) do
+ error = assert_raises(ActiveRecord::QueryCanceled) do
s = Sample.create!(value: 1)
latch = Concurrent::CountDownLatch.new
@@ -144,6 +145,7 @@ module ActiveRecord
thread.join
end
end
+ assert_kind_of ActiveRecord::QueryAborted, error
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/annotate_test.rb b/activerecord/test/cases/adapters/postgresql/annotate_test.rb
deleted file mode 100644
index 42a2861511..0000000000
--- a/activerecord/test/cases/adapters/postgresql/annotate_test.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# 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/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 210758f462..dcee4fd22d 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -26,19 +26,19 @@ module ActiveRecord
end
def test_encoding
- assert_queries(1) do
+ assert_queries(1, ignore_none: true) do
assert_not_nil @connection.encoding
end
end
def test_collation
- assert_queries(1) do
+ assert_queries(1, ignore_none: true) do
assert_not_nil @connection.collation
end
end
def test_ctype
- assert_queries(1) do
+ assert_queries(1, ignore_none: true) do
assert_not_nil @connection.ctype
end
end
@@ -239,7 +239,6 @@ module ActiveRecord
end
private
-
def with_warning_suppression
log_level = @connection.client_min_messages
@connection.client_min_messages = "error"
diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
index 0fd7b2c6ed..16baa8933d 100644
--- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
@@ -27,20 +27,20 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase
ActiveRecord::Base.table_name_prefix = "p_"
ActiveRecord::Base.table_name_suffix = "_s"
- ActiveRecord::SchemaMigration.reset_table_name
+ @connection.schema_migration.reset_table_name
ActiveRecord::InternalMetadata.reset_table_name
- ActiveRecord::SchemaMigration.delete_all rescue nil
+ @connection.schema_migration.delete_all rescue nil
ActiveRecord::Migration.verbose = false
end
def teardown
- ActiveRecord::SchemaMigration.delete_all rescue nil
+ @connection.schema_migration.delete_all rescue nil
ActiveRecord::Migration.verbose = true
ActiveRecord::Base.table_name_prefix = @old_table_name_prefix
ActiveRecord::Base.table_name_suffix = @old_table_name_suffix
- ActiveRecord::SchemaMigration.reset_table_name
+ @connection.schema_migration.reset_table_name
ActiveRecord::InternalMetadata.reset_table_name
super
@@ -50,7 +50,7 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase
@connection.disable_extension("hstore")
migrations = [EnableHstore.new(nil, 1)]
- ActiveRecord::Migrator.new(:up, migrations).migrate
+ ActiveRecord::Migrator.new(:up, migrations, ActiveRecord::Base.connection.schema_migration).migrate
assert @connection.extension_enabled?("hstore"), "extension hstore should be enabled"
end
@@ -58,7 +58,7 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase
@connection.enable_extension("hstore")
migrations = [DisableHstore.new(nil, 1)]
- ActiveRecord::Migrator.new(:up, migrations).migrate
+ ActiveRecord::Migrator.new(:up, migrations, ActiveRecord::Base.connection.schema_migration).migrate
assert_not @connection.extension_enabled?("hstore"), "extension hstore should not be enabled"
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index 14c262f4ce..f312b6e23d 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -361,7 +361,6 @@ class PostgreSQLGeometricTypesTest < ActiveRecord::PostgreSQLTestCase
end
private
-
def assert_column_exists(column_name)
assert connection.column_exists?(table_name, column_name)
end
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index 1aa0348879..ff2ab22a80 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -54,8 +54,12 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
type = PostgresqlMoney.type_for_attribute("wealth")
assert_equal(12345678.12, type.cast(+"$12,345,678.12"))
assert_equal(12345678.12, type.cast(+"$12.345.678,12"))
+ 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)"))
+ assert_equal(-1.15, type.cast(+"-1.15"))
+ assert_equal(-2.25, type.cast(+"(2.25)"))
end
def test_schema_dumping
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index fbd3cbf90f..830c0892d3 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -13,6 +13,7 @@ module ActiveRecord
def setup
@connection = ActiveRecord::Base.connection
+ @connection_handler = ActiveRecord::Base.connection_handler
end
def test_bad_connection
@@ -23,6 +24,18 @@ module ActiveRecord
end
end
+ def test_database_exists_returns_false_when_the_database_does_not_exist
+ config = { database: "non_extant_database", adapter: "postgresql" }
+ assert_not ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.database_exists?(config),
+ "expected database #{config[:database]} to not exist"
+ end
+
+ def test_database_exists_returns_true_when_the_database_exists
+ config = ActiveRecord::Base.configurations["arunit"]
+ assert ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.database_exists?(config),
+ "expected database #{config[:database]} to exist"
+ end
+
def test_primary_key
with_example_table do
assert_equal "id", @connection.primary_key("ex")
@@ -240,9 +253,11 @@ module ActiveRecord
def test_expression_index
with_example_table do
- @connection.add_index "ex", "mod(id, 10), abs(number)", name: "expression"
+ expr = "mod(id, 10), abs(number)"
+ @connection.add_index "ex", expr, name: "expression"
index = @connection.indexes("ex").find { |idx| idx.name == "expression" }
- assert_equal "mod(id, 10), abs(number)", index.columns
+ assert_equal expr, index.columns
+ assert_equal true, @connection.index_exists?("ex", expr, name: "expression")
end
end
@@ -379,7 +394,7 @@ module ActiveRecord
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_handler.while_preventing_writes do
@connection.execute("INSERT INTO ex (data) VALUES ('138853948594')")
end
end
@@ -391,7 +406,7 @@ module ActiveRecord
@connection.execute("INSERT INTO ex (data) VALUES ('138853948594')")
assert_raises(ActiveRecord::ReadOnlyError) do
- @connection.while_preventing_writes do
+ @connection_handler.while_preventing_writes do
@connection.execute("UPDATE ex SET data = '9989' WHERE data = '138853948594'")
end
end
@@ -403,7 +418,7 @@ module ActiveRecord
@connection.execute("INSERT INTO ex (data) VALUES ('138853948594')")
assert_raises(ActiveRecord::ReadOnlyError) do
- @connection.while_preventing_writes do
+ @connection_handler.while_preventing_writes do
@connection.execute("DELETE FROM ex where data = '138853948594'")
end
end
@@ -414,20 +429,20 @@ module ActiveRecord
with_example_table do
@connection.execute("INSERT INTO ex (data) VALUES ('138853948594')")
- @connection.while_preventing_writes do
+ @connection_handler.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
+ @connection_handler.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
+ @connection_handler.while_preventing_writes do
assert_equal [], @connection.execute("SET standard_conforming_strings = on").entries
end
end
@@ -436,14 +451,13 @@ module ActiveRecord
with_example_table do
@connection.execute("INSERT INTO ex (data) VALUES ('138853948594')")
- @connection.while_preventing_writes do
+ @connection_handler.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)
super(@connection, "ex", definition, &block)
end
diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
index ba477c63f4..a4f722c063 100644
--- a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
@@ -13,7 +13,7 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase
end
module MissingSuperuserPrivileges
- def execute(sql)
+ def execute(sql, name = nil)
if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
super "BROKEN;" rescue nil # put transaction in broken state
raise ActiveRecord::StatementInvalid, "PG::InsufficientPrivilege"
@@ -24,7 +24,7 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase
end
module ProgrammerMistake
- def execute(sql)
+ def execute(sql, name = nil)
if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
raise ArgumentError, "something is not right."
else
@@ -106,7 +106,6 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase
end
private
-
def assert_transaction_is_not_broken
assert_equal 1, @connection.select_value("SELECT 1")
end
diff --git a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
index 7eccaf4aa2..fae20de086 100644
--- a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
@@ -25,7 +25,6 @@ class PostgresqlRenameTableTest < ActiveRecord::PostgreSQLTestCase
end
private
-
def num_indices_named(name)
@connection.execute(<<~SQL).values.length
SELECT 1 FROM "pg_index"
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 336cec30ca..fe6a3deff4 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -104,7 +104,11 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
end
def test_schema_names
- assert_equal ["public", "test_schema", "test_schema2"], @connection.schema_names
+ schema_names = @connection.schema_names
+ assert_includes schema_names, "public"
+ assert_includes schema_names, "test_schema"
+ assert_includes schema_names, "test_schema2"
+ assert_includes schema_names, "hint_plan" if @connection.supports_optimizer_hints?
end
def test_create_schema
diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
index 919ff3d158..311863a418 100644
--- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
@@ -177,7 +177,6 @@ module ActiveRecord
end
private
-
def with_warning_suppression
log_level = ActiveRecord::Base.connection.client_min_messages
ActiveRecord::Base.connection.client_min_messages = "error"
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index d2d8ea8042..a1c985fc71 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -293,14 +293,16 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
create_table("pg_uuids_4", id: :uuid)
end
end.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], ActiveRecord::Base.connection.schema_migration).migrate
schema = dump_table_schema "pg_uuids_4"
assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
ensure
drop_table "pg_uuids_4"
ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::Base.connection.schema_migration.delete_all
end
+ uses_transaction :test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration
end
class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase
@@ -341,14 +343,16 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase
create_table("pg_uuids_4", id: :uuid, default: nil)
end
end.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], ActiveRecord::Base.connection.schema_migration).migrate
schema = dump_table_schema "pg_uuids_4"
assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: nil/, schema)
ensure
drop_table "pg_uuids_4"
ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::Base.connection.schema_migration.delete_all
end
+ uses_transaction :test_schema_dumper_for_uuid_primary_key_with_default_nil_in_legacy_migration
end
class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
diff --git a/activerecord/test/cases/adapters/sqlite3/annotate_test.rb b/activerecord/test/cases/adapters/sqlite3/annotate_test.rb
deleted file mode 100644
index 6567a5eca3..0000000000
--- a/activerecord/test/cases/adapters/sqlite3/annotate_test.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-require "cases/helper"
-require "models/post"
-
-class SQLite3AnnotateTest < ActiveRecord::SQLite3TestCase
- fixtures :posts
-
- def test_annotate_wraps_content_in_an_inline_comment
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
- posts = Post.select(:id).annotate("foo")
- assert posts.first
- end
- end
-
- def test_annotate_is_sanitized
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
- posts = Post.select(:id).annotate("*/foo/*")
- assert posts.first
- end
-
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do
- posts = Post.select(:id).annotate("**//foo//**")
- assert posts.first
- end
-
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/ /\* bar \*/}) do
- posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
- assert posts.first
- end
-
- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* \+ MAX_EXECUTION_TIME\(1\) \*/}) do
- posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
- assert posts.first
- end
- end
-end
diff --git a/activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb b/activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb
deleted file mode 100644
index 93a7dafebd..0000000000
--- a/activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-require "cases/helper"
-require "models/topic"
-
-module ActiveRecord
- module ConnectionAdapters
- class SQLite3Adapter
- class BindParameterTest < ActiveRecord::SQLite3TestCase
- def test_too_many_binds
- topics = Topic.where(id: (1..999).to_a << 2**63)
- assert_equal Topic.count, topics.count
-
- topics = Topic.where.not(id: (1..999).to_a << 2**63)
- assert_equal 0, topics.count
- end
- end
- end
- end
-end
diff --git a/activerecord/test/cases/adapters/sqlite3/collation_test.rb b/activerecord/test/cases/adapters/sqlite3/collation_test.rb
index 76c8f7d8dd..d938b5ff2f 100644
--- a/activerecord/test/cases/adapters/sqlite3/collation_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/collation_test.rb
@@ -11,6 +11,10 @@ class SQLite3CollationTest < ActiveRecord::SQLite3TestCase
@connection.create_table :collation_table_sqlite3, force: true do |t|
t.string :string_nocase, collation: "NOCASE"
t.text :text_rtrim, collation: "RTRIM"
+ # The decimal column might interfere with collation parsing.
+ # Thus, add this column type and some other string column afterwards.
+ t.decimal :decimal_col, precision: 6, scale: 2
+ t.string :string_after_decimal_nocase, collation: "NOCASE"
end
end
@@ -22,6 +26,11 @@ class SQLite3CollationTest < ActiveRecord::SQLite3TestCase
column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == "string_nocase" }
assert_equal :string, column.type
assert_equal "NOCASE", column.collation
+
+ # Verify collation of a column behind the decimal column as well.
+ column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == "string_after_decimal_nocase" }
+ assert_equal :string, column.type
+ assert_equal "NOCASE", column.collation
end
test "text column with collation" do
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 806cfbfc00..b6d72c7bcd 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -19,6 +19,8 @@ module ActiveRecord
@conn = Base.sqlite3_connection database: ":memory:",
adapter: "sqlite3",
timeout: 100
+
+ @connection_handler = ActiveRecord::Base.connection_handler
end
def test_bad_connection
@@ -28,6 +30,17 @@ module ActiveRecord
end
end
+ def test_database_exists_returns_false_when_the_database_does_not_exist
+ assert_not SQLite3Adapter.database_exists?(adapter: "sqlite3", database: "non_extant_db"),
+ "expected non_extant_db to not exist"
+ end
+
+ def test_database_exists_returns_true_when_databae_exists
+ config = ActiveRecord::Base.configurations["arunit"]
+ assert SQLite3Adapter.database_exists?(config),
+ "expected #{config[:database]} to exist"
+ end
+
unless in_memory_db?
def test_connect_with_url
original_connection = ActiveRecord::Base.remove_connection
@@ -51,6 +64,11 @@ module ActiveRecord
end
end
+ def test_database_exists_returns_true_for_an_in_memory_db
+ assert SQLite3Adapter.database_exists?(database: ":memory:"),
+ "Expected in memory database to exist"
+ end
+
def test_column_types
owner = Owner.create!(name: "hello".encode("ascii-8bit"))
owner.reload
@@ -572,7 +590,7 @@ module ActiveRecord
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
+ @connection_handler.while_preventing_writes do
@conn.execute("INSERT INTO ex (data) VALUES ('138853948594')")
end
end
@@ -584,7 +602,7 @@ module ActiveRecord
@conn.execute("INSERT INTO ex (data) VALUES ('138853948594')")
assert_raises(ActiveRecord::ReadOnlyError) do
- @conn.while_preventing_writes do
+ @connection_handler.while_preventing_writes do
@conn.execute("UPDATE ex SET data = '9989' WHERE data = '138853948594'")
end
end
@@ -596,7 +614,7 @@ module ActiveRecord
@conn.execute("INSERT INTO ex (data) VALUES ('138853948594')")
assert_raises(ActiveRecord::ReadOnlyError) do
- @conn.while_preventing_writes do
+ @connection_handler.while_preventing_writes do
@conn.execute("DELETE FROM ex where data = '138853948594'")
end
end
@@ -608,7 +626,7 @@ module ActiveRecord
@conn.execute("INSERT INTO ex (data) VALUES ('138853948594')")
assert_raises(ActiveRecord::ReadOnlyError) do
- @conn.while_preventing_writes do
+ @connection_handler.while_preventing_writes do
@conn.execute("REPLACE INTO ex (data) VALUES ('249823948')")
end
end
@@ -619,7 +637,7 @@ module ActiveRecord
with_example_table "id int, data string" do
@conn.execute("INSERT INTO ex (data) VALUES ('138853948594')")
- @conn.while_preventing_writes do
+ @connection_handler.while_preventing_writes do
assert_equal 1, @conn.execute("SELECT data from ex WHERE data = '138853948594'").count
end
end
@@ -629,14 +647,13 @@ module ActiveRecord
with_example_table "id int, data string" do
@conn.execute("INSERT INTO ex (data) VALUES ('138853948594')")
- @conn.while_preventing_writes do
+ @connection_handler.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)
subscriber = SQLSubscriber.new
subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber)
diff --git a/activerecord/test/cases/annotate_test.rb b/activerecord/test/cases/annotate_test.rb
new file mode 100644
index 0000000000..4d71d28f83
--- /dev/null
+++ b/activerecord/test/cases/annotate_test.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/post"
+
+class AnnotateTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def test_annotate_wraps_content_in_an_inline_comment
+ quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
+
+ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
+ posts = Post.select(:id).annotate("foo")
+ assert posts.first
+ end
+ end
+
+ def test_annotate_is_sanitized
+ quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts")
+
+ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
+ posts = Post.select(:id).annotate("*/foo/*")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do
+ posts = Post.select(:id).annotate("**//foo//**")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/ /\* bar \*/}i) do
+ posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \+ MAX_EXECUTION_TIME\(1\) \*/}i) do
+ posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
+ assert posts.first
+ end
+ end
+
+ private
+ def regexp_escape_table_name(name)
+ Regexp.escape(Post.connection.quote_table_name(name))
+ end
+end
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 7aa6d089c5..2d5a06a4ac 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -9,7 +9,8 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
@original_verbose = ActiveRecord::Migration.verbose
ActiveRecord::Migration.verbose = false
@connection = ActiveRecord::Base.connection
- ActiveRecord::SchemaMigration.drop_table
+ @schema_migration = @connection.schema_migration
+ @schema_migration.drop_table
end
teardown do
@@ -18,21 +19,21 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
@connection.drop_table :nep_schema_migrations rescue nil
@connection.drop_table :has_timestamps rescue nil
@connection.drop_table :multiple_indexes rescue nil
- ActiveRecord::SchemaMigration.delete_all rescue nil
+ @schema_migration.delete_all rescue nil
ActiveRecord::Migration.verbose = @original_verbose
end
def test_has_primary_key
old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type
ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
- assert_equal "version", ActiveRecord::SchemaMigration.primary_key
+ assert_equal "version", @schema_migration.primary_key
- ActiveRecord::SchemaMigration.create_table
- assert_difference "ActiveRecord::SchemaMigration.count", 1 do
- ActiveRecord::SchemaMigration.create version: 12
+ @schema_migration.create_table
+ assert_difference "@schema_migration.count", 1 do
+ @schema_migration.create version: 12
end
ensure
- ActiveRecord::SchemaMigration.drop_table
+ @schema_migration.drop_table
ActiveRecord::Base.primary_key_prefix_type = old_primary_key_prefix_type
end
@@ -54,7 +55,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
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.reset_table_name
+ @schema_migration.reset_table_name
ActiveRecord::InternalMetadata.reset_table_name
ActiveRecord::Schema.define(version: 7) do
create_table :fruits do |t|
@@ -67,7 +68,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
assert_equal 7, @connection.migration_context.current_version
ensure
ActiveRecord::Base.table_name_prefix = old_table_name_prefix
- ActiveRecord::SchemaMigration.reset_table_name
+ @schema_migration.reset_table_name
ActiveRecord::InternalMetadata.reset_table_name
end
@@ -89,10 +90,10 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
end
def test_normalize_version
- assert_equal "118", ActiveRecord::SchemaMigration.normalize_migration_number("0000118")
- assert_equal "002", ActiveRecord::SchemaMigration.normalize_migration_number("2")
- assert_equal "017", ActiveRecord::SchemaMigration.normalize_migration_number("0017")
- assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947")
+ assert_equal "118", @schema_migration.normalize_migration_number("0000118")
+ assert_equal "002", @schema_migration.normalize_migration_number("2")
+ assert_equal "017", @schema_migration.normalize_migration_number("0017")
+ assert_equal "20131219224947", @schema_migration.normalize_migration_number("20131219224947")
end
def test_schema_load_with_multiple_indexes_for_column_of_different_names
diff --git a/activerecord/test/cases/arel/attributes/attribute_test.rb b/activerecord/test/cases/arel/attributes/attribute_test.rb
index c7bd0a053b..7ebb90c6fd 100644
--- a/activerecord/test/cases/arel/attributes/attribute_test.rb
+++ b/activerecord/test/cases/arel/attributes/attribute_test.rb
@@ -638,6 +638,18 @@ module Arel
)
end
+ if Gem::Version.new("2.7.0") <= Gem::Version.new(RUBY_VERSION)
+ it "can be constructed with a range implicitly starting at Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(eval("..0")) # eval for backwards compatibility
+
+ node.must_equal Nodes::LessThanOrEqual.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ )
+ end
+ 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
@@ -839,6 +851,18 @@ module Arel
)
end
+ if Gem::Version.new("2.7.0") <= Gem::Version.new(RUBY_VERSION)
+ it "can be constructed with a range implicitly starting at Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(eval("..0")) # eval for backwards compatibility
+
+ node.must_equal Nodes::GreaterThan.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ )
+ end
+ 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
diff --git a/activerecord/test/cases/arel/attributes_test.rb b/activerecord/test/cases/arel/attributes_test.rb
index b00af4bd29..1712633ae9 100644
--- a/activerecord/test/cases/arel/attributes_test.rb
+++ b/activerecord/test/cases/arel/attributes_test.rb
@@ -23,46 +23,5 @@ module Arel
assert_equal 2, array.uniq.size
end
end
-
- describe "for" do
- it "deals with unknown column types" do
- column = Struct.new(:type).new :crazy
- Attributes.for(column).must_equal Attributes::Undefined
- end
-
- it "returns the correct constant for strings" do
- [:string, :text, :binary].each do |type|
- column = Struct.new(:type).new type
- Attributes.for(column).must_equal Attributes::String
- end
- end
-
- it "returns the correct constant for ints" do
- column = Struct.new(:type).new :integer
- Attributes.for(column).must_equal Attributes::Integer
- end
-
- it "returns the correct constant for floats" do
- column = Struct.new(:type).new :float
- Attributes.for(column).must_equal Attributes::Float
- end
-
- it "returns the correct constant for decimals" do
- column = Struct.new(:type).new :decimal
- Attributes.for(column).must_equal Attributes::Decimal
- end
-
- it "returns the correct constant for boolean" do
- column = Struct.new(:type).new :boolean
- Attributes.for(column).must_equal Attributes::Boolean
- end
-
- it "returns the correct constant for time" do
- [:date, :datetime, :timestamp, :time].each do |type|
- column = Struct.new(:type).new type
- Attributes.for(column).must_equal Attributes::Time
- end
- end
- end
end
end
diff --git a/activerecord/test/cases/arel/nodes/node_test.rb b/activerecord/test/cases/arel/nodes/node_test.rb
index f4f07ef2c5..f1e0ce1ea9 100644
--- a/activerecord/test/cases/arel/nodes/node_test.rb
+++ b/activerecord/test/cases/arel/nodes/node_test.rb
@@ -18,24 +18,5 @@ module Arel
assert klass.ancestors.include?(Nodes::Node), klass.name
end
end
-
- def test_each
- list = []
- node = Nodes::Node.new
- node.each { |n| list << n }
- assert_equal [node], list
- end
-
- def test_generator
- list = []
- node = Nodes::Node.new
- node.each.each { |n| list << n }
- assert_equal [node], list
- end
-
- def test_enumerable
- node = Nodes::Node.new
- assert_kind_of Enumerable, node
- end
end
end
diff --git a/activerecord/test/cases/arel/select_manager_test.rb b/activerecord/test/cases/arel/select_manager_test.rb
index e6c49cd429..526fe6787a 100644
--- a/activerecord/test/cases/arel/select_manager_test.rb
+++ b/activerecord/test/cases/arel/select_manager_test.rb
@@ -369,16 +369,6 @@ module Arel
mgr = table.from
assert mgr.ast
end
-
- it "should allow orders to work when the ast is grepped" do
- table = Table.new :users
- mgr = table.from
- mgr.project Arel.sql "*"
- mgr.from table
- mgr.orders << Arel::Nodes::Ascending.new(Arel.sql("foo"))
- mgr.ast.grep(Arel::Nodes::OuterJoin)
- mgr.to_sql.must_be_like %{ SELECT * FROM "users" ORDER BY foo ASC }
- end
end
describe "taken" do
diff --git a/activerecord/test/cases/arel/support/fake_record.rb b/activerecord/test/cases/arel/support/fake_record.rb
index 18e6c10c9d..5ebeabd4a3 100644
--- a/activerecord/test/cases/arel/support/fake_record.rb
+++ b/activerecord/test/cases/arel/support/fake_record.rb
@@ -62,6 +62,10 @@ module FakeRecord
comment
end
+ def in_clause_length
+ 3
+ end
+
def schema_cache
self
end
diff --git a/activerecord/test/cases/arel/visitors/depth_first_test.rb b/activerecord/test/cases/arel/visitors/depth_first_test.rb
deleted file mode 100644
index 106be2311d..0000000000
--- a/activerecord/test/cases/arel/visitors/depth_first_test.rb
+++ /dev/null
@@ -1,276 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "../helper"
-
-module Arel
- module Visitors
- class TestDepthFirst < Arel::Test
- Collector = Struct.new(:calls) do
- def call(object)
- calls << object
- end
- end
-
- def setup
- @collector = Collector.new []
- @visitor = Visitors::DepthFirst.new @collector
- end
-
- def test_raises_with_object
- assert_raises(TypeError) do
- @visitor.accept(Object.new)
- end
- end
-
-
- # unary ops
- [
- Arel::Nodes::Not,
- Arel::Nodes::Group,
- Arel::Nodes::On,
- Arel::Nodes::Grouping,
- Arel::Nodes::Offset,
- Arel::Nodes::Ordering,
- Arel::Nodes::StringJoin,
- Arel::Nodes::UnqualifiedColumn,
- Arel::Nodes::ValuesList,
- Arel::Nodes::Limit,
- Arel::Nodes::Else,
- ].each do |klass|
- define_method("test_#{klass.name.gsub('::', '_')}") do
- op = klass.new(:a)
- @visitor.accept op
- assert_equal [:a, op], @collector.calls
- end
- end
-
- # functions
- [
- Arel::Nodes::Exists,
- Arel::Nodes::Avg,
- Arel::Nodes::Min,
- Arel::Nodes::Max,
- Arel::Nodes::Sum,
- ].each do |klass|
- define_method("test_#{klass.name.gsub('::', '_')}") do
- func = klass.new(:a, "b")
- @visitor.accept func
- assert_equal [:a, "b", false, func], @collector.calls
- end
- end
-
- def test_named_function
- func = Arel::Nodes::NamedFunction.new(:a, :b, "c")
- @visitor.accept func
- assert_equal [:a, :b, false, "c", func], @collector.calls
- end
-
- def test_lock
- lock = Nodes::Lock.new true
- @visitor.accept lock
- assert_equal [lock], @collector.calls
- end
-
- def test_count
- count = Nodes::Count.new :a, :b, "c"
- @visitor.accept count
- assert_equal [:a, "c", :b, count], @collector.calls
- end
-
- def test_inner_join
- join = Nodes::InnerJoin.new :a, :b
- @visitor.accept join
- assert_equal [:a, :b, join], @collector.calls
- end
-
- def test_full_outer_join
- join = Nodes::FullOuterJoin.new :a, :b
- @visitor.accept join
- assert_equal [:a, :b, join], @collector.calls
- end
-
- def test_outer_join
- join = Nodes::OuterJoin.new :a, :b
- @visitor.accept join
- assert_equal [:a, :b, join], @collector.calls
- end
-
- def test_right_outer_join
- join = Nodes::RightOuterJoin.new :a, :b
- @visitor.accept join
- 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,
- Arel::Nodes::Concat,
- Arel::Nodes::DoesNotMatch,
- Arel::Nodes::Equality,
- Arel::Nodes::GreaterThan,
- Arel::Nodes::GreaterThanOrEqual,
- Arel::Nodes::In,
- Arel::Nodes::LessThan,
- Arel::Nodes::LessThanOrEqual,
- Arel::Nodes::Matches,
- Arel::Nodes::NotEqual,
- Arel::Nodes::NotIn,
- Arel::Nodes::Or,
- Arel::Nodes::TableAlias,
- Arel::Nodes::As,
- Arel::Nodes::DeleteStatement,
- Arel::Nodes::JoinSource,
- Arel::Nodes::When,
- ].each do |klass|
- define_method("test_#{klass.name.gsub('::', '_')}") do
- binary = klass.new(:a, :b)
- @visitor.accept binary
- assert_equal [:a, :b, binary], @collector.calls
- end
- end
-
- def test_Arel_Nodes_InfixOperation
- binary = Arel::Nodes::InfixOperation.new(:o, :a, :b)
- @visitor.accept binary
- assert_equal [:a, :b, binary], @collector.calls
- end
-
- # N-ary
- [
- Arel::Nodes::And,
- ].each do |klass|
- define_method("test_#{klass.name.gsub('::', '_')}") do
- binary = klass.new([:a, :b, :c])
- @visitor.accept binary
- assert_equal [:a, :b, :c, binary], @collector.calls
- end
- end
-
- [
- Arel::Attributes::Integer,
- Arel::Attributes::Float,
- Arel::Attributes::String,
- Arel::Attributes::Time,
- Arel::Attributes::Boolean,
- Arel::Attributes::Attribute
- ].each do |klass|
- define_method("test_#{klass.name.gsub('::', '_')}") do
- binary = klass.new(:a, :b)
- @visitor.accept binary
- assert_equal [:a, :b, binary], @collector.calls
- end
- end
-
- def test_table
- relation = Arel::Table.new(:users)
- @visitor.accept relation
- assert_equal ["users", relation], @collector.calls
- end
-
- def test_array
- node = Nodes::Or.new(:a, :b)
- list = [node]
- @visitor.accept list
- assert_equal [:a, :b, node, list], @collector.calls
- end
-
- def test_set
- node = Nodes::Or.new(:a, :b)
- set = Set.new([node])
- @visitor.accept set
- assert_equal [:a, :b, node, set], @collector.calls
- end
-
- def test_hash
- node = Nodes::Or.new(:a, :b)
- hash = { node => node }
- @visitor.accept hash
- assert_equal [:a, :b, node, :a, :b, node, hash], @collector.calls
- end
-
- def test_update_statement
- stmt = Nodes::UpdateStatement.new
- stmt.relation = :a
- stmt.values << :b
- stmt.wheres << :c
- stmt.orders << :d
- stmt.limit = :e
-
- @visitor.accept stmt
- assert_equal [:a, :b, stmt.values, :c, stmt.wheres, :d, stmt.orders,
- :e, stmt], @collector.calls
- end
-
- def test_select_core
- core = Nodes::SelectCore.new
- core.projections << :a
- core.froms = :b
- core.wheres << :c
- core.groups << :d
- core.windows << :e
- core.havings << :f
-
- @visitor.accept core
- assert_equal [
- :a, core.projections,
- :b, [],
- core.source,
- :c, core.wheres,
- :d, core.groups,
- :e, core.windows,
- :f, core.havings,
- core], @collector.calls
- end
-
- def test_select_statement
- ss = Nodes::SelectStatement.new
- ss.cores.replace [:a]
- ss.orders << :b
- ss.limit = :c
- ss.lock = :d
- ss.offset = :e
-
- @visitor.accept ss
- assert_equal [
- :a, ss.cores,
- :b, ss.orders,
- :c,
- :d,
- :e,
- ss], @collector.calls
- end
-
- def test_insert_statement
- stmt = Nodes::InsertStatement.new
- stmt.relation = :a
- stmt.columns << :b
- stmt.values = :c
-
- @visitor.accept stmt
- assert_equal [:a, :b, stmt.columns, :c, stmt], @collector.calls
- end
-
- def test_case
- node = Arel::Nodes::Case.new
- node.case = :a
- node.conditions << :b
- node.default = :c
-
- @visitor.accept node
- assert_equal [:a, :b, node.conditions, :c, node], @collector.calls
- end
-
- def test_node
- node = Nodes::Node.new
- @visitor.accept node
- assert_equal [node], @collector.calls
- end
- end
- end
-end
diff --git a/activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb b/activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb
index a07a1a050a..36f9eb49a2 100644
--- a/activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb
+++ b/activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb
@@ -48,7 +48,13 @@ module Arel
node = Nodes::Union.new(Nodes::True.new, Nodes::False.new)
assert_equal "( TRUE UNION FALSE )", node.to_sql
- node.first # from Nodes::Node's Enumerable mixin
+ visitor = Class.new(Visitor) {
+ def visit_Arel_Nodes_Union(o); end
+ alias :visit_Arel_Nodes_True :visit_Arel_Nodes_Union
+ alias :visit_Arel_Nodes_False :visit_Arel_Nodes_Union
+ }.new
+
+ visitor.accept(node)
assert_equal "( TRUE UNION FALSE )", node.to_sql
end
diff --git a/activerecord/test/cases/arel/visitors/oracle12_test.rb b/activerecord/test/cases/arel/visitors/oracle12_test.rb
index ebea12910d..4ce5cab4db 100644
--- a/activerecord/test/cases/arel/visitors/oracle12_test.rb
+++ b/activerecord/test/cases/arel/visitors/oracle12_test.rb
@@ -8,7 +8,6 @@ module Arel
before do
@visitor = Oracle12.new Table.engine.connection
@table = Table.new(:users)
- @attr = @table[:id]
end
def compile(node)
@@ -96,26 +95,6 @@ module Arel
sql.must_be_like %{ "users"."name" IS NOT NULL }
end
end
-
- describe "Nodes::In" do
- it "should know how to visit" do
- ary = (1 .. 1001).to_a
- node = @attr.in ary
- compile(node).must_be_like %{
- "users"."id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000) OR \"users\".\"id\" IN (1001)
- }
- end
- end
-
- describe "Nodes::NotIn" do
- it "should know how to visit" do
- ary = (1 .. 1001).to_a
- node = @attr.not_in ary
- compile(node).must_be_like %{
- "users"."id" NOT IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000) AND \"users\".\"id\" NOT IN (1001)
- }
- 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 f69b201855..893edc7f74 100644
--- a/activerecord/test/cases/arel/visitors/oracle_test.rb
+++ b/activerecord/test/cases/arel/visitors/oracle_test.rb
@@ -8,7 +8,6 @@ module Arel
before do
@visitor = Oracle.new Table.engine.connection
@table = Table.new(:users)
- @attr = @table[:id]
end
def compile(node)
@@ -232,26 +231,6 @@ module Arel
sql.must_be_like %{ "users"."name" IS NOT NULL }
end
end
-
- describe "Nodes::In" do
- it "should know how to visit" do
- ary = (1 .. 1001).to_a
- node = @attr.in ary
- compile(node).must_be_like %{
- "users"."id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000) OR \"users\".\"id\" IN (1001)
- }
- end
- end
-
- describe "Nodes::NotIn" do
- it "should know how to visit" do
- ary = (1 .. 1001).to_a
- node = @attr.not_in ary
- compile(node).must_be_like %{
- "users"."id" NOT IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000) AND \"users\".\"id\" NOT IN (1001)
- }
- 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 625e37f1c0..fd19574876 100644
--- a/activerecord/test/cases/arel/visitors/to_sql_test.rb
+++ b/activerecord/test/cases/arel/visitors/to_sql_test.rb
@@ -395,6 +395,11 @@ module Arel
compile(node).must_be_like %{
"users"."id" IN (1, 2, 3)
}
+
+ node = @attr.in [1, 2, 3, 4, 5]
+ compile(node).must_be_like %{
+ ("users"."id" IN (1, 2, 3) OR "users"."id" IN (4, 5))
+ }
end
it "should return 1=0 when empty right which is always false" do
@@ -545,6 +550,11 @@ module Arel
compile(node).must_be_like %{
"users"."id" NOT IN (1, 2, 3)
}
+
+ node = @attr.not_in [1, 2, 3, 4, 5]
+ compile(node).must_be_like %{
+ "users"."id" NOT IN (1, 2, 3) AND "users"."id" NOT IN (4, 5)
+ }
end
it "should return 1=1 when empty right which is always true" do
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 49f754be63..cbe48a374f 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -37,8 +37,8 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_with_hmt_does_not_table_name_collide_when_joining_associations
authors = Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).order(:id).to_a
- assert_equal 3, assert_no_queries { authors.size }
- assert_equal 10, assert_no_queries { authors[0].comments.size }
+ assert_equal 3, assert_queries(0) { authors.size }
+ assert_equal 10, assert_queries(0) { authors[0].comments.size }
end
def test_eager_association_loading_grafts_stashed_associations_to_correct_parent
@@ -103,14 +103,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
firms = Firm.all.merge!(includes: { account: { firm: :account } }, order: "companies.id").to_a
assert_equal 2, firms.size
assert_equal firms.first.account, firms.first.account.firm.account
- assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account }
- assert_equal companies(:first_firm).account.firm.account, assert_no_queries { firms.first.account.firm.account }
+ assert_equal companies(:first_firm).account, assert_queries(0) { firms.first.account.firm.account }
+ assert_equal companies(:first_firm).account.firm.account, assert_queries(0) { firms.first.account.firm.account }
end
def test_eager_association_loading_with_has_many_sti
topics = Topic.all.merge!(includes: :replies, order: "topics.id").to_a
first, second, = topics(:first).replies.size, topics(:second).replies.size
- assert_no_queries do
+ assert_queries(0) do
assert_equal first, topics[0].replies.size
assert_equal second, topics[1].replies.size
end
@@ -131,13 +131,13 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
replies = Reply.all.merge!(includes: :topic, order: "topics.id").to_a
assert_includes replies, topics(:second)
assert_not_includes replies, topics(:first)
- assert_equal topics(:first), assert_no_queries { replies.first.topic }
+ assert_equal topics(:first), assert_queries(0) { replies.first.topic }
end
def test_eager_association_loading_with_multiple_stis_and_order
author = Author.all.merge!(includes: { posts: [ :special_comments, :very_special_comment ] }, order: ["authors.name", "comments.body", "very_special_comments_posts.body"], where: "posts.id = 4").first
assert_equal authors(:david), author
- assert_no_queries do
+ assert_queries(0) do
author.posts.first.special_comments
author.posts.first.very_special_comment
end
@@ -146,7 +146,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_of_stis_with_multiple_references
authors = Author.all.merge!(includes: { posts: { special_comments: { post: [ :special_comments, :very_special_comment ] } } }, order: "comments.body, very_special_comments_posts.body", where: "posts.id = 4").to_a
assert_equal [authors(:david)], authors
- assert_no_queries do
+ assert_queries(0) do
authors.first.posts.first.special_comments.first.post.special_comments
authors.first.posts.first.special_comments.first.post.very_special_comment
end
@@ -155,14 +155,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_where_first_level_returns_nil
authors = Author.all.merge!(includes: { post_about_thinking: :comments }, order: "authors.id DESC").to_a
assert_equal [authors(:bob), authors(:mary), authors(:david)], authors
- assert_no_queries do
+ assert_queries(0) do
authors[2].post_about_thinking.comments.first
end
end
def test_preload_through_missing_records
post = Post.where.not(author_id: Author.select(:id)).preload(author: { comments: :post }).first!
- assert_no_queries { assert_nil post.author }
+ assert_queries(0) { assert_nil post.author }
end
def test_eager_association_loading_with_missing_first_record
@@ -172,12 +172,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through
source = Vertex.all.merge!(includes: { sinks: { sinks: { sinks: :sinks } } }, order: "vertices.id").first
- assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first }
+ assert_equal vertices(:vertex_4), assert_queries(0) { source.sinks.first.sinks.first.sinks.first }
end
def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many
sink = Vertex.all.merge!(includes: { sources: { sources: { sources: :sources } } }, order: "vertices.id DESC").first
- assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first }
+ assert_equal vertices(:vertex_1), assert_queries(0) { sink.sources.first.sources.first.sources.first.sources.first }
end
def test_eager_association_loading_with_cascaded_interdependent_one_level_and_two_levels
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index 849939de75..9be21b23db 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -14,6 +14,7 @@ module Remembered
included do
after_create :remember
+
private
def remember; self.class.remembered << self; end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 594d161fa3..cb46f9e053 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -101,6 +101,17 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal [taggings(:normal_comment_rating)], rating.taggings_without_tag
end
+ def test_loading_association_with_string_joins
+ rating = Rating.first
+ assert_equal [taggings(:normal_comment_rating)], rating.taggings_with_no_tag
+
+ rating = Rating.preload(:taggings_with_no_tag).first
+ assert_equal [taggings(:normal_comment_rating)], rating.taggings_with_no_tag
+
+ rating = Rating.eager_load(:taggings_with_no_tag).first
+ assert_equal [taggings(:normal_comment_rating)], rating.taggings_with_no_tag
+ end
+
def test_loading_with_scope_including_joins
member = Member.first
assert_equal members(:groucho), member
@@ -240,7 +251,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_load_associated_records_in_one_query_when_adapter_has_no_limit
- assert_called(Comment.connection, :in_clause_length, returns: nil) do
+ assert_not_called(Comment.connection, :in_clause_length) do
post = posts(:welcome)
assert_queries(2) do
Post.includes(:comments).where(id: post.id).to_a
@@ -249,16 +260,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_load_associated_records_in_several_queries_when_many_ids_passed
- assert_called(Comment.connection, :in_clause_length, returns: 1) do
+ assert_called(Comment.connection, :in_clause_length, times: 2, returns: 1) do
post1, post2 = posts(:welcome), posts(:thinking)
- assert_queries(3) do
+ assert_queries(2) do
Post.includes(:comments).where(id: [post1.id, post2.id]).to_a
end
end
end
def test_load_associated_records_in_one_query_when_a_few_ids_passed
- assert_called(Comment.connection, :in_clause_length, returns: 3) do
+ assert_not_called(Comment.connection, :in_clause_length) do
post = posts(:welcome)
assert_queries(2) do
Post.includes(:comments).where(id: post.id).to_a
@@ -512,7 +523,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name
quoted_posts_id = Comment.connection.quote_table_name("posts") + "." + Comment.connection.quote_column_name("id")
assert_nothing_raised do
- Comment.includes(:post).references(:posts).order(Arel.sql(quoted_posts_id))
+ Comment.includes(:post).references(:posts).order(quoted_posts_id)
end
end
@@ -778,7 +789,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
.where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'")
.references(:comments)
.scoping do
-
posts = authors(:david).posts.limit(2).to_a
assert_equal 2, posts.size
end
@@ -787,7 +797,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
.where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')")
.references(:authors, :comments)
.scoping do
-
count = Post.limit(2).count
assert_equal count, posts.size
end
@@ -959,14 +968,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
posts(:thinking, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: Arel.sql("UPPER(posts.title)"), limit: 2, offset: 1
+ order: "UPPER(posts.title)", limit: 2, offset: 1
).to_a
)
assert_equal(
posts(:sti_post_and_comments, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: Arel.sql("UPPER(posts.title) DESC"), limit: 2, offset: 1
+ order: "UPPER(posts.title) DESC", limit: 2, offset: 1
).to_a
)
end
@@ -976,14 +985,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
posts(:thinking, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: [Arel.sql("UPPER(posts.title)"), "posts.id"], limit: 2, offset: 1
+ order: ["UPPER(posts.title)", "posts.id"], limit: 2, offset: 1
).to_a
)
assert_equal(
posts(:sti_post_and_comments, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: [Arel.sql("UPPER(posts.title) DESC"), "posts.id"], limit: 2, offset: 1
+ order: ["UPPER(posts.title) DESC", "posts.id"], limit: 2, offset: 1
).to_a
)
end
@@ -1234,7 +1243,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
Post.all.merge!(select: "posts.*, authors.name as author_name", includes: :comments, joins: :author, order: "posts.id").to_a
end
assert_equal "David", posts[0].author_name
- assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments }
+ assert_equal posts(:welcome).comments.sort_by(&:id), assert_no_queries { posts[0].comments.sort_by(&:id) }
end
def test_eager_loading_with_conditions_on_join_model_preloads
@@ -1246,8 +1255,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_preload_belongs_to_uses_exclusive_scope
- people = Person.males.merge(includes: :primary_contact).to_a
- assert_not_equal people.length, 0
+ people = Person.males.includes(:primary_contact).to_a
+ assert_equal 2, people.length
people.each do |person|
assert_no_queries { assert_not_nil person.primary_contact }
assert_equal Person.find(person.id).primary_contact, person.primary_contact
@@ -1256,16 +1265,17 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_preload_has_many_uses_exclusive_scope
people = Person.males.includes(:agents).to_a
+ assert_equal 2, people.length
people.each do |person|
- assert_equal Person.find(person.id).agents, person.agents
+ assert_equal Person.find(person.id).agents.sort_by(&:id), person.agents.sort_by(&:id)
end
end
def test_preload_has_many_using_primary_key
- expected = Firm.first.clients_using_primary_key.to_a
+ expected = Firm.first.clients_using_primary_key.sort_by(&:id)
firm = Firm.includes(:clients_using_primary_key).first
assert_no_queries do
- assert_equal expected, firm.clients_using_primary_key
+ assert_equal expected, firm.clients_using_primary_key.sort_by(&:id)
end
end
@@ -1516,6 +1526,24 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_match message, error.message
end
+ test "preloading and eager loading of optional instance dependent associations is not supported" do
+ message = "association scope 'posts_mentioning_author' is"
+ error = assert_raises(ArgumentError) do
+ Author.includes(:posts_mentioning_author).to_a
+ end
+ assert_match message, error.message
+
+ error = assert_raises(ArgumentError) do
+ Author.preload(:posts_mentioning_author).to_a
+ end
+ assert_match message, error.message
+
+ error = assert_raises(ArgumentError) do
+ Author.eager_load(:posts_mentioning_author).to_a
+ end
+ assert_match message, error.message
+ end
+
test "preload with invalid argument" do
exception = assert_raises(ArgumentError) do
Author.preload(10).to_a
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index aef8f31112..604a52655c 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -70,8 +70,8 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
extend!(Developer)
extend!(MyApplication::Business::Developer)
- assert Object.const_get "DeveloperAssociationNameAssociationExtension"
- assert MyApplication::Business.const_get "DeveloperAssociationNameAssociationExtension"
+ assert Developer.const_get "AssociationNameAssociationExtension"
+ assert MyApplication::Business::Developer.const_get "AssociationNameAssociationExtension"
end
def test_proxy_association_after_scoped
@@ -87,8 +87,7 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
end
private
-
def extend!(model)
- ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) { }
+ ActiveRecord::Associations::Builder::HasMany.send(:define_extensions, model, :association_name) { }
end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index fe8bdd03ba..25cfa0a723 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -313,10 +313,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_build
devel = Developer.find(1)
- # Load schema information so we don't query below if running just this test.
- Project.define_attribute_methods
-
- proj = assert_no_queries { devel.projects.build("name" => "Projekt") }
+ proj = assert_queries(0) { devel.projects.build("name" => "Projekt") }
assert_not_predicate devel.projects, :loaded?
assert_equal devel.projects.last, proj
@@ -332,10 +329,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_new_aliased_to_build
devel = Developer.find(1)
- # Load schema information so we don't query below if running just this test.
- Project.define_attribute_methods
-
- proj = assert_no_queries { devel.projects.new("name" => "Projekt") }
+ proj = assert_queries(0) { devel.projects.new("name" => "Projekt") }
assert_not_predicate devel.projects, :loaded?
assert_equal devel.projects.last, proj
@@ -556,7 +550,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
developer = project.developers.first
- assert_no_queries do
+ assert_queries(0) do
assert_predicate project.developers, :loaded?
assert_includes project.developers, developer
end
@@ -751,7 +745,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
developer = developers(:david)
developer.projects.reload
- assert_no_queries do
+ assert_queries(0) do
developer.project_ids
developer.project_ids
end
@@ -879,7 +873,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_has_and_belongs_to_many_associations_on_new_records_use_null_relations
projects = Developer.new.projects
- assert_no_queries do
+ assert_queries(0) do
assert_equal [], projects
assert_equal [], projects.where(title: "omg")
assert_equal [], projects.pluck(:title)
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 32285f269a..6c54c2f1cd 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -468,10 +468,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
company = companies(:first_firm)
new_clients = []
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
- assert_no_queries do
+ assert_queries(0) do
new_clients << company.clients_of_firm.build(name: "Another Client")
new_clients << company.clients_of_firm.build(name: "Another Client II")
new_clients << company.clients_of_firm.build(name: "Another Client III")
@@ -492,10 +489,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
company = companies(:first_firm)
new_clients = []
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
- assert_no_queries do
+ assert_queries(0) do
new_clients << company.clients_of_firm.build(name: "Another Client")
new_clients << company.clients_of_firm.build(name: "Another Client II")
new_clients << company.clients_of_firm.build(name: "Another Client III")
@@ -1015,11 +1009,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_transactions_when_adding_to_new_record
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
firm = Firm.new
- assert_no_queries do
+ assert_queries(0) do
firm.clients_of_firm.concat(Client.new("name" => "Natural Company"))
end
end
@@ -1034,10 +1025,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_new_aliased_to_build
company = companies(:first_firm)
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
- new_client = assert_no_queries { company.clients_of_firm.new("name" => "Another Client") }
+ new_client = assert_queries(0) { company.clients_of_firm.new("name" => "Another Client") }
assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
@@ -1048,10 +1036,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build
company = companies(:first_firm)
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
- new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
+ new_client = assert_queries(0) { company.clients_of_firm.build("name" => "Another Client") }
assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
@@ -1109,10 +1094,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_many
company = companies(:first_firm)
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
- new_clients = assert_no_queries { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
+ new_clients = assert_queries(0) { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
assert_equal 2, new_clients.size
end
@@ -1127,10 +1109,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, first_topic.replies.length
- # Load schema information so we don't query below if running just this test.
- Reply.define_attribute_methods
-
- assert_no_queries do
+ assert_queries(0) do
first_topic.replies.build(title: "Not saved", content: "Superstars")
assert_equal 2, first_topic.replies.size
end
@@ -1141,10 +1120,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_via_block
company = companies(:first_firm)
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
- new_client = assert_no_queries { company.clients_of_firm.build { |client| client.name = "Another Client" } }
+ new_client = assert_queries(0) { company.clients_of_firm.build { |client| client.name = "Another Client" } }
assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
@@ -1155,10 +1131,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_many_via_block
company = companies(:first_firm)
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
- new_clients = assert_no_queries do
+ new_clients = assert_queries(0) do
company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) do |client|
client.name = "changed"
end
@@ -1447,11 +1420,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_transaction_when_deleting_new_record
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
firm = Firm.new
- assert_no_queries do
+ assert_queries(0) do
client = Client.new("name" => "New Client")
firm.clients_of_firm << client
firm.clients_of_firm.destroy(client)
@@ -1966,11 +1936,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_transactions_when_replacing_on_new_record
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
firm = Firm.new
- assert_no_queries do
+ assert_queries(0) do
firm.clients_of_firm = [Client.new("name" => "New Client")]
end
end
@@ -2024,11 +1991,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_get_ids_for_association_on_new_record_does_not_try_to_find_records
- # Load schema information so we don't query below if running just this test.
- companies(:first_client).contract_ids
-
company = Company.new
- assert_no_queries do
+ assert_queries(0) do
company.contract_ids
end
@@ -2711,18 +2675,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
bulb = Bulb.create!
tyre = Tyre.create!
- car = Car.create! do |c|
+ car = Car.create!(name: "honda") do |c|
c.bulbs << bulb
c.tyres << tyre
end
+ assert_equal [nil, "honda"], car.saved_change_to_name
+
assert_equal 1, car.bulbs.count
assert_equal 1, car.tyres.count
end
test "associations replace in memory when records have the same id" do
bulb = Bulb.create!
- car = Car.create!(bulbs: [bulb])
+ car = Car.create!(name: "honda", bulbs: [bulb])
+
+ assert_equal [nil, "honda"], car.saved_change_to_name
new_bulb = Bulb.find(bulb.id)
new_bulb.name = "foo"
@@ -2733,7 +2701,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "in memory replacement executes no queries" do
bulb = Bulb.create!
- car = Car.create!(bulbs: [bulb])
+ car = Car.create!(name: "honda", bulbs: [bulb])
+
+ assert_equal [nil, "honda"], car.saved_change_to_name
new_bulb = Bulb.find(bulb.id)
@@ -2765,7 +2735,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "in memory replacements sets inverse instance" do
bulb = Bulb.create!
- car = Car.create!(bulbs: [bulb])
+ car = Car.create!(name: "honda", bulbs: [bulb])
+
+ assert_equal [nil, "honda"], car.saved_change_to_name
new_bulb = Bulb.find(bulb.id)
car.bulbs = [new_bulb]
@@ -2785,7 +2757,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "in memory replacement maintains order" do
first_bulb = Bulb.create!
second_bulb = Bulb.create!
- car = Car.create!(bulbs: [first_bulb, second_bulb])
+ car = Car.create!(name: "honda", bulbs: [first_bulb, second_bulb])
+
+ assert_equal [nil, "honda"], car.saved_change_to_name
same_bulb = Bulb.find(first_bulb.id)
car.bulbs = [second_bulb, same_bulb]
@@ -2958,8 +2932,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [], reference.ideal_jobs
end
- private
+ def test_has_many_preloading_with_duplicate_records
+ posts = Post.joins(:comments).preload(:comments).to_a
+ assert_equal [1, 2], posts.first.comments.map(&:id)
+ end
+ private
def force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.load_target
end
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 c13789f7ec..6faa9664f7 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -58,6 +58,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal preloaded, Marshal.load(Marshal.dump(preloaded))
end
+ def test_through_association_with_joins
+ assert_equal [comments(:eager_other_comment1)], authors(:mary).comments.merge(Post.joins(:comments))
+ end
+
+ def test_through_association_with_left_joins
+ assert_equal [comments(:eager_other_comment1)], authors(:mary).comments.merge(Post.left_joins(:comments))
+ end
+
def test_preload_with_nested_association
posts = Post.preload(:author, :author_favorites_with_scope).to_a
@@ -299,10 +307,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_queries(1) { posts(:thinking) }
new_person = nil # so block binding catches it
- # Load schema information so we don't query below if running just this test.
- Person.define_attribute_methods
-
- assert_no_queries do
+ assert_queries(0) do
new_person = Person.new first_name: "bob"
end
@@ -322,10 +327,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_associate_new_by_building
assert_queries(1) { posts(:thinking) }
- # Load schema information so we don't query below if running just this test.
- Person.define_attribute_methods
-
- assert_no_queries do
+ assert_queries(0) do
posts(:thinking).people.build(first_name: "Bob")
posts(:thinking).people.new(first_name: "Ted")
end
@@ -749,10 +751,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
firm = companies(:first_firm)
lifo = Developer.new(name: "lifo")
- assert_raises(ActiveRecord::RecordInvalid) { firm.developers << lifo }
+ assert_raises(ActiveRecord::RecordInvalid) do
+ assert_deprecated { firm.developers << lifo }
+ end
lifo = Developer.create!(name: "lifo")
- assert_raises(ActiveRecord::RecordInvalid) { firm.developers << lifo }
+ assert_raises(ActiveRecord::RecordInvalid) do
+ assert_deprecated { firm.developers << lifo }
+ end
end
end
@@ -1163,7 +1169,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_create_should_not_raise_exception_when_join_record_has_errors
repair_validations(Categorization) do
Categorization.validate { |r| r.errors[:base] << "Invalid Categorization" }
- Category.create(name: "Fishing", authors: [Author.first])
+ assert_deprecated { Category.create(name: "Fishing", authors: [Author.first]) }
end
end
@@ -1176,7 +1182,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
repair_validations(Categorization) do
Categorization.validate { |r| r.errors[:base] << "Invalid Categorization" }
assert_raises(ActiveRecord::RecordInvalid) do
- Category.create!(name: "Fishing", authors: [Author.first])
+ assert_deprecated { Category.create!(name: "Fishing", authors: [Author.first]) }
end
end
end
@@ -1186,7 +1192,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
Categorization.validate { |r| r.errors[:base] << "Invalid Categorization" }
c = Category.new(name: "Fishing", authors: [Author.first])
assert_raises(ActiveRecord::RecordInvalid) do
- c.save!
+ assert_deprecated { c.save! }
end
end
end
@@ -1195,7 +1201,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
repair_validations(Categorization) do
Categorization.validate { |r| r.errors[:base] << "Invalid Categorization" }
c = Category.new(name: "Fishing", authors: [Author.first])
- assert_not c.save
+ assert_deprecated { assert_not c.save }
end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 7bb629466d..3ef25c7027 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -15,10 +15,13 @@ require "models/post"
require "models/drink_designer"
require "models/chef"
require "models/department"
+require "models/club"
+require "models/membership"
class HasOneAssociationsTest < ActiveRecord::TestCase
self.use_transactional_tests = false unless supports_savepoints?
- fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates, :authors, :author_addresses
+ fixtures :accounts, :companies, :developers, :projects, :developers_projects,
+ :ships, :pirates, :authors, :author_addresses, :memberships, :clubs
def setup
Account.destroyed_account_ids.clear
@@ -253,11 +256,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
def test_build_association_dont_create_transaction
- # Load schema information so we don't query below if running just this test.
- Account.define_attribute_methods
-
firm = Firm.new
- assert_no_queries do
+ assert_queries(0) do
firm.build_account
end
end
@@ -706,6 +706,40 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_has_one_with_touch_option_on_create
+ assert_queries(3) {
+ Club.create(name: "1000 Oaks", membership_attributes: { favourite: true })
+ }
+ end
+
+ def test_has_one_with_touch_option_on_update
+ new_club = Club.create(name: "1000 Oaks")
+ new_club.create_membership
+
+ assert_queries(2) { new_club.update(name: "Effingut") }
+ end
+
+ def test_has_one_with_touch_option_on_touch
+ new_club = Club.create(name: "1000 Oaks")
+ new_club.create_membership
+
+ assert_queries(1) { new_club.touch }
+ end
+
+ def test_has_one_with_touch_option_on_destroy
+ new_club = Club.create(name: "1000 Oaks")
+ new_club.create_membership
+
+ assert_queries(2) { new_club.destroy }
+ end
+
+ def test_has_one_with_touch_option_on_empty_update
+ new_club = Club.create(name: "1000 Oaks")
+ new_club.create_membership
+
+ assert_no_queries { new_club.save }
+ end
+
class SpecialBook < ActiveRecord::Base
self.table_name = "books"
belongs_to :author, class_name: "SpecialAuthor"
diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb
index 0a8863c35d..d44c6407f5 100644
--- a/activerecord/test/cases/associations/left_outer_join_association_test.rb
+++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb
@@ -32,6 +32,10 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
assert_equal 17, Post.left_outer_joins(:comments).count
end
+ def test_merging_left_joins_should_be_left_joins
+ assert_equal 5, Author.left_joins(:posts).merge(Post.no_comments).count
+ end
+
def test_left_joins_aliases_left_outer_joins
assert_equal Post.left_outer_joins(:comments).to_sql, Post.left_joins(:comments).to_sql
end
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index 35da74102d..8d74ae3961 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -626,7 +626,6 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
end
private
-
def assert_includes_and_joins_equal(query, expected, association)
actual = assert_queries(1) { query.joins(association).to_a.uniq }
assert_equal expected, actual
diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb
index c7a78e6bc4..db7f945a36 100644
--- a/activerecord/test/cases/associations/required_test.rb
+++ b/activerecord/test/cases/associations/required_test.rb
@@ -117,7 +117,6 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
end
private
-
def subclass_of(klass, &block)
subclass = Class.new(klass, &block)
def subclass.name
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 9fd62dcf72..71b5407dcc 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -1081,13 +1081,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal ["title"], model.accessed_fields
end
- test "generated attribute methods ancestors have correct class" do
+ test "generated attribute methods ancestors have correct module" do
mod = Topic.send(:generated_attribute_methods)
- assert_match %r(Topic::GeneratedAttributeMethods), mod.inspect
+ assert_equal "Topic::GeneratedAttributeMethods", mod.inspect
end
private
-
def new_topic_like_ar_class(&block)
klass = Class.new(ActiveRecord::Base) do
self.table_name = "topics"
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index a6fb9f0af7..d6ac5a1057 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -241,7 +241,7 @@ module ActiveRecord
test "attributes not backed by database columns are always initialized" do
OverloadedType.create!
- model = OverloadedType.first
+ model = OverloadedType.last
assert_nil model.non_existent_decimal
model.non_existent_decimal = "123"
@@ -253,7 +253,7 @@ module ActiveRecord
attribute :non_existent_decimal, :decimal, default: 123
end
child.create!
- model = child.first
+ model = child.last
assert_equal 123, model.non_existent_decimal
end
@@ -264,7 +264,7 @@ module ActiveRecord
attribute :foo, :string, default: "lol"
end
child.create!
- model = child.first
+ model = child.last
assert_equal "lol", model.foo
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 1a0732c14b..3528ac045f 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -2,6 +2,7 @@
require "cases/helper"
require "models/author"
+require "models/book"
require "models/bird"
require "models/post"
require "models/comment"
@@ -12,12 +13,14 @@ require "models/developer"
require "models/computer"
require "models/invoice"
require "models/line_item"
+require "models/mouse"
require "models/order"
require "models/parrot"
require "models/pirate"
require "models/project"
require "models/ship"
require "models/ship_part"
+require "models/squeak"
require "models/tag"
require "models/tagging"
require "models/treasure"
@@ -39,7 +42,6 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
def self.name; "Person"; end
private
-
def should_be_cool
unless first_name == "cool"
errors.add :first_name, "not cool"
@@ -82,7 +84,6 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
end
private
-
def assert_no_difference_when_adding_callbacks_twice_for(model, association_name)
reflection = model.reflect_on_association(association_name)
assert_no_difference "callbacks_for_model(#{model.name}).length" do
@@ -387,6 +388,20 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert_predicate auditlog, :valid?
end
+
+ def test_validation_does_not_validate_non_dirty_association_target
+ mouse = Mouse.create!(name: "Will")
+ Squeak.create!(mouse: mouse)
+
+ mouse.name = nil
+ mouse.save! validate: false
+
+ squeak = Squeak.last
+
+ assert_equal true, squeak.valid?
+ assert_equal true, squeak.mouse.present?
+ assert_equal true, squeak.valid?
+ end
end
class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase
@@ -644,10 +659,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_before_save
company = companies(:first_firm)
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
- new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
+ new_client = assert_queries(0) { company.clients_of_firm.build("name" => "Another Client") }
assert_not_predicate company.clients_of_firm, :loaded?
company.name += "-changed"
@@ -659,10 +671,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_many_before_save
company = companies(:first_firm)
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
- assert_no_queries { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
+ assert_queries(0) { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
company.name += "-changed"
assert_queries(3) { assert company.save }
@@ -672,10 +681,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_via_block_before_save
company = companies(:first_firm)
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
- new_client = assert_no_queries { company.clients_of_firm.build { |client| client.name = "Another Client" } }
+ new_client = assert_queries(0) { company.clients_of_firm.build { |client| client.name = "Another Client" } }
assert_not_predicate company.clients_of_firm, :loaded?
company.name += "-changed"
@@ -687,10 +693,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_many_via_block_before_save
company = companies(:first_firm)
- # Load schema information so we don't query below if running just this test.
- Client.define_attribute_methods
-
- assert_no_queries do
+ assert_queries(0) do
company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) do |client|
client.name = "changed"
end
@@ -1685,6 +1688,10 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te
super
@pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?")
@pirate.birds.create(name: "cookoo")
+
+ @author = Author.new(name: "DHH")
+ @author.published_books.build(name: "Rework", isbn: "1234")
+ @author.published_books.build(name: "Remote", isbn: "1234")
end
test "should automatically validate associations" do
@@ -1693,6 +1700,42 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te
assert_not_predicate @pirate, :valid?
end
+
+ test "rollbacks whole transaction and raises ActiveRecord::RecordInvalid when associations fail to #save! due to uniqueness validation failure" do
+ author_count_before_save = Author.count
+ book_count_before_save = Book.count
+
+ assert_no_difference "Author.count" do
+ assert_no_difference "Book.count" do
+ exception = assert_raises(ActiveRecord::RecordInvalid) do
+ @author.save!
+ end
+
+ assert_equal("Validation failed: Published books is invalid", exception.message)
+ end
+ end
+
+ assert_equal(author_count_before_save, Author.count)
+ assert_equal(book_count_before_save, Book.count)
+ end
+
+ test "rollbacks whole transaction when associations fail to #save due to uniqueness validation failure" do
+ author_count_before_save = Author.count
+ book_count_before_save = Book.count
+
+ assert_no_difference "Author.count" do
+ assert_no_difference "Book.count" do
+ assert_nothing_raised do
+ result = @author.save
+
+ assert_not(result)
+ end
+ end
+ end
+
+ assert_equal(author_count_before_save, Author.count)
+ assert_equal(book_count_before_save, Book.count)
+ end
end
class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 99f47cfe37..1324bdf9b8 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -67,6 +67,16 @@ end
class BasicsTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :author_addresses, :categorizations, :categories, :posts
+ def test_generated_association_methods_module_name
+ mod = Post.send(:generated_association_methods)
+ assert_equal "Post::GeneratedAssociationMethods", mod.inspect
+ end
+
+ def test_generated_relation_methods_module_name
+ mod = Post.send(:generated_relation_methods)
+ assert_equal "Post::GeneratedRelationMethods", mod.inspect
+ end
+
def test_column_names_are_escaped
conn = ActiveRecord::Base.connection
classname = conn.class.name[/[^:]*$/]
@@ -1131,11 +1141,14 @@ class BasicsTest < ActiveRecord::TestCase
def test_clear_cache!
# preheat cache
c1 = Post.connection.schema_cache.columns("posts")
+ assert_not_equal 0, Post.connection.schema_cache.size
+
ActiveRecord::Base.clear_cache!
+ assert_equal 0, Post.connection.schema_cache.size
+
c2 = Post.connection.schema_cache.columns("posts")
- c1.each_with_index do |v, i|
- assert_not_same v, c2[i]
- end
+ assert_not_equal 0, Post.connection.schema_cache.size
+
assert_equal c1, c2
end
@@ -1205,6 +1218,8 @@ class BasicsTest < ActiveRecord::TestCase
wr.close
assert Marshal.load rd.read
rd.close
+ ensure
+ self.class.send(:remove_const, "Post") if self.class.const_defined?("Post", false)
end
end
@@ -1400,6 +1415,14 @@ class BasicsTest < ActiveRecord::TestCase
assert_not_includes SymbolIgnoredDeveloper.columns_hash.keys, "first_name"
end
+ test ".columns_hash raises an error if the record has an empty table name" do
+ expected_message = "FirstAbstractClass has no table configured. Set one with FirstAbstractClass.table_name="
+ exception = assert_raises(ActiveRecord::TableNotSpecified) do
+ FirstAbstractClass.columns_hash
+ end
+ assert_equal expected_message, exception.message
+ end
+
test "ignored columns have no attribute methods" do
assert_not_respond_to Developer.new, :first_name
assert_not_respond_to Developer.new, :first_name=
@@ -1484,7 +1507,7 @@ class BasicsTest < ActiveRecord::TestCase
test "creating a record raises if preventing writes" do
error = assert_raises ActiveRecord::ReadOnlyError do
- ActiveRecord::Base.connection.while_preventing_writes do
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
Bird.create! name: "Bluejay"
end
end
@@ -1496,7 +1519,7 @@ class BasicsTest < ActiveRecord::TestCase
bird = Bird.create! name: "Bluejay"
error = assert_raises ActiveRecord::ReadOnlyError do
- ActiveRecord::Base.connection.while_preventing_writes do
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
bird.update! name: "Robin"
end
end
@@ -1508,7 +1531,7 @@ class BasicsTest < ActiveRecord::TestCase
bird = Bird.create! name: "Bluejay"
error = assert_raises ActiveRecord::ReadOnlyError do
- ActiveRecord::Base.connection.while_preventing_writes do
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
bird.destroy!
end
end
@@ -1519,7 +1542,7 @@ class BasicsTest < ActiveRecord::TestCase
test "selecting a record does not raise if preventing writes" do
bird = Bird.create! name: "Bluejay"
- ActiveRecord::Base.connection.while_preventing_writes do
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
assert_equal bird, Bird.where(name: "Bluejay").first
end
end
@@ -1527,13 +1550,13 @@ class BasicsTest < ActiveRecord::TestCase
test "an explain query does not raise if preventing writes" do
Bird.create!(name: "Bluejay")
- ActiveRecord::Base.connection.while_preventing_writes do
+ ActiveRecord::Base.connection_handler.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
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
assert_queries(2, ignore_none: true) do
Bird.transaction do
ActiveRecord::Base.connection.materialize_transactions
@@ -1541,4 +1564,59 @@ class BasicsTest < ActiveRecord::TestCase
end
end
end
+
+ test "preventing writes applies to all connections on a handler" do
+ conn1_error = assert_raises ActiveRecord::ReadOnlyError do
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
+ assert_equal ActiveRecord::Base.connection, Bird.connection
+ assert_not_equal ARUnit2Model.connection, Bird.connection
+ Bird.create!(name: "Bluejay")
+ end
+ end
+
+ assert_match %r/\AWrite query attempted while in readonly mode: INSERT /, conn1_error.message
+
+ conn2_error = assert_raises ActiveRecord::ReadOnlyError do
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
+ assert_not_equal ActiveRecord::Base.connection, Professor.connection
+ assert_equal ARUnit2Model.connection, Professor.connection
+ Professor.create!(name: "Professor Bluejay")
+ end
+ end
+
+ assert_match %r/\AWrite query attempted while in readonly mode: INSERT /, conn2_error.message
+ end
+
+ unless in_memory_db?
+ test "preventing writes with multiple handlers" do
+ ActiveRecord::Base.connects_to(database: { writing: :arunit, reading: :arunit })
+
+ conn1_error = assert_raises ActiveRecord::ReadOnlyError do
+ ActiveRecord::Base.connected_to(role: :writing) do
+ assert_equal :writing, ActiveRecord::Base.current_role
+
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
+ Bird.create!(name: "Bluejay")
+ end
+ end
+ end
+
+ assert_match %r/\AWrite query attempted while in readonly mode: INSERT /, conn1_error.message
+
+ conn2_error = assert_raises ActiveRecord::ReadOnlyError do
+ ActiveRecord::Base.connected_to(role: :reading) do
+ assert_equal :reading, ActiveRecord::Base.current_role
+
+ ActiveRecord::Base.connection_handler.while_preventing_writes do
+ Bird.create!(name: "Bluejay")
+ end
+ end
+ end
+
+ assert_match %r/\AWrite query attempted while in readonly mode: INSERT /, conn2_error.message
+ ensure
+ ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
+ ActiveRecord::Base.establish_connection(:arunit)
+ end
+ end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index cf6e280898..0d0bf39f79 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -146,7 +146,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_quote_batch_order
c = Post.connection
- assert_sql(/ORDER BY #{c.quote_table_name('posts')}\.#{c.quote_column_name('id')}/) do
+ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("posts.id"))}/i) do
Post.find_in_batches(batch_size: 1) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 85685d1d00..720446b39d 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -93,7 +93,7 @@ if ActiveRecord::Base.connection.prepared_statements
def test_statement_cache_with_in_clause
@connection.clear_cache!
- topics = Topic.where(id: [1, 3])
+ topics = Topic.where(id: [1, 3]).order(:id)
assert_equal [1, 3], topics.map(&:id)
assert_not_includes statement_cache, to_sql_key(topics.arel)
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 16c2a3661d..dbd1d03c4c 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -139,6 +139,13 @@ class CalculationsTest < ActiveRecord::TestCase
end
end
+ def test_should_not_use_alias_for_grouped_field
+ assert_sql(/GROUP BY #{Regexp.escape(Account.connection.quote_table_name("accounts.firm_id"))}/i) do
+ c = Account.group(:firm_id).order("accounts_firm_id").sum(:credit_limit)
+ assert_equal [1, 2, 6, 9], c.keys.compact
+ end
+ end
+
def test_should_order_by_grouped_field
c = Account.group(:firm_id).order("firm_id").sum(:credit_limit)
assert_equal [1, 2, 6, 9], c.keys.compact
@@ -185,7 +192,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_limit_is_kept
return if current_adapter?(:OracleAdapter)
- queries = assert_sql { Account.limit(1).count }
+ queries = capture_sql { Account.limit(1).count }
assert_equal 1, queries.length
assert_match(/LIMIT/, queries.first)
end
@@ -193,7 +200,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_offset_is_kept
return if current_adapter?(:OracleAdapter)
- queries = assert_sql { Account.offset(1).count }
+ queries = capture_sql { Account.offset(1).count }
assert_equal 1, queries.length
assert_match(/OFFSET/, queries.first)
end
@@ -201,14 +208,14 @@ class CalculationsTest < ActiveRecord::TestCase
def test_limit_with_offset_is_kept
return if current_adapter?(:OracleAdapter)
- queries = assert_sql { Account.limit(1).offset(1).count }
+ queries = capture_sql { Account.limit(1).offset(1).count }
assert_equal 1, queries.length
assert_match(/LIMIT/, queries.first)
assert_match(/OFFSET/, queries.first)
end
def test_no_limit_no_offset
- queries = assert_sql { Account.count }
+ queries = capture_sql { Account.count }
assert_equal 1, queries.length
assert_no_match(/LIMIT/, queries.first)
assert_no_match(/OFFSET/, queries.first)
@@ -224,15 +231,12 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_apply_distinct_in_count
- queries = assert_sql do
+ queries = capture_sql do
Account.distinct.count
Account.group(:firm_id).distinct.count
end
queries.each do |query|
- # `table_alias_length` in `column_alias_for` would execute
- # "SHOW max_identifier_length" statement in PostgreSQL adapter.
- next if query == "SHOW max_identifier_length"
assert_match %r{\ASELECT(?! DISTINCT) COUNT\(DISTINCT\b}, query
end
end
@@ -464,7 +468,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_not_perform_joined_include_by_default
assert_equal Account.count, Account.includes(:firm).count
- queries = assert_sql { Account.includes(:firm).count }
+ queries = capture_sql { Account.includes(:firm).count }
assert_no_match(/join/i, queries.last)
end
@@ -592,11 +596,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_sum_expression
- if current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter)
- assert_equal 636, Account.sum("2 * credit_limit")
- else
- assert_equal 636, Account.sum("2 * credit_limit").to_i
- end
+ assert_equal 636, Account.sum("2 * credit_limit")
end
def test_sum_expression_returns_zero_when_no_records_to_sum
@@ -774,6 +774,12 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [[2, 2], [4, 4]], Reply.includes(:topic).pluck(:id, :"topics.id")
end
+ def test_group_by_with_order_by_virtual_count_attribute
+ expected = { "SpecialPost" => 1, "StiPost" => 2 }
+ actual = Post.group(:type).order(:count).limit(2).maximum(:comments_count)
+ assert_equal expected, actual
+ end if current_adapter?(:PostgreSQLAdapter)
+
def test_group_by_with_limit
expected = { "Post" => 8, "SpecialPost" => 1 }
actual = Post.includes(:comments).group(:type).order(:type).limit(2).count("comments.id")
@@ -840,7 +846,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_pluck_columns_with_same_name
expected = [["The First Topic", "The Second Topic of the day"], ["The Third Topic of the day", "The Fourth Topic of the day"]]
- actual = Topic.joins(:replies)
+ actual = Topic.joins(:replies).order(:id)
.pluck("topics.title", "replies_topics.title")
assert_equal expected, actual
end
@@ -860,28 +866,25 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_pluck_loaded_relation
- Company.attribute_names # Load schema information so we don't query below
companies = Company.order(:id).limit(3).load
- assert_no_queries do
+ assert_queries(0) do
assert_equal ["37signals", "Summit", "Microsoft"], companies.pluck(:name)
end
end
def test_pluck_loaded_relation_multiple_columns
- Company.attribute_names # Load schema information so we don't query below
companies = Company.order(:id).limit(3).load
- assert_no_queries do
+ assert_queries(0) do
assert_equal [[1, "37signals"], [2, "Summit"], [3, "Microsoft"]], companies.pluck(:id, :name)
end
end
def test_pluck_loaded_relation_sql_fragment
- Company.attribute_names # Load schema information so we don't query below
companies = Company.order(:name).limit(3).load
- assert_queries 1 do
+ assert_queries(1) do
assert_equal ["37signals", "Apex", "Ex Nihilo"], companies.pluck(Arel.sql("DISTINCT name"))
end
end
diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb
index 483383257b..f07f3c42e6 100644
--- a/activerecord/test/cases/collection_cache_key_test.rb
+++ b/activerecord/test/cases/collection_cache_key_test.rb
@@ -171,5 +171,39 @@ module ActiveRecord
assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
end
+
+ test "cache_key should be stable when using collection_cache_versioning" do
+ with_collection_cache_versioning do
+ developers = Developer.where(salary: 100000)
+
+ assert_match(/\Adevelopers\/query-(\h+)\z/, developers.cache_key)
+
+ /\Adevelopers\/query-(\h+)\z/ =~ developers.cache_key
+
+ assert_equal ActiveSupport::Digest.hexdigest(developers.to_sql), $1
+ end
+ end
+
+ test "cache_version for relation" do
+ with_collection_cache_versioning do
+ developers = Developer.where(salary: 100000).order(updated_at: :desc)
+ last_developer_timestamp = developers.first.updated_at
+
+ assert_match(/(\d+)-(\d+)\z/, developers.cache_version)
+
+ /(\d+)-(\d+)\z/ =~ developers.cache_version
+
+ assert_equal developers.count.to_s, $1
+ assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $2
+ end
+ end
+
+ def with_collection_cache_versioning(value = true)
+ @old_collection_cache_versioning = ActiveRecord::Base.collection_cache_versioning
+ ActiveRecord::Base.collection_cache_versioning = value
+ yield
+ ensure
+ ActiveRecord::Base.collection_cache_versioning = @old_collection_cache_versioning
+ end
end
end
diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb
index 584e03d196..25e2f20676 100644
--- a/activerecord/test/cases/comment_test.rb
+++ b/activerecord/test/cases/comment_test.rb
@@ -14,6 +14,9 @@ if ActiveRecord::Base.connection.supports_comments?
class BlankComment < ActiveRecord::Base
end
+ class PkCommented < ActiveRecord::Base
+ end
+
setup do
@connection = ActiveRecord::Base.connection
@@ -35,8 +38,13 @@ if ActiveRecord::Base.connection.supports_comments?
t.index :absent_comment
end
+ @connection.create_table("pk_commenteds", comment: "Table comment", id: false, force: true) do |t|
+ t.integer :id, comment: "Primary key comment", primary_key: true
+ end
+
Commented.reset_column_information
BlankComment.reset_column_information
+ PkCommented.reset_column_information
end
teardown do
@@ -44,6 +52,11 @@ if ActiveRecord::Base.connection.supports_comments?
@connection.drop_table "blank_comments", if_exists: true
end
+ def test_default_primary_key_comment
+ column = Commented.columns_hash["id"]
+ assert_nil column.comment
+ end
+
def test_column_created_in_block
column = Commented.columns_hash["name"]
assert_equal :string, column.type
@@ -164,5 +177,17 @@ if ActiveRecord::Base.connection.supports_comments?
column = Commented.columns_hash["name"]
assert_nil column.comment
end
+
+ def test_comment_on_primary_key
+ column = PkCommented.columns_hash["id"]
+ assert_equal "Primary key comment", column.comment
+ assert_equal "Table comment", @connection.table_comment("pk_commenteds")
+ end
+
+ def test_schema_dump_with_primary_key_comment
+ output = dump_table_schema "pk_commenteds"
+ assert_match %r[create_table "pk_commenteds",.*\s+comment: "Table comment"], output
+ assert_no_match %r[create_table "pk_commenteds",.*\s+comment: "Primary key comment"], output
+ end
end
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 6282759a10..843242a897 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -29,7 +29,7 @@ module ActiveRecord
def test_establish_connection_uses_spec_name
old_config = ActiveRecord::Base.configurations
- config = { "readonly" => { "adapter" => "sqlite3" } }
+ config = { "readonly" => { "adapter" => "sqlite3", "pool" => "5" } }
ActiveRecord::Base.configurations = config
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(ActiveRecord::Base.configurations)
spec = resolver.spec(:readonly)
@@ -367,11 +367,24 @@ module ActiveRecord
assert_same klass2.connection, ActiveRecord::Base.connection
end
+ class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+ end
+
+ class MyClass < ApplicationRecord
+ end
+
def test_connection_specification_name_should_fallback_to_parent
klassA = Class.new(Base)
klassB = Class.new(klassA)
+ klassC = Class.new(MyClass)
assert_equal klassB.connection_specification_name, klassA.connection_specification_name
+ assert_equal klassC.connection_specification_name, klassA.connection_specification_name
+
+ assert_equal "primary", klassA.connection_specification_name
+ assert_equal "primary", klassC.connection_specification_name
+
klassA.connection_specification_name = "readonly"
assert_equal "readonly", klassB.connection_specification_name
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
index a2d289bf2f..d3184f39f5 100644
--- a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
@@ -209,7 +209,7 @@ module ActiveRecord
config = {
"default_env" => {
"animals" => { adapter: "sqlite3", database: "db/animals.sqlite3" },
- "primary" => { adapter: "sqlite3", database: "db/primary.sqlite3" }
+ "primary" => { adapter: "sqlite3", database: "db/primary.sqlite3" }
}
}
@prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
@@ -236,7 +236,7 @@ module ActiveRecord
config = {
"default_env" => {
"animals" => { adapter: "sqlite3", database: "db/animals.sqlite3" },
- "primary" => { adapter: "sqlite3", database: "db/primary.sqlite3" }
+ "primary" => { adapter: "sqlite3", database: "db/primary.sqlite3" }
}
}
@prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
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 515bf5df06..95e57f42e3 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
@@ -244,6 +244,25 @@ module ActiveRecord
assert_equal expected, actual
end
+ def test_no_url_sub_key_with_database_url_doesnt_trample_other_envs
+ ENV["DATABASE_URL"] = "postgres://localhost/baz"
+
+ config = { "default_env" => { "database" => "foo" }, "other_env" => { "url" => "postgres://foohost/bardb" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "database" => "baz",
+ "adapter" => "postgresql",
+ "host" => "localhost"
+ },
+ "other_env" =>
+ { "adapter" => "postgresql",
+ "database" => "bardb",
+ "host" => "foohost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
def test_merge_no_conflicts_with_database_url
ENV["DATABASE_URL"] = "postgres://localhost/foo"
@@ -273,6 +292,77 @@ module ActiveRecord
}
assert_equal expected, actual
end
+
+ def test_merge_no_conflicts_with_database_url_and_adapter
+ ENV["DATABASE_URL"] = "postgres://localhost/foo"
+
+ config = { "default_env" => { "adapter" => "postgresql", "pool" => "5" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => "5"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_merge_no_conflicts_with_database_url_and_numeric_pool
+ ENV["DATABASE_URL"] = "postgres://localhost/foo"
+
+ config = { "default_env" => { "pool" => 5 } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => 5
+ }
+ }
+
+ assert_equal expected, actual
+ end
+
+ def test_tiered_configs_with_database_url
+ ENV["DATABASE_URL"] = "postgres://localhost/foo"
+
+ config = {
+ "default_env" => {
+ "primary" => { "pool" => 5 },
+ "animals" => { "pool" => 5 }
+ }
+ }
+
+ expected = {
+ "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => 5
+ }
+
+ ["primary", "animals"].each do |spec_name|
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ actual = configs.configs_for(env_name: "default_env", spec_name: spec_name).config
+ assert_equal expected, actual
+ end
+ end
+
+ def test_does_not_change_other_environments
+ ENV["DATABASE_URL"] = "postgres://localhost/foo"
+ config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" }, "default_env" => {} }
+
+ actual = resolve_spec(:production, config)
+ assert_equal config["production"].merge("name" => "production"), actual
+
+ actual = resolve_spec(:default_env, config)
+ assert_equal({
+ "host" => "localhost",
+ "database" => "foo",
+ "adapter" => "postgresql",
+ "name" => "default_env"
+ }, actual)
+ end
end
end
end
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 38331aa641..774380d7e0 100644
--- a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
@@ -40,7 +40,7 @@ if current_adapter?(:Mysql2Adapter)
end
def test_enum_type_with_value_matching_other_type
- assert_lookup_type :string, "ENUM('unicode', '8bit', 'none')"
+ assert_lookup_type :string, "ENUM('unicode', '8bit', 'none', 'time')"
end
def test_binary_types
@@ -58,7 +58,6 @@ if current_adapter?(:Mysql2Adapter)
end
private
-
def assert_lookup_type(type, lookup)
cast_type = @connection.send(:type_map).lookup(lookup)
assert_equal type, cast_type.type
diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
index 89a9c30f9b..28e232b88f 100644
--- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb
+++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
@@ -95,6 +95,10 @@ module ActiveRecord
assert_no_queries do
assert_equal @database_version.to_s, @cache.database_version.to_s
+
+ if current_adapter?(:Mysql2Adapter)
+ assert_not_nil @cache.database_version.full_version_string
+ end
end
end
diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
index 1c79d776f0..e92bb40632 100644
--- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
@@ -109,7 +109,6 @@ unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strin
end
private
-
def assert_lookup_type(type, lookup)
cast_type = @connection.send(:type_map).lookup(lookup)
assert_equal type, cast_type.type
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index a15ad9a45b..ccbb6e16cd 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -507,7 +507,6 @@ module ActiveRecord
pool.schema_cache = schema_cache
pool.with_connection do |conn|
- assert_not_same pool.schema_cache, conn.schema_cache
assert_equal pool.schema_cache.size, conn.schema_cache.size
assert_same pool.schema_cache.columns(:posts), conn.schema_cache.columns(:posts)
end
@@ -695,6 +694,28 @@ module ActiveRecord
end
end
+ def test_public_connections_access_threadsafe
+ _conn1 = @pool.checkout
+ conn2 = @pool.checkout
+
+ connections = @pool.connections
+ found_conn = nil
+
+ # Without assuming too much about implementation
+ # details make sure that a concurrent change to
+ # the pool is thread-safe.
+ connections.each_index do |idx|
+ if connections[idx] == conn2
+ Thread.new do
+ @pool.remove(conn2)
+ end.join
+ end
+ found_conn = connections[idx]
+ end
+
+ assert_not_nil found_conn
+ end
+
private
def with_single_connection_pool
one_conn_spec = ActiveRecord::Base.connection_pool.spec.dup
diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb
index 1c934602ec..119d48b85e 100644
--- a/activerecord/test/cases/database_statements_test.rb
+++ b/activerecord/test/cases/database_statements_test.rb
@@ -23,7 +23,6 @@ class DatabaseStatementsTest < ActiveRecord::TestCase
end
private
-
def return_the_inserted_id(method:)
# Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
if current_adapter?(:OracleAdapter)
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index ae0ce195b3..8673a99c45 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -3,6 +3,7 @@
require "cases/helper"
require "models/author"
require "models/book"
+require "active_support/log_subscriber/test_helper"
class EnumTest < ActiveRecord::TestCase
fixtures :books, :authors, :author_addresses
@@ -565,4 +566,25 @@ class EnumTest < ActiveRecord::TestCase
assert_raises(NoMethodError) { klass.proposed }
end
+
+ test "enums with a negative condition log a warning" do
+ old_logger = ActiveRecord::Base.logger
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+
+ ActiveRecord::Base.logger = logger
+
+ expected_message = "An enum element in Book uses the prefix 'not_'."\
+ " This will cause a conflict with auto generated negative scopes."
+
+ Class.new(ActiveRecord::Base) do
+ def self.name
+ "Book"
+ end
+ enum status: [:sent, :not_sent]
+ end
+
+ assert_match(expected_message, logger.logged(:warn).first)
+ ensure
+ ActiveRecord::Base.logger = old_logger
+ end
end
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index a0e75f4e89..edd2c768d3 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -72,7 +72,6 @@ if ActiveRecord::Base.connection.supports_explain?
end
private
-
def stub_explain_for_query_plans(query_plans = ["query plan foo", "query plan bar"])
explain_called = 0
diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb
index 66413a98e4..d9e88b3feb 100644
--- a/activerecord/test/cases/finder_respond_to_test.rb
+++ b/activerecord/test/cases/finder_respond_to_test.rb
@@ -54,7 +54,6 @@ class FinderRespondToTest < ActiveRecord::TestCase
end
private
-
def ensure_topic_method_is_not_cached(method_id)
Topic.singleton_class.remove_method method_id if Topic.public_methods.include? method_id
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index ca114d468e..1f2058cc0a 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -245,7 +245,8 @@ class FinderTest < ActiveRecord::TestCase
end
def test_exists_does_not_select_columns_without_alias
- assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do
+ c = Topic.connection
+ assert_sql(/SELECT 1 AS one FROM #{Regexp.escape(c.quote_table_name("topics"))}/i) do
Topic.exists?
end
end
@@ -282,6 +283,11 @@ class FinderTest < ActiveRecord::TestCase
assert_not Post.select(:body).distinct.offset(4).exists?
end
+ def test_exists_with_distinct_and_offset_and_eagerload_and_order
+ assert Post.eager_load(:comments).distinct.offset(10).merge(Comment.order(post_id: :asc)).exists?
+ assert_not Post.eager_load(:comments).distinct.offset(11).merge(Comment.order(post_id: :asc)).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
@@ -517,6 +523,7 @@ class FinderTest < ActiveRecord::TestCase
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
+ assert_equal expected, Topic.order(nil).first
end
def test_model_class_responds_to_first_bang
@@ -540,6 +547,7 @@ class FinderTest < ActiveRecord::TestCase
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
+ assert_equal expected, Topic.order(nil).second
end
def test_model_class_responds_to_second_bang
@@ -563,6 +571,7 @@ class FinderTest < ActiveRecord::TestCase
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
+ assert_equal expected, Topic.order(nil).third
end
def test_model_class_responds_to_third_bang
@@ -586,6 +595,7 @@ class FinderTest < ActiveRecord::TestCase
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
+ assert_equal expected, Topic.order(nil).fourth
end
def test_model_class_responds_to_fourth_bang
@@ -609,6 +619,7 @@ class FinderTest < ActiveRecord::TestCase
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
+ assert_equal expected, Topic.order(nil).fifth
end
def test_model_class_responds_to_fifth_bang
@@ -777,6 +788,7 @@ class FinderTest < ActiveRecord::TestCase
assert_equal expected, clients.first(2)
assert_equal expected, clients.limit(5).first(2)
+ assert_equal expected, clients.order(nil).first(2)
end
def test_implicit_order_column_is_configurable
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 0cb868da6e..a7f01e898e 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -948,7 +948,6 @@ class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase
end
private
-
def fire_connection_notification(connection)
assert_called_with(ActiveRecord::Base.connection_handler, :retrieve_connection, ["book"], returns: connection) do
message_bus = ActiveSupport::Notifications.instrumenter
@@ -1367,7 +1366,6 @@ class MultipleDatabaseFixturesTest < ActiveRecord::TestCase
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
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 543a0aeb39..56c780c4a6 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -189,7 +189,6 @@ end
module InTimeZone
private
-
def in_time_zone(zone)
old_zone = Time.zone
old_tz = ActiveRecord::Base.time_zone_aware_attributes
diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb
index 7b388ebc5e..f41aea6125 100644
--- a/activerecord/test/cases/hot_compatibility_test.rb
+++ b/activerecord/test/cases/hot_compatibility_test.rb
@@ -115,7 +115,6 @@ class HotCompatibilityTest < ActiveRecord::TestCase
end
private
-
def get_prepared_statement_cache(connection)
connection.instance_variable_get(:@statements)
.instance_variable_get(:@cache)[Process.pid]
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 629167e9ed..01e4878c3f 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -471,9 +471,9 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_eager_load_belongs_to_primary_key_quoting
- con = Account.connection
+ c = Account.connection
bind_param = Arel::Nodes::BindParam.new(nil)
- assert_sql(/#{con.quote_table_name('companies')}\.#{con.quote_column_name('id')} = (?:#{Regexp.quote(bind_param.to_sql)}|1)/) do
+ assert_sql(/#{Regexp.escape(c.quote_table_name("companies.id"))} = (?:#{Regexp.escape(bind_param.to_sql)}|1)/i) do
Account.all.merge!(includes: :firm).find(1)
end
end
diff --git a/activerecord/test/cases/insert_all_test.rb b/activerecord/test/cases/insert_all_test.rb
index f24c63031c..d086d77081 100644
--- a/activerecord/test/cases/insert_all_test.rb
+++ b/activerecord/test/cases/insert_all_test.rb
@@ -262,7 +262,6 @@ class InsertAllTest < ActiveRecord::TestCase
end
private
-
def capture_log_output
output = StringIO.new
old_logger, ActiveRecord::Base.logger = ActiveRecord::Base.logger, ActiveSupport::Logger.new(output)
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index 82cf281cff..d68e208617 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -10,7 +10,6 @@ require "models/comment"
module JsonSerializationHelpers
private
-
def set_include_root_in_json(value)
original_root_in_json = ActiveRecord::Base.include_root_in_json
ActiveRecord::Base.include_root_in_json = value
@@ -24,7 +23,7 @@ class JsonSerializationTest < ActiveRecord::TestCase
include JsonSerializationHelpers
class NamespacedContact < Contact
- column :name, :string
+ column :name, "string"
end
def setup
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 04f9b26960..b468da7c76 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -593,7 +593,6 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
end
private
-
def add_counter_column_to(model, col = "test_count")
model.connection.add_column model.table_name, col, :integer, null: false, default: 0
model.reset_column_information
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 726ccf925e..ff2a694e66 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -12,6 +12,7 @@ module ActiveRecord
def setup
super
@connection = ActiveRecord::Base.connection
+ @schema_migration = @connection.schema_migration
@verbose_was = ActiveRecord::Migration.verbose
ActiveRecord::Migration.verbose = false
@@ -38,7 +39,7 @@ module ActiveRecord
}.new
assert connection.index_exists?(:testings, :foo, name: "custom_index_name")
- assert_raise(StandardError) { ActiveRecord::Migrator.new(:up, [migration]).migrate }
+ assert_raise(StandardError) { ActiveRecord::Migrator.new(:up, [migration], @schema_migration).migrate }
assert connection.index_exists?(:testings, :foo, name: "custom_index_name")
end
@@ -53,7 +54,7 @@ module ActiveRecord
}.new
assert connection.index_exists?(:testings, :bar)
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration).migrate
assert_not connection.index_exists?(:testings, :bar)
end
@@ -67,7 +68,7 @@ module ActiveRecord
end
}.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration).migrate
assert_not connection.index_exists?(:more_testings, :foo_id)
assert_not connection.index_exists?(:more_testings, :bar_id)
@@ -84,7 +85,7 @@ module ActiveRecord
end
}.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration).migrate
assert connection.column_exists?(:more_testings, :created_at, null: true)
assert connection.column_exists?(:more_testings, :updated_at, null: true)
@@ -101,7 +102,7 @@ module ActiveRecord
end
}.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration).migrate
assert connection.column_exists?(:testings, :created_at, null: true)
assert connection.column_exists?(:testings, :updated_at, null: true)
@@ -117,7 +118,7 @@ module ActiveRecord
end
}.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration).migrate
assert connection.column_exists?(:testings, :created_at, null: true)
assert connection.column_exists?(:testings, :updated_at, null: true)
@@ -131,7 +132,7 @@ module ActiveRecord
end
}.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration).migrate
assert connection.column_exists?(:testings, :created_at, null: true)
assert connection.column_exists?(:testings, :updated_at, null: true)
@@ -146,7 +147,7 @@ module ActiveRecord
end
}.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], @schema_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)
@@ -163,7 +164,7 @@ module ActiveRecord
end
}.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], @schema_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)
@@ -179,7 +180,7 @@ module ActiveRecord
end
}.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], @schema_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)
@@ -193,7 +194,7 @@ module ActiveRecord
end
}.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], @schema_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)
@@ -230,7 +231,7 @@ module ActiveRecord
end
}.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration).migrate
assert connection.column_exists?(:testings, :foo, comment: "comment")
end
@@ -243,7 +244,7 @@ module ActiveRecord
end
}.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration).migrate
assert_equal "comment", connection.table_comment("testings")
end
@@ -261,7 +262,7 @@ module ActiveRecord
}.new
Testing.create!
- ActiveRecord::Migrator.new(:up, [migration]).migrate
+ ActiveRecord::Migrator.new(:up, [migration], @schema_migration).migrate
assert_equal ["foobar"], Testing.all.map(&:foo)
ensure
ActiveRecord::Base.clear_cache!
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index e0cbb29dcf..0257545330 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -151,7 +151,6 @@ module ActiveRecord
end
private
-
def with_table_cleanup
tables_before = connection.data_sources
diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb
index c056199140..da8bdc472a 100644
--- a/activerecord/test/cases/migration/helper.rb
+++ b/activerecord/test/cases/migration/helper.rb
@@ -34,7 +34,6 @@ module ActiveRecord
end
private
-
delegate(*CONNECTION_METHODS, to: :connection)
end
end
diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb
index 28f4cc124b..431047f957 100644
--- a/activerecord/test/cases/migration/logger_test.rb
+++ b/activerecord/test/cases/migration/logger_test.rb
@@ -17,19 +17,20 @@ module ActiveRecord
def setup
super
- ActiveRecord::SchemaMigration.create_table
- ActiveRecord::SchemaMigration.delete_all
+ @schema_migration = ActiveRecord::Base.connection.schema_migration
+ @schema_migration.create_table
+ @schema_migration.delete_all
end
teardown do
- ActiveRecord::SchemaMigration.drop_table
+ @schema_migration.drop_table
end
def test_migration_should_be_run_without_logger
previous_logger = ActiveRecord::Base.logger
ActiveRecord::Base.logger = nil
migrations = [Migration.new("a", 1), Migration.new("b", 2), Migration.new("c", 3)]
- ActiveRecord::Migrator.new(:up, migrations).migrate
+ ActiveRecord::Migrator.new(:up, migrations, @schema_migration).migrate
ensure
ActiveRecord::Base.logger = previous_logger
end
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index 769241ba12..451894fc54 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -126,7 +126,6 @@ module ActiveRecord
end
private
-
def with_polymorphic_column
add_column table_name, :supplier_type, :string
add_index table_name, [:supplier_id, :supplier_type]
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 8e8ed494d9..20f577b2c5 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -38,6 +38,7 @@ class MigrationTest < ActiveRecord::TestCase
end
Reminder.reset_column_information
@verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false
+ @schema_migration = ActiveRecord::Base.connection.schema_migration
ActiveRecord::Base.connection.schema_cache.clear!
end
@@ -84,7 +85,7 @@ class MigrationTest < ActiveRecord::TestCase
def test_migrator_versions
migrations_path = MIGRATIONS_ROOT + "/valid"
- migrator = ActiveRecord::MigrationContext.new(migrations_path)
+ migrator = ActiveRecord::MigrationContext.new(migrations_path, @schema_migration)
migrator.up
assert_equal 3, migrator.current_version
@@ -102,23 +103,23 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
migrations_path = MIGRATIONS_ROOT + "/valid"
- migrator = ActiveRecord::MigrationContext.new(migrations_path)
+ migrator = ActiveRecord::MigrationContext.new(migrations_path, @schema_migration)
assert_equal true, migrator.needs_migration?
end
def test_any_migrations
- migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid")
+ migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid", @schema_migration)
assert_predicate migrator, :any_migrations?
- migrator_empty = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/empty")
+ migrator_empty = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/empty", @schema_migration)
assert_not_predicate migrator_empty, :any_migrations?
end
def test_migration_version
- migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/version_check")
+ migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/version_check", @schema_migration)
assert_equal 0, migrator.current_version
migrator.up(20131219224947)
assert_equal 20131219224947, migrator.current_version
@@ -190,6 +191,7 @@ class MigrationTest < ActiveRecord::TestCase
assert_not_predicate BigNumber, :table_exists?
GiveMeBigNumbers.up
+ assert_predicate BigNumber, :table_exists?
BigNumber.reset_column_information
assert BigNumber.create(
@@ -248,7 +250,7 @@ class MigrationTest < ActiveRecord::TestCase
assert_not_predicate Reminder, :table_exists?
name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" }
- migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid")
+ migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid", @schema_migration)
migrator.up(&name_filter)
assert_column Person, :last_name
@@ -310,7 +312,7 @@ class MigrationTest < ActiveRecord::TestCase
end
}.new
- migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ migrator = ActiveRecord::Migrator.new(:up, [migration], @schema_migration, 100)
e = assert_raise(StandardError) { migrator.migrate }
@@ -331,7 +333,7 @@ class MigrationTest < ActiveRecord::TestCase
end
}.new
- migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ migrator = ActiveRecord::Migrator.new(:up, [migration], @schema_migration, 100)
e = assert_raise(StandardError) { migrator.run }
@@ -354,7 +356,7 @@ class MigrationTest < ActiveRecord::TestCase
end
}.new
- migrator = ActiveRecord::Migrator.new(:up, [migration], 101)
+ migrator = ActiveRecord::Migrator.new(:up, [migration], @schema_migration, 101)
e = assert_raise(StandardError) { migrator.migrate }
assert_equal "An error has occurred, all later migrations canceled:\n\nSomething broke", e.message
@@ -413,7 +415,7 @@ class MigrationTest < ActiveRecord::TestCase
def test_internal_metadata_stores_environment
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrations_path = MIGRATIONS_ROOT + "/valid"
- migrator = ActiveRecord::MigrationContext.new(migrations_path)
+ migrator = ActiveRecord::MigrationContext.new(migrations_path, @schema_migration)
migrator.up
assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
@@ -441,7 +443,7 @@ class MigrationTest < ActiveRecord::TestCase
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrations_path = MIGRATIONS_ROOT + "/valid"
- migrator = ActiveRecord::MigrationContext.new(migrations_path)
+ migrator = ActiveRecord::MigrationContext.new(migrations_path, @schema_migration)
migrator.up
assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
assert_equal "bar", ActiveRecord::InternalMetadata[:foo]
@@ -482,6 +484,7 @@ class MigrationTest < ActiveRecord::TestCase
Thing.reset_table_name
Thing.reset_sequence_name
WeNeedThings.up
+ assert_predicate Thing, :table_exists?
Thing.reset_column_information
assert Thing.create("content" => "hello world")
@@ -502,8 +505,9 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Base.table_name_suffix = "_suffix"
Reminder.reset_table_name
Reminder.reset_sequence_name
- Reminder.reset_column_information
WeNeedReminders.up
+ assert_predicate Reminder, :table_exists?
+ Reminder.reset_column_information
assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
assert_equal "hello world", Reminder.first.content
@@ -636,7 +640,7 @@ class MigrationTest < ActiveRecord::TestCase
if ActiveRecord::Base.connection.supports_advisory_locks?
def test_migrator_generates_valid_lock_id
migration = Class.new(ActiveRecord::Migration::Current).new
- migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ migrator = ActiveRecord::Migrator.new(:up, [migration], @schema_migration, 100)
lock_id = migrator.send(:generate_migrator_advisory_lock_id)
@@ -650,7 +654,7 @@ class MigrationTest < ActiveRecord::TestCase
# It is important we are consistent with how we generate this so that
# exclusive locking works across migrator versions
migration = Class.new(ActiveRecord::Migration::Current).new
- migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ migrator = ActiveRecord::Migrator.new(:up, [migration], @schema_migration, 100)
lock_id = migrator.send(:generate_migrator_advisory_lock_id)
@@ -672,7 +676,7 @@ class MigrationTest < ActiveRecord::TestCase
end
}.new
- migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ migrator = ActiveRecord::Migrator.new(:up, [migration], @schema_migration, 100)
lock_id = migrator.send(:generate_migrator_advisory_lock_id)
with_another_process_holding_lock(lock_id) do
@@ -693,7 +697,7 @@ class MigrationTest < ActiveRecord::TestCase
end
}.new
- migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ migrator = ActiveRecord::Migrator.new(:up, [migration], @schema_migration, 100)
lock_id = migrator.send(:generate_migrator_advisory_lock_id)
with_another_process_holding_lock(lock_id) do
@@ -706,7 +710,7 @@ class MigrationTest < ActiveRecord::TestCase
def test_with_advisory_lock_raises_the_right_error_when_it_fails_to_release_lock
migration = Class.new(ActiveRecord::Migration::Current).new
- migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ migrator = ActiveRecord::Migrator.new(:up, [migration], @schema_migration, 100)
lock_id = migrator.send(:generate_migrator_advisory_lock_id)
e = assert_raises(ActiveRecord::ConcurrentMigrationError) do
@@ -854,7 +858,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
expected_query_count = {
- "Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not
+ "Mysql2Adapter" => 1, # mysql2 supports creating two indexes using one statement
"PostgreSQLAdapter" => 2,
}.fetch(classname) {
raise "need an expected query count for #{classname}"
@@ -886,7 +890,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
expected_query_count = {
- "Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not
+ "Mysql2Adapter" => 1, # mysql2 supports dropping and creating two indexes using one statement
"PostgreSQLAdapter" => 2,
}.fetch(classname) {
raise "need an expected query count for #{classname}"
@@ -935,7 +939,6 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
end
private
-
def with_bulk_change_table
# Reset columns/indexes cache as we're changing the table
@columns = @indexes = nil
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index 30e199f1c5..aeba8e1d14 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -23,8 +23,9 @@ class MigratorTest < ActiveRecord::TestCase
def setup
super
- ActiveRecord::SchemaMigration.create_table
- ActiveRecord::SchemaMigration.delete_all rescue nil
+ @schema_migration = ActiveRecord::Base.connection.schema_migration
+ @schema_migration.create_table
+ @schema_migration.delete_all rescue nil
@verbose_was = ActiveRecord::Migration.verbose
ActiveRecord::Migration.message_count = 0
ActiveRecord::Migration.class_eval do
@@ -36,7 +37,7 @@ class MigratorTest < ActiveRecord::TestCase
end
teardown do
- ActiveRecord::SchemaMigration.delete_all rescue nil
+ @schema_migration.delete_all rescue nil
ActiveRecord::Migration.verbose = @verbose_was
ActiveRecord::Migration.class_eval do
undef :puts
@@ -49,7 +50,7 @@ class MigratorTest < ActiveRecord::TestCase
def test_migrator_with_duplicate_names
e = assert_raises(ActiveRecord::DuplicateMigrationNameError) do
list = [ActiveRecord::Migration.new("Chunky"), ActiveRecord::Migration.new("Chunky")]
- ActiveRecord::Migrator.new(:up, list)
+ ActiveRecord::Migrator.new(:up, list, @schema_migration)
end
assert_match(/Multiple migrations have the name Chunky/, e.message)
end
@@ -57,39 +58,40 @@ class MigratorTest < ActiveRecord::TestCase
def test_migrator_with_duplicate_versions
assert_raises(ActiveRecord::DuplicateMigrationVersionError) do
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 1)]
- ActiveRecord::Migrator.new(:up, list)
+ ActiveRecord::Migrator.new(:up, list, @schema_migration)
end
end
def test_migrator_with_missing_version_numbers
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
- ActiveRecord::Migrator.new(:up, list, 3).run
+ ActiveRecord::Migrator.new(:up, list, @schema_migration, 3).run
end
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
- ActiveRecord::Migrator.new(:up, list, -1).run
+ ActiveRecord::Migrator.new(:up, list, @schema_migration, -1).run
end
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
- ActiveRecord::Migrator.new(:up, list, 0).run
+ ActiveRecord::Migrator.new(:up, list, @schema_migration, 0).run
end
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
- ActiveRecord::Migrator.new(:up, list, 3).migrate
+ ActiveRecord::Migrator.new(:up, list, @schema_migration, 3).migrate
end
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
- ActiveRecord::Migrator.new(:up, list, -1).migrate
+ ActiveRecord::Migrator.new(:up, list, @schema_migration, -1).migrate
end
end
def test_finds_migrations
- migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid").migrations
+ schema_migration = ActiveRecord::Base.connection.schema_migration
+ migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid", schema_migration).migrations
[[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
assert_equal migrations[i].version, pair.first
@@ -98,7 +100,8 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_finds_migrations_in_subdirectories
- migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid_with_subdirectories").migrations
+ schema_migration = ActiveRecord::Base.connection.schema_migration
+ migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid_with_subdirectories", schema_migration).migrations
[[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
assert_equal migrations[i].version, pair.first
@@ -107,8 +110,9 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_finds_migrations_from_two_directories
+ schema_migration = ActiveRecord::Base.connection.schema_migration
directories = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"]
- migrations = ActiveRecord::MigrationContext.new(directories).migrations
+ migrations = ActiveRecord::MigrationContext.new(directories, schema_migration).migrations
[[20090101010101, "PeopleHaveHobbies"],
[20090101010202, "PeopleHaveDescriptions"],
@@ -121,14 +125,16 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_finds_migrations_in_numbered_directory
- migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/10_urban").migrations
+ schema_migration = ActiveRecord::Base.connection.schema_migration
+ migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/10_urban", schema_migration).migrations
assert_equal 9, migrations[0].version
assert_equal "AddExpressions", migrations[0].name
end
def test_relative_migrations
+ schema_migration = ActiveRecord::Base.connection.schema_migration
list = Dir.chdir(MIGRATIONS_ROOT) do
- ActiveRecord::MigrationContext.new("valid").migrations
+ ActiveRecord::MigrationContext.new("valid", schema_migration).migrations
end
migration_proxy = list.find { |item|
@@ -138,9 +144,9 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_finds_pending_migrations
- ActiveRecord::SchemaMigration.create!(version: "1")
+ @schema_migration.create!(version: "1")
migration_list = [ActiveRecord::Migration.new("foo", 1), ActiveRecord::Migration.new("bar", 3)]
- migrations = ActiveRecord::Migrator.new(:up, migration_list).pending_migrations
+ migrations = ActiveRecord::Migrator.new(:up, migration_list, @schema_migration).pending_migrations
assert_equal 1, migrations.size
assert_equal migration_list.last, migrations.first
@@ -148,35 +154,38 @@ class MigratorTest < ActiveRecord::TestCase
def test_migrations_status
path = MIGRATIONS_ROOT + "/valid"
+ schema_migration = ActiveRecord::Base.connection.schema_migration
- ActiveRecord::SchemaMigration.create(version: 2)
- ActiveRecord::SchemaMigration.create(version: 10)
+ @schema_migration.create(version: 2)
+ @schema_migration.create(version: 10)
assert_equal [
["down", "001", "Valid people have last names"],
["up", "002", "We need reminders"],
["down", "003", "Innocent jointable"],
["up", "010", "********** NO FILE **********"],
- ], ActiveRecord::MigrationContext.new(path).migrations_status
+ ], ActiveRecord::MigrationContext.new(path, schema_migration).migrations_status
end
def test_migrations_status_in_subdirectories
path = MIGRATIONS_ROOT + "/valid_with_subdirectories"
+ schema_migration = ActiveRecord::Base.connection.schema_migration
- ActiveRecord::SchemaMigration.create(version: 2)
- ActiveRecord::SchemaMigration.create(version: 10)
+ @schema_migration.create(version: 2)
+ @schema_migration.create(version: 10)
assert_equal [
["down", "001", "Valid people have last names"],
["up", "002", "We need reminders"],
["down", "003", "Innocent jointable"],
["up", "010", "********** NO FILE **********"],
- ], ActiveRecord::MigrationContext.new(path).migrations_status
+ ], ActiveRecord::MigrationContext.new(path, schema_migration).migrations_status
end
def test_migrations_status_with_schema_define_in_subdirectories
path = MIGRATIONS_ROOT + "/valid_with_subdirectories"
prev_paths = ActiveRecord::Migrator.migrations_paths
+ schema_migration = ActiveRecord::Base.connection.schema_migration
ActiveRecord::Migrator.migrations_paths = path
ActiveRecord::Schema.define(version: 3) do
@@ -186,16 +195,17 @@ class MigratorTest < ActiveRecord::TestCase
["up", "001", "Valid people have last names"],
["up", "002", "We need reminders"],
["up", "003", "Innocent jointable"],
- ], ActiveRecord::MigrationContext.new(path).migrations_status
+ ], ActiveRecord::MigrationContext.new(path, schema_migration).migrations_status
ensure
ActiveRecord::Migrator.migrations_paths = prev_paths
end
def test_migrations_status_from_two_directories
paths = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"]
+ schema_migration = ActiveRecord::Base.connection.schema_migration
- ActiveRecord::SchemaMigration.create(version: "20100101010101")
- ActiveRecord::SchemaMigration.create(version: "20160528010101")
+ @schema_migration.create(version: "20100101010101")
+ @schema_migration.create(version: "20160528010101")
assert_equal [
["down", "20090101010101", "People have hobbies"],
@@ -204,18 +214,18 @@ class MigratorTest < ActiveRecord::TestCase
["down", "20100201010101", "Valid with timestamps we need reminders"],
["down", "20100301010101", "Valid with timestamps innocent jointable"],
["up", "20160528010101", "********** NO FILE **********"],
- ], ActiveRecord::MigrationContext.new(paths).migrations_status
+ ], ActiveRecord::MigrationContext.new(paths, schema_migration).migrations_status
end
def test_migrator_interleaved_migrations
pass_one = [Sensor.new("One", 1)]
- ActiveRecord::Migrator.new(:up, pass_one).migrate
+ ActiveRecord::Migrator.new(:up, pass_one, @schema_migration).migrate
assert pass_one.first.went_up
assert_not pass_one.first.went_down
pass_two = [Sensor.new("One", 1), Sensor.new("Three", 3)]
- ActiveRecord::Migrator.new(:up, pass_two).migrate
+ ActiveRecord::Migrator.new(:up, pass_two, @schema_migration).migrate
assert_not pass_two[0].went_up
assert pass_two[1].went_up
assert pass_two.all? { |x| !x.went_down }
@@ -224,7 +234,7 @@ class MigratorTest < ActiveRecord::TestCase
Sensor.new("Two", 2),
Sensor.new("Three", 3)]
- ActiveRecord::Migrator.new(:down, pass_three).migrate
+ ActiveRecord::Migrator.new(:down, pass_three, @schema_migration).migrate
assert pass_three[0].went_down
assert_not pass_three[1].went_down
assert pass_three[2].went_down
@@ -232,7 +242,7 @@ class MigratorTest < ActiveRecord::TestCase
def test_up_calls_up
migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
- migrator = ActiveRecord::Migrator.new(:up, migrations)
+ migrator = ActiveRecord::Migrator.new(:up, migrations, @schema_migration)
migrator.migrate
assert migrations.all?(&:went_up)
assert migrations.all? { |m| !m.went_down }
@@ -243,7 +253,7 @@ class MigratorTest < ActiveRecord::TestCase
test_up_calls_up
migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
- migrator = ActiveRecord::Migrator.new(:down, migrations)
+ migrator = ActiveRecord::Migrator.new(:down, migrations, @schema_migration)
migrator.migrate
assert migrations.all? { |m| !m.went_up }
assert migrations.all?(&:went_down)
@@ -251,30 +261,31 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_current_version
- ActiveRecord::SchemaMigration.create!(version: "1000")
- migrator = ActiveRecord::MigrationContext.new("db/migrate")
+ @schema_migration.create!(version: "1000")
+ schema_migration = ActiveRecord::Base.connection.schema_migration
+ migrator = ActiveRecord::MigrationContext.new("db/migrate", schema_migration)
assert_equal 1000, migrator.current_version
end
def test_migrator_one_up
calls, migrations = sensors(3)
- ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1).migrate
assert_equal [[:up, 1]], calls
calls.clear
- ActiveRecord::Migrator.new(:up, migrations, 2).migrate
+ ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 2).migrate
assert_equal [[:up, 2]], calls
end
def test_migrator_one_down
calls, migrations = sensors(3)
- ActiveRecord::Migrator.new(:up, migrations).migrate
+ ActiveRecord::Migrator.new(:up, migrations, @schema_migration).migrate
assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
calls.clear
- ActiveRecord::Migrator.new(:down, migrations, 1).migrate
+ ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 1).migrate
assert_equal [[:down, 3], [:down, 2]], calls
end
@@ -282,17 +293,17 @@ class MigratorTest < ActiveRecord::TestCase
def test_migrator_one_up_one_down
calls, migrations = sensors(3)
- ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1).migrate
assert_equal [[:up, 1]], calls
calls.clear
- ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 0).migrate
assert_equal [[:down, 1]], calls
end
def test_migrator_double_up
calls, migrations = sensors(3)
- migrator = ActiveRecord::Migrator.new(:up, migrations, 1)
+ migrator = ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1)
assert_equal(0, migrator.current_version)
migrator.migrate
@@ -305,7 +316,7 @@ class MigratorTest < ActiveRecord::TestCase
def test_migrator_double_down
calls, migrations = sensors(3)
- migrator = ActiveRecord::Migrator.new(:up, migrations, 1)
+ migrator = ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1)
assert_equal 0, migrator.current_version
@@ -313,7 +324,7 @@ class MigratorTest < ActiveRecord::TestCase
assert_equal [[:up, 1]], calls
calls.clear
- migrator = ActiveRecord::Migrator.new(:down, migrations, 1)
+ migrator = ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 1)
migrator.run
assert_equal [[:down, 1]], calls
calls.clear
@@ -328,12 +339,12 @@ class MigratorTest < ActiveRecord::TestCase
_, migrations = sensors(3)
ActiveRecord::Migration.verbose = true
- ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1).migrate
assert_not_equal 0, ActiveRecord::Migration.message_count
ActiveRecord::Migration.message_count = 0
- ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 0).migrate
assert_not_equal 0, ActiveRecord::Migration.message_count
end
@@ -341,9 +352,9 @@ class MigratorTest < ActiveRecord::TestCase
_, migrations = sensors(3)
ActiveRecord::Migration.verbose = false
- ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1).migrate
assert_equal 0, ActiveRecord::Migration.message_count
- ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 0).migrate
assert_equal 0, ActiveRecord::Migration.message_count
end
@@ -351,23 +362,24 @@ class MigratorTest < ActiveRecord::TestCase
calls, migrations = sensors(3)
# migrate up to 1
- ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1).migrate
assert_equal [[:up, 1]], calls
calls.clear
# migrate down to 0
- ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 0).migrate
assert_equal [[:down, 1]], calls
calls.clear
# migrate down to 0 again
- ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 0).migrate
assert_equal [], calls
end
def test_migrator_going_down_due_to_version_target
+ schema_migration = ActiveRecord::Base.connection.schema_migration
calls, migrator = migrator_class(3)
- migrator = migrator.new("valid")
+ migrator = migrator.new("valid", schema_migration)
migrator.up(1)
assert_equal [[:up, 1]], calls
@@ -382,8 +394,9 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_migrator_output_when_running_multiple_migrations
+ schema_migration = ActiveRecord::Base.connection.schema_migration
_, migrator = migrator_class(3)
- migrator = migrator.new("valid")
+ migrator = migrator.new("valid", schema_migration)
result = migrator.migrate
assert_equal(3, result.count)
@@ -397,8 +410,9 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_migrator_output_when_running_single_migration
+ schema_migration = ActiveRecord::Base.connection.schema_migration
_, migrator = migrator_class(1)
- migrator = migrator.new("valid")
+ migrator = migrator.new("valid", schema_migration)
result = migrator.run(:up, 1)
@@ -406,8 +420,9 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_migrator_rollback
+ schema_migration = ActiveRecord::Base.connection.schema_migration
_, migrator = migrator_class(3)
- migrator = migrator.new("valid")
+ migrator = migrator.new("valid", schema_migration)
migrator.migrate
assert_equal(3, migrator.current_version)
@@ -426,18 +441,20 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_migrator_db_has_no_schema_migrations_table
+ schema_migration = ActiveRecord::Base.connection.schema_migration
_, migrator = migrator_class(3)
- migrator = migrator.new("valid")
+ migrator = migrator.new("valid", schema_migration)
- ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
- assert_not ActiveRecord::Base.connection.table_exists?("schema_migrations")
+ ActiveRecord::SchemaMigration.drop_table
+ assert_not_predicate ActiveRecord::SchemaMigration, :table_exists?
migrator.migrate(1)
- assert ActiveRecord::Base.connection.table_exists?("schema_migrations")
+ assert_predicate ActiveRecord::SchemaMigration, :table_exists?
end
def test_migrator_forward
+ schema_migration = ActiveRecord::Base.connection.schema_migration
_, migrator = migrator_class(3)
- migrator = migrator.new("/valid")
+ migrator = migrator.new("/valid", schema_migration)
migrator.migrate(1)
assert_equal(1, migrator.current_version)
@@ -450,18 +467,20 @@ class MigratorTest < ActiveRecord::TestCase
def test_only_loads_pending_migrations
# migrate up to 1
- ActiveRecord::SchemaMigration.create!(version: "1")
+ @schema_migration.create!(version: "1")
+ schema_migration = ActiveRecord::Base.connection.schema_migration
calls, migrator = migrator_class(3)
- migrator = migrator.new("valid")
+ migrator = migrator.new("valid", schema_migration)
migrator.migrate
assert_equal [[:up, 2], [:up, 3]], calls
end
def test_get_all_versions
+ schema_migration = ActiveRecord::Base.connection.schema_migration
_, migrator = migrator_class(3)
- migrator = migrator.new("valid")
+ migrator = migrator.new("valid", schema_migration)
migrator.migrate
assert_equal([1, 2, 3], migrator.get_all_versions)
diff --git a/activerecord/test/cases/multi_db_migrator_test.rb b/activerecord/test/cases/multi_db_migrator_test.rb
new file mode 100644
index 0000000000..650b3af6f0
--- /dev/null
+++ b/activerecord/test/cases/multi_db_migrator_test.rb
@@ -0,0 +1,218 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "cases/migration/helper"
+
+class MultiDbMigratorTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ # Use this class to sense if migrations have gone
+ # up or down.
+ class Sensor < ActiveRecord::Migration::Current
+ attr_reader :went_up, :went_down
+
+ def initialize(name = self.class.name, version = nil)
+ super
+ @went_up = false
+ @went_down = false
+ end
+
+ def up; @went_up = true; end
+ def down; @went_down = true; end
+ end
+
+ def setup
+ super
+ @connection_a = ActiveRecord::Base.connection
+ @connection_b = ARUnit2Model.connection
+
+ @connection_a.schema_migration.create_table
+ @connection_b.schema_migration.create_table
+
+ @connection_a.schema_migration.delete_all rescue nil
+ @connection_b.schema_migration.delete_all rescue nil
+
+ @path_a = MIGRATIONS_ROOT + "/valid"
+ @path_b = MIGRATIONS_ROOT + "/to_copy"
+
+ @schema_migration_a = @connection_a.schema_migration
+ @migrations_a = ActiveRecord::MigrationContext.new(@path_a, @schema_migration_a).migrations
+ @schema_migration_b = @connection_b.schema_migration
+ @migrations_b = ActiveRecord::MigrationContext.new(@path_b, @schema_migration_b).migrations
+
+ @migrations_a_list = [[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]]
+ @migrations_b_list = [[1, "PeopleHaveHobbies"], [2, "PeopleHaveDescriptions"]]
+
+ @verbose_was = ActiveRecord::Migration.verbose
+
+ ActiveRecord::Migration.message_count = 0
+ ActiveRecord::Migration.class_eval do
+ undef :puts
+ def puts(*)
+ ActiveRecord::Migration.message_count += 1
+ end
+ end
+ end
+
+ teardown do
+ @connection_a.schema_migration.delete_all rescue nil
+ @connection_b.schema_migration.delete_all rescue nil
+
+ ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::Migration.class_eval do
+ undef :puts
+ def puts(*)
+ super
+ end
+ end
+ end
+
+ def test_finds_migrations
+ @migrations_a_list.each_with_index do |pair, i|
+ assert_equal @migrations_a[i].version, pair.first
+ assert_equal @migrations_a[i].name, pair.last
+ end
+
+ @migrations_b_list.each_with_index do |pair, i|
+ assert_equal @migrations_b[i].version, pair.first
+ assert_equal @migrations_b[i].name, pair.last
+ end
+ end
+
+ def test_migrations_status
+ @schema_migration_a.create(version: 2)
+ @schema_migration_a.create(version: 10)
+
+ assert_equal [
+ ["down", "001", "Valid people have last names"],
+ ["up", "002", "We need reminders"],
+ ["down", "003", "Innocent jointable"],
+ ["up", "010", "********** NO FILE **********"],
+ ], ActiveRecord::MigrationContext.new(@path_a, @schema_migration_a).migrations_status
+
+ @schema_migration_b.create(version: 4)
+
+ assert_equal [
+ ["down", "001", "People have hobbies"],
+ ["down", "002", "People have descriptions"],
+ ["up", "004", "********** NO FILE **********"]
+ ], ActiveRecord::MigrationContext.new(@path_b, @schema_migration_b).migrations_status
+ end
+
+ def test_get_all_versions
+ _, migrator_a = migrator_class(3)
+ migrator_a = migrator_a.new(@path_a, @schema_migration_a)
+
+ migrator_a.migrate
+ assert_equal([1, 2, 3], migrator_a.get_all_versions)
+
+ migrator_a.rollback
+ assert_equal([1, 2], migrator_a.get_all_versions)
+
+ migrator_a.rollback
+ assert_equal([1], migrator_a.get_all_versions)
+
+ migrator_a.rollback
+ assert_equal([], migrator_a.get_all_versions)
+
+ _, migrator_b = migrator_class(2)
+ migrator_b = migrator_b.new(@path_b, @schema_migration_b)
+
+ migrator_b.migrate
+ assert_equal([1, 2], migrator_b.get_all_versions)
+
+ migrator_b.rollback
+ assert_equal([1], migrator_b.get_all_versions)
+
+ migrator_b.rollback
+ assert_equal([], migrator_b.get_all_versions)
+ end
+
+ def test_finds_pending_migrations
+ @schema_migration_a.create!(version: "1")
+ migration_list_a = [ActiveRecord::Migration.new("foo", 1), ActiveRecord::Migration.new("bar", 3)]
+ migrations_a = ActiveRecord::Migrator.new(:up, migration_list_a, @schema_migration_a).pending_migrations
+
+ assert_equal 1, migrations_a.size
+ assert_equal migration_list_a.last, migrations_a.first
+
+ @schema_migration_b.create!(version: "1")
+ migration_list_b = [ActiveRecord::Migration.new("foo", 1), ActiveRecord::Migration.new("bar", 3)]
+ migrations_b = ActiveRecord::Migrator.new(:up, migration_list_b, @schema_migration_b).pending_migrations
+
+ assert_equal 1, migrations_b.size
+ assert_equal migration_list_b.last, migrations_b.first
+ end
+
+ def test_migrator_db_has_no_schema_migrations_table
+ _, migrator = migrator_class(3)
+ migrator = migrator.new(@path_a, @schema_migration_a)
+
+ @schema_migration_a.drop_table
+ assert_not @connection_a.table_exists?("schema_migrations")
+ migrator.migrate(1)
+ assert @connection_a.table_exists?("schema_migrations")
+
+ _, migrator = migrator_class(3)
+ migrator = migrator.new(@path_b, @schema_migration_b)
+
+ @schema_migration_b.drop_table
+ assert_not @connection_b.table_exists?("schema_migrations")
+ migrator.migrate(1)
+ assert @connection_b.table_exists?("schema_migrations")
+ end
+
+ def test_migrator_forward
+ _, migrator = migrator_class(3)
+ migrator = migrator.new(@path_a, @schema_migration_a)
+ migrator.migrate(1)
+ assert_equal(1, migrator.current_version)
+
+ migrator.forward(2)
+ assert_equal(3, migrator.current_version)
+
+ migrator.forward
+ assert_equal(3, migrator.current_version)
+
+ _, migrator_b = migrator_class(3)
+ migrator_b = migrator_b.new(@path_b, @schema_migration_b)
+ migrator_b.migrate(1)
+ assert_equal(1, migrator_b.current_version)
+
+ migrator_b.forward(2)
+ assert_equal(3, migrator_b.current_version)
+
+ migrator_b.forward
+ assert_equal(3, migrator_b.current_version)
+ end
+
+ private
+ def m(name, version)
+ x = Sensor.new name, version
+ x.extend(Module.new {
+ define_method(:up) { yield(:up, x); super() }
+ define_method(:down) { yield(:down, x); super() }
+ }) if block_given?
+ end
+
+ def sensors(count)
+ calls = []
+ migrations = count.times.map { |i|
+ m(nil, i + 1) { |c, migration|
+ calls << [c, migration.version]
+ }
+ }
+ [calls, migrations]
+ end
+
+ def migrator_class(count)
+ calls, migrations = sensors(count)
+
+ migrator = Class.new(ActiveRecord::MigrationContext) {
+ define_method(:migrations) { |*|
+ migrations
+ }
+ }
+ [calls, migrator]
+ end
+end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index bb1c1ea17d..b49e62bee6 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -851,7 +851,6 @@ module NestedAttributesOnACollectionAssociationTests
end
private
-
def association_setter
@association_setter ||= "#{@association_name}_attributes=".to_sym
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index d5057ad381..7b7aa7e9b7 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -183,7 +183,7 @@ class PersistenceTest < ActiveRecord::TestCase
assert_not_predicate company, :valid?
original_errors = company.errors
client = company.becomes(Client)
- assert_equal original_errors.keys, client.errors.keys
+ assert_equal assert_deprecated { original_errors.keys }, assert_deprecated { client.errors.keys }
end
def test_becomes_errors_base
@@ -197,7 +197,7 @@ class PersistenceTest < ActiveRecord::TestCase
admin.errors.add :token, :invalid
child = admin.becomes(child_class)
- assert_equal [:token], child.errors.keys
+ assert_equal [:token], assert_deprecated { child.errors.keys }
assert_nothing_raised do
child.errors.add :foo, :invalid
end
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index 080aeb0989..d783b2945d 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -72,7 +72,6 @@ class PooledConnectionsTest < ActiveRecord::TestCase
end
private
-
def add_record(name)
ActiveRecord::Base.connection_pool.with_connection { Project.create! name: name }
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 4759d3b6b2..511d7fc982 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -203,6 +203,14 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_queries(3, ignore_none: true) { klass.create! }
end
+ def test_assign_id_raises_error_if_primary_key_doesnt_exist
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "dashboards"
+ end
+ dashboard = klass.new
+ assert_raises(ActiveModel::MissingAttributeError) { dashboard.id = "1" }
+ end
+
if current_adapter?(:PostgreSQLAdapter)
def test_serial_with_quoted_sequence_name
column = MixedCaseMonkey.columns_hash[MixedCaseMonkey.primary_key]
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index eb32b690aa..79bd6906d1 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -335,11 +335,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_does_not_wrap_results_in_arrays
Task.cache do
- if current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter)
- assert_equal 2, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
- else
- assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
- end
+ assert_equal 2, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
end
end
@@ -540,8 +536,24 @@ class QueryCacheTest < ActiveRecord::TestCase
ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
end
- private
+ test "query cache is enabled in threads with shared connection" do
+ ActiveRecord::Base.connection_pool.lock_thread = true
+
+ assert_cache :off
+ thread_a = Thread.new do
+ middleware { |env|
+ assert_cache :clean
+ [200, {}, nil]
+ }.call({})
+ end
+
+ thread_a.join
+
+ ActiveRecord::Base.connection_pool.lock_thread = false
+ 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
diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb
index 0b06cec40b..35db3d1175 100644
--- a/activerecord/test/cases/relation/where_clause_test.rb
+++ b/activerecord/test/cases/relation/where_clause_test.rb
@@ -106,7 +106,7 @@ class ActiveRecord::Relation
Arel::Nodes::Not.new(random_object)
])
- assert_equal expected, original.invert
+ assert_equal expected, original.invert(:nor)
end
test "except removes binary predicates referencing a given column" do
@@ -233,7 +233,6 @@ class ActiveRecord::Relation
end
private
-
def table
Arel::Table.new("table")
end
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index b045184d7d..aad30ddea0 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -18,7 +18,19 @@ require "support/stubs/strong_parameters"
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
- fixtures :posts, :edges, :authors, :author_addresses, :binaries, :essays, :cars, :treasures, :price_estimates, :topics
+ fixtures :posts, :comments, :edges, :authors, :author_addresses, :binaries, :essays, :cars, :treasures, :price_estimates, :topics
+
+ def test_in_clause_is_correctly_sliced
+ assert_called(Author.connection, :in_clause_length, returns: 1) do
+ david = authors(:david)
+ assert_equal [david], Author.where(name: "David", id: [1, 2])
+ end
+ end
+
+ def test_type_casting_nested_joins
+ comment = comments(:eager_other_comment1)
+ assert_equal [comment], Comment.joins(post: :author).where(authors: { id: "2-foo" })
+ end
def test_where_copies_bind_params
author = authors(:david)
@@ -115,13 +127,58 @@ module ActiveRecord
assert_equal expected.to_sql, actual.to_sql
end
- def test_polymorphic_shallow_where_not
- treasure = treasures(:sapphire)
+ def test_where_not_polymorphic_association
+ sapphire = treasures(:sapphire)
- expected = [price_estimates(:diamond), price_estimates(:honda)]
- actual = PriceEstimate.where.not(estimate_of: treasure)
+ all = [treasures(:diamond), sapphire, cars(:honda), sapphire]
+ assert_equal all, PriceEstimate.all.sort_by(&:id).map(&:estimate_of)
- assert_equal expected.sort_by(&:id), actual.sort_by(&:id)
+ actual = PriceEstimate.where.not(estimate_of: sapphire)
+ only = PriceEstimate.where(estimate_of: sapphire)
+
+ expected = all - [sapphire]
+ assert_equal expected, actual.sort_by(&:id).map(&:estimate_of)
+ assert_equal all - expected, only.sort_by(&:id).map(&:estimate_of)
+ end
+
+ def test_where_not_polymorphic_id_and_type_as_nand
+ sapphire = treasures(:sapphire)
+
+ all = [treasures(:diamond), sapphire, cars(:honda), sapphire]
+ assert_equal all, PriceEstimate.all.sort_by(&:id).map(&:estimate_of)
+
+ actual = PriceEstimate.where.yield_self do |where_chain|
+ where_chain.stub(:not_behaves_as_nor?, false) do
+ where_chain.not(estimate_of_type: sapphire.class.polymorphic_name, estimate_of_id: sapphire.id)
+ end
+ end
+ only = PriceEstimate.where(estimate_of_type: sapphire.class.polymorphic_name, estimate_of_id: sapphire.id)
+
+ expected = all - [sapphire]
+ assert_equal expected, actual.sort_by(&:id).map(&:estimate_of)
+ assert_equal all - expected, only.sort_by(&:id).map(&:estimate_of)
+ end
+
+ def test_where_not_polymorphic_id_and_type_as_nor_is_deprecated
+ sapphire = treasures(:sapphire)
+
+ all = [treasures(:diamond), sapphire, cars(:honda), sapphire]
+ assert_equal all, PriceEstimate.all.sort_by(&:id).map(&:estimate_of)
+
+ message = <<~MSG.squish
+ NOT conditions will no longer behave as NOR in Rails 6.1.
+ To continue using NOR conditions, NOT each conditions manually
+ (`.where.not(:estimate_of_type => ...).where.not(:estimate_of_id => ...)`).
+ MSG
+ actual = assert_deprecated(message) do
+ PriceEstimate.where.not(estimate_of_type: sapphire.class.polymorphic_name, estimate_of_id: sapphire.id)
+ end
+ only = PriceEstimate.where(estimate_of_type: sapphire.class.polymorphic_name, estimate_of_id: sapphire.id)
+
+ expected = all - [sapphire]
+ # NOT (estimate_of_type = 'Treasure' OR estimate_of_id = sapphire.id) matches only `cars(:honda)` unfortunately.
+ assert_not_equal expected, actual.sort_by(&:id).map(&:estimate_of)
+ assert_equal all - expected, only.sort_by(&:id).map(&:estimate_of)
end
def test_polymorphic_nested_array_where
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 3f370e5ede..e74fb1a098 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -363,6 +363,13 @@ module ActiveRecord
assert_match %r{/\*\+ BADHINT \*/}, post_with_hint.to_sql
end
+ def test_does_not_duplicate_optimizer_hints_on_merge
+ escaped_table = Post.connection.quote_table_name("posts")
+ expected = "SELECT /*+ OMGHINT */ #{escaped_table}.* FROM #{escaped_table}"
+ query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql
+ assert_equal expected, query
+ end
+
class EnsureRoundTripTypeCasting < ActiveRecord::Type::Value
def type
:string
@@ -405,7 +412,6 @@ module ActiveRecord
end
private
-
def skip_if_sqlite3_version_includes_quoting_bug
if sqlite3_version_includes_quoting_bug?
skip <<-ERROR.squish
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 2417775ef1..1a20fe5dc2 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -298,7 +298,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_reverse_order_with_function
- topics = Topic.order(Arel.sql("length(title)")).reverse_order
+ topics = Topic.order("length(title)").reverse_order
assert_equal topics(:second).title, topics.first.title
end
@@ -308,9 +308,9 @@ class RelationTest < ActiveRecord::TestCase
end
def test_reverse_order_with_function_other_predicates
- topics = Topic.order(Arel.sql("author_name, length(title), id")).reverse_order
+ topics = Topic.order("author_name, length(title), id").reverse_order
assert_equal topics(:second).title, topics.first.title
- topics = Topic.order(Arel.sql("length(author_name), id, length(title)")).reverse_order
+ topics = Topic.order("length(author_name), id, length(title)").reverse_order
assert_equal topics(:fifth).title, topics.first.title
end
@@ -337,21 +337,21 @@ class RelationTest < ActiveRecord::TestCase
def test_reverse_order_with_nulls_first_or_last
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order(Arel.sql("title NULLS FIRST")).reverse_order
+ Topic.order("title NULLS FIRST").reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order(Arel.sql("title NULLS FIRST")).reverse_order
+ Topic.order("title NULLS FIRST").reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order(Arel.sql("title nulls last")).reverse_order
+ Topic.order("title nulls last").reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order(Arel.sql("title NULLS FIRST, author_name")).reverse_order
+ Topic.order("title NULLS FIRST, author_name").reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order(Arel.sql("author_name, title nulls last")).reverse_order
+ Topic.order("author_name, title nulls last").reverse_order
end
- end
+ end if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
def test_default_reverse_order_on_table_without_primary_key
assert_raises(ActiveRecord::IrreversibleOrderError) do
@@ -706,7 +706,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_to_sql_on_eager_join
- expected = assert_sql {
+ expected = capture_sql {
Post.eager_load(:last_comment).order("comments.id DESC").to_a
}.first
actual = Post.eager_load(:last_comment).order("comments.id DESC").to_sql
@@ -1679,7 +1679,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.order("comments.body")
assert_equal ["comments"], scope.references_values
- scope = Post.order(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}"))
+ scope = Post.order("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")
if current_adapter?(:OracleAdapter)
assert_equal ["COMMENTS"], scope.references_values
else
@@ -1696,7 +1696,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.order("comments.body asc")
assert_equal ["comments"], scope.references_values
- scope = Post.order(Arel.sql("foo(comments.body)"))
+ scope = Post.order("foo(comments.body)")
assert_equal [], scope.references_values
end
@@ -1704,7 +1704,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.reorder("comments.body")
assert_equal %w(comments), scope.references_values
- scope = Post.reorder(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}"))
+ scope = Post.reorder("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")
if current_adapter?(:OracleAdapter)
assert_equal ["COMMENTS"], scope.references_values
else
@@ -1721,7 +1721,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.reorder("comments.body asc")
assert_equal %w(comments), scope.references_values
- scope = Post.reorder(Arel.sql("foo(comments.body)"))
+ scope = Post.reorder("foo(comments.body)")
assert_equal [], scope.references_values
end
@@ -1955,8 +1955,8 @@ class RelationTest < ActiveRecord::TestCase
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)
+ assert_equal companies, Company.joins(:contracts).order(:metadata, :count)
+ assert_equal companies.reverse, Company.joins(:contracts).order(metadata: :desc, count: :desc)
end
test "delegations do not leak to other classes" do
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 49e9be9565..bb7184c5fc 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -33,6 +33,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
schema_info = ActiveRecord::Base.connection.dump_schema_information
assert_match(/20100201010101.*20100301010101/m, schema_info)
+ assert_includes schema_info, "20100101010101"
ensure
ActiveRecord::SchemaMigration.delete_all
end
diff --git a/activerecord/test/cases/schema_loading_test.rb b/activerecord/test/cases/schema_loading_test.rb
index f539156466..5da2d9e08f 100644
--- a/activerecord/test/cases/schema_loading_test.rb
+++ b/activerecord/test/cases/schema_loading_test.rb
@@ -43,7 +43,6 @@ class SchemaLoadingTest < ActiveRecord::TestCase
end
private
-
def define_model
Class.new(ActiveRecord::Base) do
include SchemaLoadCounter
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index dd4a0b0455..6b6861465b 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -760,7 +760,7 @@ module ActiveRecord
end
class DatabaseTasksMigrateTest < DatabaseTasksMigrationTestCase
- def test_migrate_set_and_unset_verbose_and_version_env_vars
+ def test_can_migrate_from_pending_migration_error_action_dispatch
verbose, version = ENV["VERBOSE"], ENV["VERSION"]
ENV["VERSION"] = "2"
ENV["VERBOSE"] = "false"
@@ -772,7 +772,9 @@ module ActiveRecord
ENV.delete("VERBOSE")
# re-run up migration
- assert_includes capture_migration_output, "migrating"
+ assert_includes(capture(:stdout) do
+ ActiveSupport::ActionableError.dispatch ActiveRecord::PendingMigrationError, "Run pending migrations"
+ end, "migrating")
ensure
ENV["VERBOSE"], ENV["VERSION"] = verbose, version
end
@@ -833,7 +835,6 @@ module ActiveRecord
end
private
-
def capture_migration_status
capture(:stdout) do
ActiveRecord::Tasks::DatabaseTasks.migrate_status
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 552e623fd4..ac3c5bc26e 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -7,7 +7,10 @@ if current_adapter?(:Mysql2Adapter)
module ActiveRecord
class MysqlDBCreateTest < ActiveRecord::TestCase
def setup
- @connection = Class.new { def create_database(*); end }.new
+ @connection = Class.new do
+ def create_database(*); end
+ def error_number(_); end
+ end.new
@configuration = {
"adapter" => "mysql2",
"database" => "my-app-db"
@@ -90,9 +93,11 @@ if current_adapter?(:Mysql2Adapter)
with_stubbed_connection_establish_connection do
ActiveRecord::Base.connection.stub(
:create_database,
- proc { raise ActiveRecord::Tasks::DatabaseAlreadyExists }
+ proc { raise ActiveRecord::StatementInvalid }
) do
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ ActiveRecord::Base.connection.stub(:error_number, 1007) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
assert_equal "Database 'my-app-db' already exists\n", $stderr.string
end
@@ -100,7 +105,6 @@ if current_adapter?(:Mysql2Adapter)
end
private
-
def with_stubbed_connection_establish_connection
ActiveRecord::Base.stub(:establish_connection, nil) do
ActiveRecord::Base.stub(:connection, @connection) do
@@ -180,7 +184,6 @@ if current_adapter?(:Mysql2Adapter)
end
private
-
def with_stubbed_connection_establish_connection
ActiveRecord::Base.stub(:establish_connection, nil) do
ActiveRecord::Base.stub(:connection, @connection) do
@@ -233,7 +236,6 @@ if current_adapter?(:Mysql2Adapter)
end
private
-
def with_stubbed_connection_establish_connection
ActiveRecord::Base.stub(:establish_connection, nil) do
ActiveRecord::Base.stub(:connection, @connection) do
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index 065ba7734c..f9df650687 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -139,7 +139,6 @@ if current_adapter?(:PostgreSQLAdapter)
end
private
-
def with_stubbed_connection_establish_connection
ActiveRecord::Base.stub(:connection, @connection) do
ActiveRecord::Base.stub(:establish_connection, nil) do
@@ -201,7 +200,6 @@ if current_adapter?(:PostgreSQLAdapter)
end
private
-
def with_stubbed_connection_establish_connection
ActiveRecord::Base.stub(:connection, @connection) do
ActiveRecord::Base.stub(:establish_connection, nil) do
@@ -301,7 +299,6 @@ if current_adapter?(:PostgreSQLAdapter)
end
private
-
def with_stubbed_connection
ActiveRecord::Base.stub(:connection, @connection) do
yield
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 5b25432dc0..1b8bad32a4 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -34,7 +34,7 @@ module ActiveRecord
ActiveRecord::Base.connection.materialize_transactions
SQLCounter.clear_log
yield
- SQLCounter.log_all.dup
+ SQLCounter.log.dup
end
def assert_sql(*patterns_to_match)
@@ -107,32 +107,12 @@ module ActiveRecord
clear_log
- self.ignored_sql = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
-
- # FIXME: this needs to be refactored so specific database can add their own
- # ignored SQL, or better yet, use a different notification for the queries
- # instead examining the SQL content.
- oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im, /^\s*select .* from all_sequences/im]
- mysql_ignored = [/^SHOW FULL TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /, /^\s*SELECT (?:column_name|table_name)\b.*\bFROM information_schema\.(?:key_column_usage|tables)\b/im]
- postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i, /^\s*SELECT\b.*::regtype::oid\b/im]
- sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im]
-
- [oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql|
- ignored_sql.concat db_ignored_sql
- end
-
- attr_reader :ignore
-
- def initialize(ignore = Regexp.union(self.class.ignored_sql))
- @ignore = ignore
- end
-
def call(name, start, finish, message_id, values)
return if values[:cached]
sql = values[:sql]
self.class.log_all << sql
- self.class.log << sql unless ignore.match?(sql)
+ self.class.log << sql unless ["SCHEMA", "TRANSACTION"].include? values[:name]
end
end
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 53fe31e087..19b89ab08c 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -36,6 +36,8 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id"
+ before_destroy { self.class.find(id).touch if persisted? }
+
before_commit { |record| record.do_before_commit(nil) }
after_commit { |record| record.do_after_commit(nil) }
after_save_commit { |record| record.do_after_commit(:save) }
@@ -458,7 +460,6 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
private
-
def add_transaction_execution_blocks(record)
record.after_commit_block(:create) { |r| r.history << :commit_on_create }
record.after_commit_block(:update) { |r| r.history << :commit_on_update }
@@ -549,6 +550,8 @@ class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase
end
class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
class TopicWithHistory < ActiveRecord::Base
self.table_name = :topics
@@ -562,11 +565,22 @@ class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase
end
class TopicWithCallbacksOnDestroy < TopicWithHistory
- after_commit(on: :destroy) { |record| record.class.history << :destroy }
+ after_commit(on: :destroy) { |record| record.class.history << :commit_on_destroy }
+ after_rollback(on: :destroy) { |record| record.class.history << :rollback_on_destroy }
+
+ before_destroy :before_destroy_for_transaction
+
+ private
+ def before_destroy_for_transaction; end
end
class TopicWithCallbacksOnUpdate < TopicWithHistory
- after_commit(on: :update) { |record| record.class.history << :update }
+ after_commit(on: :update) { |record| record.class.history << :commit_on_update }
+
+ before_save :before_save_for_transaction
+
+ private
+ def before_save_for_transaction; end
end
def test_trigger_once_on_multiple_deletions
@@ -574,10 +588,39 @@ class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase
topic = TopicWithCallbacksOnDestroy.new
topic.save
topic_clone = TopicWithCallbacksOnDestroy.find(topic.id)
+
+ topic.define_singleton_method(:before_destroy_for_transaction) do
+ topic_clone.destroy
+ end
+
topic.destroy
- topic_clone.destroy
- assert_equal [:destroy], TopicWithCallbacksOnDestroy.history
+ assert_equal [:commit_on_destroy], TopicWithCallbacksOnDestroy.history
+ end
+
+ def test_rollback_on_multiple_deletions
+ TopicWithCallbacksOnDestroy.clear_history
+ topic = TopicWithCallbacksOnDestroy.new
+ topic.save
+ topic_clone = TopicWithCallbacksOnDestroy.find(topic.id)
+
+ topic.define_singleton_method(:before_destroy_for_transaction) do
+ topic_clone.update!(author_name: "Test Author Clone")
+ topic_clone.destroy
+ end
+
+ TopicWithCallbacksOnDestroy.transaction do
+ topic.update!(author_name: "Test Author")
+ topic.destroy
+ raise ActiveRecord::Rollback
+ end
+
+ assert_not_predicate topic, :destroyed?
+ assert_not_predicate topic_clone, :destroyed?
+ assert_equal [nil, "Test Author"], topic.author_name_change_to_be_saved
+ assert_equal [nil, "Test Author Clone"], topic_clone.author_name_change_to_be_saved
+
+ assert_equal [:rollback_on_destroy], TopicWithCallbacksOnDestroy.history
end
def test_trigger_on_update_where_row_was_deleted
@@ -585,7 +628,11 @@ class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase
topic = TopicWithCallbacksOnUpdate.new
topic.save
topic_clone = TopicWithCallbacksOnUpdate.find(topic.id)
- topic.destroy
+
+ topic_clone.define_singleton_method(:before_save_for_transaction) do
+ topic.destroy
+ end
+
topic_clone.author_name = "Test Author"
topic_clone.save
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 7bad3de343..b5c1cac3d9 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -18,6 +18,65 @@ class TransactionTest < ActiveRecord::TestCase
@first, @second = Topic.find(1, 2).sort_by(&:id)
end
+ def test_rollback_dirty_changes
+ topic = topics(:fifth)
+
+ ActiveRecord::Base.transaction do
+ topic.update(title: "Ruby on Rails")
+ raise ActiveRecord::Rollback
+ end
+
+ title_change = ["The Fifth Topic of the day", "Ruby on Rails"]
+ assert_equal title_change, topic.changes["title"]
+ end
+
+ def test_rollback_dirty_changes_multiple_saves
+ topic = topics(:fifth)
+
+ ActiveRecord::Base.transaction do
+ topic.update(title: "Ruby on Rails")
+ topic.update(title: "Another Title")
+ raise ActiveRecord::Rollback
+ end
+
+ title_change = ["The Fifth Topic of the day", "Another Title"]
+ assert_equal title_change, topic.changes["title"]
+ end
+
+ def test_rollback_dirty_changes_then_retry_save
+ topic = topics(:fifth)
+
+ ActiveRecord::Base.transaction do
+ topic.update(title: "Ruby on Rails")
+ raise ActiveRecord::Rollback
+ end
+
+ title_change = ["The Fifth Topic of the day", "Ruby on Rails"]
+ assert_equal title_change, topic.changes["title"]
+
+ assert topic.save
+
+ assert_equal title_change, topic.saved_changes["title"]
+ assert_equal topic.title, topic.reload.title
+ end
+
+ def test_rollback_dirty_changes_then_retry_save_on_new_record
+ topic = Topic.new(title: "Ruby on Rails")
+
+ ActiveRecord::Base.transaction do
+ topic.save
+ raise ActiveRecord::Rollback
+ end
+
+ title_change = [nil, "Ruby on Rails"]
+ assert_equal title_change, topic.changes["title"]
+
+ assert topic.save
+
+ assert_equal title_change, topic.saved_changes["title"]
+ assert_equal topic.title, topic.reload.title
+ end
+
def test_persisted_in_a_model_with_custom_primary_key_after_failed_save
movie = Movie.create
assert_not_predicate movie, :persisted?
@@ -1018,7 +1077,6 @@ class TransactionTest < ActiveRecord::TestCase
end
private
-
%w(validation save destroy).each do |filter|
define_method("add_cancelling_before_#{filter}_with_db_side_effect_to_topic") do |topic|
meta = class << topic; self; end
diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb
index d5d8f2a09a..87edb163f2 100644
--- a/activerecord/test/cases/unsafe_raw_sql_test.rb
+++ b/activerecord/test/cases/unsafe_raw_sql_test.rb
@@ -77,7 +77,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
assert_equal ids_expected, ids_disabled
end
- test "order: allows table and column name" do
+ test "order: allows table and column names" do
ids_expected = Post.order(Arel.sql("title")).pluck(:id)
ids_depr = with_unsafe_raw_sql_deprecated { Post.order("posts.title").pluck(:id) }
@@ -87,6 +87,17 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
assert_equal ids_expected, ids_disabled
end
+ test "order: allows quoted table and column names" do
+ ids_expected = Post.order(Arel.sql("title")).pluck(:id)
+
+ quoted_title = Post.connection.quote_table_name("posts.title")
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(quoted_title).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(quoted_title).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
test "order: allows column name and direction in string" do
ids_expected = Post.order(Arel.sql("title desc")).pluck(:id)
@@ -116,10 +127,10 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
["asc", "desc", ""].each do |direction|
%w(first last).each do |position|
- ids_expected = Post.order(Arel.sql("type #{direction} nulls #{position}")).pluck(:id)
+ ids_expected = Post.order(Arel.sql("type::text #{direction} nulls #{position}")).pluck(:id)
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order("type #{direction} nulls #{position}").pluck(:id) }
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order("type #{direction} nulls #{position}").pluck(:id) }
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("type::text #{direction} nulls #{position}").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("type::text #{direction} nulls #{position}").pluck(:id) }
assert_equal ids_expected, ids_depr
assert_equal ids_expected, ids_disabled
@@ -130,7 +141,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
test "order: disallows invalid column name" do
with_unsafe_raw_sql_disabled do
assert_raises(ActiveRecord::UnknownAttributeReference) do
- Post.order("len(title) asc").pluck(:id)
+ Post.order("REPLACE(title, 'misc', 'zzzz') asc").pluck(:id)
end
end
end
@@ -146,7 +157,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
test "order: disallows invalid column with direction" do
with_unsafe_raw_sql_disabled do
assert_raises(ActiveRecord::UnknownAttributeReference) do
- Post.order("len(title)" => :asc).pluck(:id)
+ Post.order("REPLACE(title, 'misc', 'zzzz')" => :asc).pluck(:id)
end
end
end
@@ -179,7 +190,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
test "order: disallows invalid Array arguments" do
with_unsafe_raw_sql_disabled do
assert_raises(ActiveRecord::UnknownAttributeReference) do
- Post.order(["author_id", "length(title)"]).pluck(:id)
+ Post.order(["author_id", "REPLACE(title, 'misc', 'zzzz')"]).pluck(:id)
end
end
end
@@ -187,8 +198,8 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
test "order: allows valid Array arguments" do
ids_expected = Post.order(Arel.sql("author_id, length(title)")).pluck(:id)
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", Arel.sql("length(title)")]).pluck(:id) }
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", Arel.sql("length(title)")]).pluck(:id) }
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", "length(title)"]).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", "length(title)"]).pluck(:id) }
assert_equal ids_expected, ids_depr
assert_equal ids_expected, ids_disabled
@@ -197,7 +208,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
test "order: logs deprecation warning for unrecognized column" do
with_unsafe_raw_sql_deprecated do
assert_deprecated(/Dangerous query method/) do
- Post.order("length(title)")
+ Post.order("REPLACE(title, 'misc', 'zzzz')")
end
end
end
@@ -212,6 +223,16 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
assert_equal titles_expected, titles_disabled
end
+ test "pluck: allows string column name with function and alias" do
+ titles_expected = Post.pluck(Arel.sql("UPPER(title)"))
+
+ titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("UPPER(title) AS title") }
+ titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("UPPER(title) AS title") }
+
+ assert_equal titles_expected, titles_depr
+ assert_equal titles_expected, titles_disabled
+ end
+
test "pluck: allows symbol column name" do
titles_expected = Post.pluck(Arel.sql("title"))
@@ -262,10 +283,21 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
assert_equal titles_expected, titles_disabled
end
+ test "pluck: allows quoted table and column names" do
+ titles_expected = Post.pluck(Arel.sql("title"))
+
+ quoted_title = Post.connection.quote_table_name("posts.title")
+ titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck(quoted_title) }
+ titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck(quoted_title) }
+
+ assert_equal titles_expected, titles_depr
+ assert_equal titles_expected, titles_disabled
+ end
+
test "pluck: disallows invalid column name" do
with_unsafe_raw_sql_disabled do
assert_raises(ActiveRecord::UnknownAttributeReference) do
- Post.pluck("length(title)")
+ Post.pluck("REPLACE(title, 'misc', 'zzzz')")
end
end
end
@@ -273,7 +305,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
test "pluck: disallows invalid column name amongst valid names" do
with_unsafe_raw_sql_disabled do
assert_raises(ActiveRecord::UnknownAttributeReference) do
- Post.pluck(:title, "length(title)")
+ Post.pluck(:title, "REPLACE(title, 'misc', 'zzzz')")
end
end
end
@@ -281,7 +313,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
test "pluck: disallows invalid column names with includes" do
with_unsafe_raw_sql_disabled do
assert_raises(ActiveRecord::UnknownAttributeReference) do
- Post.includes(:comments).pluck(:title, "length(title)")
+ Post.includes(:comments).pluck(:title, "REPLACE(title, 'misc', 'zzzz')")
end
end
end
@@ -296,24 +328,25 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
test "pluck: logs deprecation warning" do
with_unsafe_raw_sql_deprecated do
assert_deprecated(/Dangerous query method/) do
- Post.includes(:comments).pluck(:title, "length(title)")
+ Post.includes(:comments).pluck(:title, "REPLACE(title, 'misc', 'zzzz')")
end
end
end
- def with_unsafe_raw_sql_disabled(&blk)
- with_config(:disabled, &blk)
- end
+ private
+ def with_unsafe_raw_sql_disabled(&block)
+ with_config(:disabled, &block)
+ end
- def with_unsafe_raw_sql_deprecated(&blk)
- with_config(:deprecated, &blk)
- end
+ def with_unsafe_raw_sql_deprecated(&block)
+ with_config(:deprecated, &block)
+ end
- def with_config(new_value, &blk)
- old_value = ActiveRecord::Base.allow_unsafe_raw_sql
- ActiveRecord::Base.allow_unsafe_raw_sql = new_value
- blk.call
- ensure
- ActiveRecord::Base.allow_unsafe_raw_sql = old_value
- end
+ def with_config(new_value, &block)
+ old_value = ActiveRecord::Base.allow_unsafe_raw_sql
+ ActiveRecord::Base.allow_unsafe_raw_sql = new_value
+ yield
+ ensure
+ ActiveRecord::Base.allow_unsafe_raw_sql = old_value
+ end
end
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index b7c52ea18c..4dd8a4a82b 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -40,19 +40,20 @@ class I18nValidationTest < ActiveRecord::TestCase
COMMON_CASES = [
# [ case, validation_options, generate_message_options]
[ "given no options", {}, {}],
- [ "given custom message", { message: "custom" }, { message: "custom" }],
- [ "given if condition", { if: lambda { true } }, {}],
- [ "given unless condition", { unless: lambda { false } }, {}],
- [ "given option that is not reserved", { format: "jpg" }, { format: "jpg" }],
- [ "given on condition", { on: [:create, :update] }, {}]
+ [ "given custom message", { message: "custom" }, { message: "custom" }],
+ [ "given if condition", { if: lambda { true } }, {}],
+ [ "given unless condition", { unless: lambda { false } }, {}],
+ [ "given option that is not reserved", { format: "jpg" }, { format: "jpg" }],
+ [ "given on condition", { on: [:create, :update] }, {}]
]
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_uniqueness_of on generated message #{name}" do
Topic.validates_uniqueness_of :title, validation_options
@topic.title = unique_topic.title
- assert_called_with(@topic.errors, :generate_message, [:title, :taken, generate_message_options.merge(value: "unique!")]) do
+ assert_called_with(ActiveModel::Error, :generate_message, [:title, :taken, @topic, generate_message_options.merge(value: "unique!")]) do
@topic.valid?
+ @topic.errors.messages
end
end
end
@@ -60,8 +61,9 @@ class I18nValidationTest < ActiveRecord::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_associated on generated message #{name}" do
Topic.validates_associated :replies, validation_options
- assert_called_with(replied_topic.errors, :generate_message, [:replies, :invalid, generate_message_options.merge(value: replied_topic.replies)]) do
+ assert_called_with(ActiveModel::Error, :generate_message, [:replies, :invalid, replied_topic, generate_message_options.merge(value: replied_topic.replies)]) do
replied_topic.save
+ replied_topic.errors.messages
end
end
end
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 60ebdce178..7003afa33a 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -130,7 +130,6 @@ class YamlSerializationTest < ActiveRecord::TestCase
end
private
-
def yaml_fixture(file_name)
path = File.expand_path(
"../support/yaml_compatibility_fixtures/#{file_name}.yml",
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 67be59a1fe..da7e4139b1 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -116,6 +116,7 @@ class Author < ActiveRecord::Base
has_many :tags_with_primary_key, through: :posts
has_many :books
+ has_many :published_books, class_name: "PublishedBook"
has_many :unpublished_books, -> { where(status: [:proposed, :written]) }, class_name: "Book"
has_many :subscriptions, through: :books
has_many :subscribers, -> { order("subscribers.nick") }, through: :subscriptions
@@ -153,6 +154,7 @@ class Author < ActiveRecord::Base
has_many :comments_on_posts_with_default_include, through: :posts_with_default_include, source: :comments
has_many :posts_with_signature, ->(record) { where("posts.title LIKE ?", "%by #{record.name.downcase}%") }, class_name: "Post"
+ has_many :posts_mentioning_author, ->(record = nil) { where("posts.body LIKE ?", "%#{record&.name&.downcase}%") }, class_name: "Post"
has_many :posts_with_extension, -> { order(:title) }, class_name: "Post" do
def extension_method; end
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index afdda1a81e..43b82e6047 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -24,3 +24,9 @@ class Book < ActiveRecord::Base
"do publish work..."
end
end
+
+class PublishedBook < ActiveRecord::Base
+ self.table_name = "books"
+
+ validates_uniqueness_of :isbn
+end
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index 13e72e9c50..890e427616 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class Club < ActiveRecord::Base
- has_one :membership
+ has_one :membership, touch: true
has_many :memberships, inverse_of: false
has_many :members, through: :memberships
has_one :sponsor
@@ -12,8 +12,9 @@ class Club < ActiveRecord::Base
scope :general, -> { left_joins(:category).where(categories: { name: "General" }).unscope(:limit) }
- private
+ accepts_nested_attributes_for :membership
+ private
def private_method
"I'm sorry sir, this is a *private* club, not a *pirate* club"
end
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index a0f48d23f1..339b5c8ca8 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -25,7 +25,6 @@ class Company < AbstractCompany
end
private
-
def private_method
"I am Jack's innermost fears and aspirations"
end
diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb
index 52b7e06a63..320b26b950 100644
--- a/activerecord/test/models/company_in_module.rb
+++ b/activerecord/test/models/company_in_module.rb
@@ -91,7 +91,6 @@ module MyApplication
validate :check_empty_credit_limit
private
-
def check_empty_credit_limit
errors.add("credit_card", :blank) if credit_card.blank?
end
diff --git a/activerecord/test/models/contact.rb b/activerecord/test/models/contact.rb
index 6e02ff199b..d5f6f00691 100644
--- a/activerecord/test/models/contact.rb
+++ b/activerecord/test/models/contact.rb
@@ -10,14 +10,14 @@ module ContactFakeColumns
table_name => "id"
}
- column :id, :integer
- column :name, :string
- column :age, :integer
- column :avatar, :binary
- column :created_at, :datetime
- column :awesome, :boolean
- column :preferences, :string
- column :alternative_id, :integer
+ column :id, "integer"
+ column :name, "string"
+ column :age, "integer"
+ column :avatar, "binary"
+ column :created_at, "datetime"
+ column :awesome, "boolean"
+ column :preferences, "string"
+ column :alternative_id, "integer"
serialize :preferences
@@ -37,7 +37,7 @@ end
class ContactSti < ActiveRecord::Base
extend ContactFakeColumns
- column :type, :string
+ column :type, "string"
def type; "ContactSti" end
end
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index c6574cf6e7..92d01ba338 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -2,13 +2,13 @@
require "ostruct"
-module DeveloperProjectsAssociationExtension2
- def find_least_recent
- order("id ASC").first
+class Developer < ActiveRecord::Base
+ module ProjectsAssociationExtension2
+ def find_least_recent
+ order("id ASC").first
+ end
end
-end
-class Developer < ActiveRecord::Base
self.ignored_columns = %w(first_name last_name)
has_and_belongs_to_many :projects do
@@ -24,19 +24,19 @@ class Developer < ActiveRecord::Base
has_and_belongs_to_many :shared_computers, class_name: "Computer"
has_and_belongs_to_many :projects_extended_by_name,
- -> { extending(DeveloperProjectsAssociationExtension) },
+ -> { extending(ProjectsAssociationExtension) },
class_name: "Project",
join_table: "developers_projects",
association_foreign_key: "project_id"
has_and_belongs_to_many :projects_extended_by_name_twice,
- -> { extending(DeveloperProjectsAssociationExtension, DeveloperProjectsAssociationExtension2) },
+ -> { extending(ProjectsAssociationExtension, ProjectsAssociationExtension2) },
class_name: "Project",
join_table: "developers_projects",
association_foreign_key: "project_id"
has_and_belongs_to_many :projects_extended_by_name_and_block,
- -> { extending(DeveloperProjectsAssociationExtension) },
+ -> { extending(ProjectsAssociationExtension) },
class_name: "Project",
join_table: "developers_projects",
association_foreign_key: "project_id" do
diff --git a/activerecord/test/models/face.rb b/activerecord/test/models/face.rb
index e900fd40fb..45ccc442ba 100644
--- a/activerecord/test/models/face.rb
+++ b/activerecord/test/models/face.rb
@@ -6,7 +6,7 @@ class Face < ActiveRecord::Base
belongs_to :polymorphic_man, polymorphic: true, inverse_of: :polymorphic_face
# Oracle identifier length is limited to 30 bytes or less, `polymorphic` renamed `poly`
belongs_to :poly_man_without_inverse, polymorphic: true
- # These is a "broken" inverse_of for the purposes of testing
+ # These are "broken" inverse_of associations for the purposes of testing
belongs_to :horrible_man, class_name: "Man", inverse_of: :horrible_face
belongs_to :horrible_polymorphic_man, polymorphic: true, inverse_of: :horrible_polymorphic_face
diff --git a/activerecord/test/models/mouse.rb b/activerecord/test/models/mouse.rb
new file mode 100644
index 0000000000..75a55c125d
--- /dev/null
+++ b/activerecord/test/models/mouse.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class Mouse < ActiveRecord::Base
+ has_many :squeaks, autosave: true
+ validates :name, presence: true
+end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index c3d15a571a..0dfd29e45e 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -101,7 +101,6 @@ class RichPerson < ActiveRecord::Base
before_validation :run_before_validation
private
-
def run_before_create
self.first_name = first_name.to_s + "run_before_create"
end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index c34968590f..50c0dddcf2 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -43,6 +43,7 @@ class Post < ActiveRecord::Base
has_one :first_comment, -> { order("id ASC") }, class_name: "Comment"
has_one :last_comment, -> { order("id desc") }, class_name: "Comment"
+ scope :no_comments, -> { left_joins(:comments).where(comments: { id: nil }) }
scope :with_special_comments, -> { joins(:comments).where(comments: { type: "SpecialComment" }) }
scope :with_very_special_comments, -> { joins(:comments).where(comments: { type: "VerySpecialComment" }) }
scope :with_post, ->(post_id) { joins(:comments).where(comments: { post_id: post_id }) }
@@ -323,8 +324,8 @@ class FakeKlass
"posts"
end
- def attribute_alias?(name)
- false
+ def attribute_aliases
+ {}
end
def sanitize_sql(sql)
diff --git a/activerecord/test/models/rating.rb b/activerecord/test/models/rating.rb
index 49aa38285f..2a18ea45ac 100644
--- a/activerecord/test/models/rating.rb
+++ b/activerecord/test/models/rating.rb
@@ -4,4 +4,5 @@ class Rating < ActiveRecord::Base
belongs_to :comment
has_many :taggings, as: :taggable
has_many :taggings_without_tag, -> { left_joins(:tag).where("tags.id": nil) }, as: :taggable, class_name: "Tagging"
+ has_many :taggings_with_no_tag, -> { joins("LEFT OUTER JOIN tags ON tags.id = taggings.tag_id").where("tags.id": nil) }, as: :taggable, class_name: "Tagging"
end
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index b35623a344..f6ab9c8a8f 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -34,29 +34,29 @@ class WrongReply < Reply
validate :check_author_name_is_secret, on: :special_case
def check_empty_title
- errors[:title] << "Empty" unless attribute_present?("title")
+ errors.add(:title, "Empty") unless attribute_present?("title")
end
def errors_on_empty_content
- errors[:content] << "Empty" unless attribute_present?("content")
+ errors.add(:content, "Empty") unless attribute_present?("content")
end
def check_content_mismatch
if attribute_present?("title") && attribute_present?("content") && content == "Mismatch"
- errors[:title] << "is Content Mismatch"
+ errors.add(:title, "is Content Mismatch")
end
end
def title_is_wrong_create
- errors[:title] << "is Wrong Create" if attribute_present?("title") && title == "Wrong Create"
+ errors.add(:title, "is Wrong Create") if attribute_present?("title") && title == "Wrong Create"
end
def check_wrong_update
- errors[:title] << "is Wrong Update" if attribute_present?("title") && title == "Wrong Update"
+ errors.add(:title, "is Wrong Update") if attribute_present?("title") && title == "Wrong Update"
end
def check_author_name_is_secret
- errors[:author_name] << "Invalid" unless author_name == "secret"
+ errors.add(:author_name, "Invalid") unless author_name == "secret"
end
end
diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb
index 7973219a79..6bab7a1eb9 100644
--- a/activerecord/test/models/ship.rb
+++ b/activerecord/test/models/ship.rb
@@ -27,7 +27,8 @@ class ShipWithoutNestedAttributes < ActiveRecord::Base
has_many :prisoners, inverse_of: :ship, foreign_key: :ship_id
has_many :parts, class_name: "ShipPart", foreign_key: :ship_id
- validates :name, presence: true
+ validates :name, presence: true, if: -> { true }
+ validates :name, presence: true, if: -> { true }
end
class Prisoner < ActiveRecord::Base
diff --git a/activerecord/test/models/squeak.rb b/activerecord/test/models/squeak.rb
new file mode 100644
index 0000000000..e0a643c238
--- /dev/null
+++ b/activerecord/test/models/squeak.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class Squeak < ActiveRecord::Base
+ belongs_to :mouse
+ accepts_nested_attributes_for :mouse
+end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 77101090f2..7a864c728c 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -93,7 +93,6 @@ class Topic < ActiveRecord::Base
end
private
-
def default_written_on
self.written_on = Time.now unless attribute_present?("written_on")
end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index b143035213..911ac808c6 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -62,10 +62,6 @@ ActiveRecord::Schema.define do
t.binary :binary_column, limit: 1
end
- create_table :enum_tests, id: false, force: true do |t|
- t.column :enum_column, "ENUM('text','blob','tiny','medium','long','unsigned','bigint')"
- end
-
execute "DROP PROCEDURE IF EXISTS ten"
execute <<~SQL
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 7d9b8afeb6..dd0ff759b6 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -115,7 +115,7 @@ ActiveRecord::Schema.define do
t.column :font_size, :integer, **default_zero
t.column :difficulty, :integer, **default_zero
t.column :cover, :string, default: "hard"
- t.string :isbn
+ t.string :isbn, **case_sensitive_options
t.datetime :published_on
t.index [:author_id, :name], unique: true
t.index :isbn, where: "published_on IS NOT NULL", unique: true
@@ -261,6 +261,7 @@ ActiveRecord::Schema.define do
t.references :developer, index: false
t.references :company, index: false
t.string :metadata
+ t.integer :count
end
create_table :customers, force: true do |t|
@@ -524,6 +525,8 @@ ActiveRecord::Schema.define do
t.integer :club_id, :member_id
t.boolean :favourite, default: false
t.integer :type
+ t.datetime :created_at
+ t.datetime :updated_at
end
create_table :member_types, force: true do |t|
@@ -560,6 +563,10 @@ ActiveRecord::Schema.define do
t.string :type
end
+ create_table :mice, force: true do |t|
+ t.string :name
+ end
+
create_table :movies, force: true, id: false do |t|
t.primary_key :movieid
t.string :name
@@ -840,6 +847,10 @@ ActiveRecord::Schema.define do
end
end
+ create_table :squeaks, force: true do |t|
+ t.integer :mouse_id
+ end
+
create_table :prisoners, force: true do |t|
t.belongs_to :ship
end
diff --git a/activerecord/test/support/config.rb b/activerecord/test/support/config.rb
index de0d90a18f..66ae57b382 100644
--- a/activerecord/test/support/config.rb
+++ b/activerecord/test/support/config.rb
@@ -12,7 +12,6 @@ module ARTest
end
private
-
def config_file
Pathname.new(ENV["ARCONFIG"] || TEST_ROOT + "/config.yml")
end