aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorWillian Gustavo Veiga <beberveiga@gmail.com>2018-10-02 12:55:36 -0300
committerWillian Gustavo Veiga <beberveiga@gmail.com>2018-10-02 12:57:37 -0300
commit2d4df1349efdf8dd2c8cc4503fd5a871b0066500 (patch)
tree9f71a297cf1bb30f1d59039997a60f97357d0782 /activerecord
parent00c50c2b5966fa1d719c8a58564811c672a0e8c6 (diff)
parentcf608ee34dd833b0357ef4eefa692db33242d2aa (diff)
downloadrails-2d4df1349efdf8dd2c8cc4503fd5a871b0066500.tar.gz
rails-2d4df1349efdf8dd2c8cc4503fd5a871b0066500.tar.bz2
rails-2d4df1349efdf8dd2c8cc4503fd5a871b0066500.zip
Merge branch 'master' into feature/reselect-method
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md164
-rw-r--r--activerecord/Rakefile4
-rw-r--r--activerecord/examples/performance.rb2
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/associations.rb4
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb52
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb62
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb40
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb19
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb4
-rw-r--r--activerecord/lib/active_record/autosave_association.rb12
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/callbacks.rb2
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb41
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb76
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb70
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb30
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb43
-rw-r--r--activerecord/lib/active_record/connection_handling.rb41
-rw-r--r--activerecord/lib/active_record/core.rb66
-rw-r--r--activerecord/lib/active_record/counter_cache.rb11
-rw-r--r--activerecord/lib/active_record/database_configurations.rb203
-rw-r--r--activerecord/lib/active_record/database_configurations/database_config.rb37
-rw-r--r--activerecord/lib/active_record/database_configurations/hash_config.rb50
-rw-r--r--activerecord/lib/active_record/database_configurations/url_config.rb74
-rw-r--r--activerecord/lib/active_record/explain.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb1
-rw-r--r--activerecord/lib/active_record/inheritance.rb6
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb4
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb14
-rw-r--r--activerecord/lib/active_record/migration.rb26
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb38
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb15
-rw-r--r--activerecord/lib/active_record/model_schema.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb27
-rw-r--r--activerecord/lib/active_record/railtie.rb47
-rw-r--r--activerecord/lib/active_record/railties/databases.rake42
-rw-r--r--activerecord/lib/active_record/reflection.rb20
-rw-r--r--activerecord/lib/active_record/relation.rb85
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb2
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb8
-rw-r--r--activerecord/lib/active_record/relation/merger.rb13
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb15
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb8
-rw-r--r--activerecord/lib/active_record/result.rb15
-rw-r--r--activerecord/lib/active_record/sanitization.rb4
-rw-r--r--activerecord/lib/active_record/scoping.rb17
-rw-r--r--activerecord/lib/active_record/scoping/default.rb9
-rw-r--r--activerecord/lib/active_record/scoping/named.rb20
-rw-r--r--activerecord/lib/active_record/statement_cache.rb28
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb34
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb8
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/test_databases.rb27
-rw-r--r--activerecord/lib/arel.rb5
-rw-r--r--activerecord/lib/arel/collectors/composite.rb3
-rw-r--r--activerecord/lib/arel/collectors/plain_string.rb2
-rw-r--r--activerecord/lib/arel/collectors/sql_string.rb4
-rw-r--r--activerecord/lib/arel/collectors/substitute_binds.rb3
-rw-r--r--activerecord/lib/arel/delete_manager.rb11
-rw-r--r--activerecord/lib/arel/factory_methods.rb4
-rw-r--r--activerecord/lib/arel/nodes/bind_param.rb2
-rw-r--r--activerecord/lib/arel/nodes/delete_statement.rb15
-rw-r--r--activerecord/lib/arel/nodes/node.rb10
-rw-r--r--activerecord/lib/arel/nodes/select_core.rb6
-rw-r--r--activerecord/lib/arel/nodes/unary.rb1
-rw-r--r--activerecord/lib/arel/nodes/update_statement.rb7
-rw-r--r--activerecord/lib/arel/select_manager.rb8
-rw-r--r--activerecord/lib/arel/table.rb3
-rw-r--r--activerecord/lib/arel/tree_manager.rb34
-rw-r--r--activerecord/lib/arel/update_manager.rb29
-rw-r--r--activerecord/lib/arel/visitors/depth_first.rb3
-rw-r--r--activerecord/lib/arel/visitors/dot.rb2
-rw-r--r--activerecord/lib/arel/visitors/mssql.rb7
-rw-r--r--activerecord/lib/arel/visitors/mysql.rb42
-rw-r--r--activerecord/lib/arel/visitors/to_sql.rb50
-rw-r--r--activerecord/lib/rails/generators/active_record/migration.rb15
-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/cases/adapter_test.rb58
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/charset_collation_test.rb10
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb3
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb42
-rw-r--r--activerecord/test/cases/arel/collectors/sql_string_test.rb16
-rw-r--r--activerecord/test/cases/arel/delete_manager_test.rb1
-rw-r--r--activerecord/test/cases/arel/select_manager_test.rb4
-rw-r--r--activerecord/test/cases/arel/visitors/depth_first_test.rb1
-rw-r--r--activerecord/test/cases/arel/visitors/dot_test.rb1
-rw-r--r--activerecord/test/cases/arel/visitors/to_sql_test.rb2
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb59
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb5
-rw-r--r--activerecord/test/cases/associations/eager_test.rb44
-rw-r--r--activerecord/test/cases/associations/extension_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb88
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb9
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb3
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb12
-rw-r--r--activerecord/test/cases/associations_test.rb2
-rw-r--r--activerecord/test/cases/attributes_test.rb14
-rw-r--r--activerecord/test/cases/autosave_association_test.rb34
-rw-r--r--activerecord/test/cases/base_test.rb18
-rw-r--r--activerecord/test/cases/batches_test.rb32
-rw-r--r--activerecord/test/cases/binary_test.rb2
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb6
-rw-r--r--activerecord/test/cases/callbacks_test.rb6
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb29
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb7
-rw-r--r--activerecord/test/cases/connection_management_test.rb2
-rw-r--r--activerecord/test/cases/connection_pool_test.rb91
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb8
-rw-r--r--activerecord/test/cases/core_test.rb8
-rw-r--r--activerecord/test/cases/dirty_test.rb22
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb2
-rw-r--r--activerecord/test/cases/filter_attributes_test.rb84
-rw-r--r--activerecord/test/cases/finder_respond_to_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb5
-rw-r--r--activerecord/test/cases/fixtures_test.rb18
-rw-r--r--activerecord/test/cases/helper.rb2
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb16
-rw-r--r--activerecord/test/cases/legacy_configurations_test.rb43
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb1
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb8
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb29
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb14
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb35
-rw-r--r--activerecord/test/cases/migration/pending_migrations_test.rb4
-rw-r--r--activerecord/test/cases/migration_test.rb30
-rw-r--r--activerecord/test/cases/migrator_test.rb1
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb12
-rw-r--r--activerecord/test/cases/persistence_test.rb168
-rw-r--r--activerecord/test/cases/query_cache_test.rb21
-rw-r--r--activerecord/test/cases/reaper_test.rb4
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb6
-rw-r--r--activerecord/test/cases/relation/delete_all_test.rb104
-rw-r--r--activerecord/test/cases/relation/merging_test.rb14
-rw-r--r--activerecord/test/cases/relation/update_all_test.rb239
-rw-r--r--activerecord/test/cases/relations_test.rb173
-rw-r--r--activerecord/test/cases/result_test.rb14
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb19
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb16
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb2
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb4
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb2
-rw-r--r--activerecord/test/cases/suppressor_test.rb2
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb217
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb24
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb25
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb8
-rw-r--r--activerecord/test/cases/test_case.rb2
-rw-r--r--activerecord/test/cases/transaction_isolation_test.rb6
-rw-r--r--activerecord/test/cases/transactions_test.rb74
-rw-r--r--activerecord/test/cases/type/type_map_test.rb6
-rw-r--r--activerecord/test/cases/validations_test.rb18
-rw-r--r--activerecord/test/config.example.yml6
-rw-r--r--activerecord/test/fixtures/citations.yml4
-rw-r--r--activerecord/test/models/author.rb2
-rw-r--r--activerecord/test/models/citation.rb1
-rw-r--r--activerecord/test/models/company.rb14
-rw-r--r--activerecord/test/models/contract.rb4
-rw-r--r--activerecord/test/models/post.rb2
-rw-r--r--activerecord/test/models/price_estimate.rb8
-rw-r--r--activerecord/test/models/reply.rb8
-rw-r--r--activerecord/test/models/topic.rb2
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb13
-rw-r--r--activerecord/test/schema/schema.rb3
-rw-r--r--activerecord/test/schema/sqlite_specific_schema.rb11
203 files changed, 3050 insertions, 1333 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index d1ae41ab97..96fd6a62c6 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,163 @@
+* 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
+ 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.
+
+ *fatkodima*, *David Verhasselt*
+
+* Raise an error instead of scanning the filesystem root when `fixture_path` is blank.
+
+ *Gannon McGibbon*, *Max Albrecht*
+
+* Allow `ActiveRecord::Base.configurations=` to be set with a symbolized hash.
+
+ *Gannon McGibbon*
+
+* Don't update counter cache unless the record is actually saved.
+
+ Fixes #31493, #33113, #33117.
+
+ *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.
+
+ *Yasuo Honda*
+
+* 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]
+ Account.last.inspect # => #<Account id: 123, name: "DHH", credit_card_number: [FILTERED] ...>
+ SecureAccount.filter_attributes += [:name]
+ SecureAccount.last.inspect # => #<SecureAccount id: 42, name: [FILTERED], credit_card_number: [FILTERED] ...>
+ ```
+
+ *Zhang Kang*
+
+* 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`.
+
+ *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
@@ -64,8 +224,8 @@
*Eddie Lebow*
-* 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.
+* 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.
*DHH*
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 170c95b827..fae56a51bb 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -94,8 +94,8 @@ namespace :db do
desc "Build the MySQL test databases"
task :build do
config = ARTest.config["connections"]["mysql2"]
- %x( mysql --user=#{config["arunit"]["username"]} --password=#{config["arunit"]["password"]} -e "create DATABASE #{config["arunit"]["database"]} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ")
- %x( mysql --user=#{config["arunit2"]["username"]} --password=#{config["arunit2"]["password"]} -e "create DATABASE #{config["arunit2"]["database"]} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ")
+ %x( mysql --user=#{config["arunit"]["username"]} --password=#{config["arunit"]["password"]} -e "create DATABASE #{config["arunit"]["database"]} DEFAULT CHARACTER SET utf8mb4" )
+ %x( mysql --user=#{config["arunit2"]["username"]} --password=#{config["arunit2"]["password"]} -e "create DATABASE #{config["arunit2"]["database"]} DEFAULT CHARACTER SET utf8mb4" )
end
desc "Drop the MySQL test databases"
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
index 1a2c78f39b..024e503ec7 100644
--- a/activerecord/examples/performance.rb
+++ b/activerecord/examples/performance.rb
@@ -176,7 +176,7 @@ Benchmark.ips(TIME) do |x|
end
x.report "Model.log" do
- Exhibit.connection.send(:log, "hello", "world") {}
+ Exhibit.connection.send(:log, "hello", "world") { }
end
x.report "AR.execute(query)" do
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index d198466dbf..d43378c64f 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -40,7 +40,6 @@ module ActiveRecord
autoload :Core
autoload :ConnectionHandling
autoload :CounterCache
- autoload :DatabaseConfigurations
autoload :DynamicMatchers
autoload :Enum
autoload :InternalMetadata
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 1ee52945ea..ab1e7ad269 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1524,6 +1524,7 @@ module ActiveRecord
# Returns the associated object. +nil+ is returned if none is found.
# [association=(associate)]
# Assigns the associate object, extracts the primary key, and sets it as the foreign key.
+ # No modification or deletion of existing records takes place.
# [build_association(attributes = {})]
# Returns a new object of the associated type that has been instantiated
# with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
@@ -1581,7 +1582,7 @@ module ActiveRecord
# association will use "taggable_type" as the default <tt>:foreign_type</tt>.
# [:primary_key]
# Specify the method that returns the primary key of associated object used for the association.
- # By default this is id.
+ # By default this is +id+.
# [:dependent]
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
@@ -1761,6 +1762,7 @@ module ActiveRecord
# has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
# has_and_belongs_to_many :categories, ->(post) {
# where("default_category = ?", post.default_category)
+ # }
#
# === Extensions
#
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 3d4ad1dd5b..3346725f2d 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -34,14 +34,28 @@ module ActiveRecord
@updated
end
- def decrement_counters # :nodoc:
+ def decrement_counters
update_counters(-1)
end
- def increment_counters # :nodoc:
+ def increment_counters
update_counters(1)
end
+ def decrement_counters_before_last_save
+ if reflection.polymorphic?
+ model_was = owner.attribute_before_last_save(reflection.foreign_type).try(:constantize)
+ else
+ model_was = klass
+ end
+
+ foreign_key_was = owner.attribute_before_last_save(reflection.foreign_key)
+
+ if foreign_key_was && model_was < ActiveRecord::Base
+ update_counters_via_scope(model_was, foreign_key_was, -1)
+ end
+ end
+
def target_changed?
owner.saved_change_to_attribute?(reflection.foreign_key)
end
@@ -50,11 +64,8 @@ module ActiveRecord
def replace(record)
if record
raise_on_type_mismatch!(record)
- update_counters_on_replace(record)
set_inverse_instance(record)
@updated = true
- else
- decrement_counters
end
replace_keys(record)
@@ -67,11 +78,16 @@ module ActiveRecord
if target && !stale_target?
target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch])
else
- counter_cache_target.update_counters(reflection.counter_cache_column => by, touch: reflection.options[:touch])
+ update_counters_via_scope(klass, owner._read_attribute(reflection.foreign_key), by)
end
end
end
+ def update_counters_via_scope(klass, foreign_key, by)
+ scope = klass.unscoped.where!(primary_key(klass) => foreign_key)
+ scope.update_counters(reflection.counter_cache_column => by, touch: reflection.options[:touch])
+ end
+
def find_target?
!loaded? && foreign_key_present? && klass
end
@@ -80,25 +96,12 @@ module ActiveRecord
reflection.counter_cache_column && owner.persisted?
end
- def update_counters_on_replace(record)
- if require_counter_update? && different_target?(record)
- owner.instance_variable_set :@_after_replace_counter_called, true
- record.increment!(reflection.counter_cache_column, touch: reflection.options[:touch])
- decrement_counters
- end
- end
-
- # Checks whether record is different to the current target, without loading it
- def different_target?(record)
- record._read_attribute(primary_key(record)) != owner._read_attribute(reflection.foreign_key)
- end
-
def replace_keys(record)
- owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record)) : nil
+ owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record.class)) : nil
end
- def primary_key(record)
- reflection.association_primary_key(record.class)
+ def primary_key(klass)
+ reflection.association_primary_key(klass)
end
def foreign_key_present?
@@ -112,11 +115,6 @@ module ActiveRecord
inverse && inverse.has_one?
end
- def counter_cache_target
- primary_key = reflection.association_primary_key(klass)
- klass.unscoped.where!(primary_key => owner._read_attribute(reflection.foreign_key))
- end
-
def stale_state
result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
result && result.to_s
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index 3fd2fb5f67..9ae452e7a1 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -19,10 +19,6 @@ module ActiveRecord
owner[reflection.foreign_type] = record ? record.class.polymorphic_name : nil
end
- def different_target?(record)
- super || record.class != klass
- end
-
def inverse_reflection_for(record)
reflection.polymorphic_inverse_of(record.class)
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index b5fb0092d7..fc00f1e900 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -21,56 +21,16 @@ module ActiveRecord::Associations::Builder # :nodoc:
add_default_callbacks(model, reflection) if reflection.options[:default]
end
- def self.define_accessors(mixin, reflection)
- super
- add_counter_cache_methods mixin
- end
-
- def self.add_counter_cache_methods(mixin)
- return if mixin.method_defined? :belongs_to_counter_cache_after_update
-
- mixin.class_eval do
- def belongs_to_counter_cache_after_update(reflection)
- foreign_key = reflection.foreign_key
- cache_column = reflection.counter_cache_column
-
- if (@_after_replace_counter_called ||= false)
- @_after_replace_counter_called = false
- elsif association(reflection.name).target_changed?
- if reflection.polymorphic?
- model = attribute_in_database(reflection.foreign_type).try(:constantize)
- model_was = attribute_before_last_save(reflection.foreign_type).try(:constantize)
- else
- model = reflection.klass
- model_was = reflection.klass
- end
-
- foreign_key_was = attribute_before_last_save foreign_key
- foreign_key = attribute_in_database foreign_key
-
- if foreign_key && model < ActiveRecord::Base
- counter_cache_target(reflection, model, foreign_key).update_counters(cache_column => 1)
- end
-
- if foreign_key_was && model_was < ActiveRecord::Base
- counter_cache_target(reflection, model_was, foreign_key_was).update_counters(cache_column => -1)
- end
- end
- end
-
- private
- def counter_cache_target(reflection, model, foreign_key)
- primary_key = reflection.association_primary_key(model)
- model.unscoped.where!(primary_key => foreign_key)
- end
- end
- end
-
def self.add_counter_cache_callbacks(model, reflection)
cache_column = reflection.counter_cache_column
model.after_update lambda { |record|
- record.belongs_to_counter_cache_after_update(reflection)
+ association = association(reflection.name)
+
+ if association.target_changed?
+ association.increment_counters
+ association.decrement_counters_before_last_save
+ end
}
klass = reflection.class_name.safe_constantize
@@ -121,12 +81,18 @@ module ActiveRecord::Associations::Builder # :nodoc:
BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method)
}}
- unless reflection.counter_cache_column
+ if reflection.counter_cache_column
+ touch_callback = callback.(:saved_changes)
+ update_callback = lambda { |record|
+ instance_exec(record, &touch_callback) unless association(reflection.name).target_changed?
+ }
+ model.after_update update_callback, if: :saved_changes?
+ else
model.after_create callback.(:saved_changes), if: :saved_changes?
+ model.after_update callback.(:saved_changes), if: :saved_changes?
model.after_destroy callback.(:changes_to_save)
end
- model.after_update callback.(:saved_changes), if: :saved_changes?
model.after_touch callback.(:changes_to_save)
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 e3070e0472..0140aa15c8 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
@@ -63,7 +63,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
def middle_reflection(join_model)
middle_name = [lhs_model.name.downcase.pluralize,
- association_name].join("_".freeze).gsub("::".freeze, "_".freeze).to_sym
+ association_name].join("_").gsub("::", "_").to_sym
middle_options = middle_options join_model
HasMany.create_reflection(lhs_model,
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 5c2ac5b374..a8f94b574d 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -88,7 +88,6 @@ module ActiveRecord
if records.empty?
[]
else
- records.uniq!
Array.wrap(associations).flat_map { |association|
preloaders_on association, records, preload_scope
}
@@ -99,12 +98,11 @@ module ActiveRecord
# Loads all the given data into +records+ for the +association+.
def preloaders_on(association, records, scope, polymorphic_parent = false)
- case association
- when Hash
+ if association.respond_to?(:to_hash)
preloaders_for_hash(association, records, scope, polymorphic_parent)
- when Symbol
+ elsif association.is_a?(Symbol)
preloaders_for_one(association, records, scope, polymorphic_parent)
- when String
+ elsif association.respond_to?(:to_str)
preloaders_for_one(association.to_sym, records, scope, polymorphic_parent)
else
raise ArgumentError, "#{association.inspect} was not recognized for preload"
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index e4b8b1a330..221ebea8ea 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -31,7 +31,7 @@ module ActiveRecord
end
}
- BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
+ RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
class GeneratedAttributeMethods < Module #:nodoc:
include Mutex_m
@@ -123,7 +123,7 @@ module ActiveRecord
# A class method is 'dangerous' if it is already (re)defined by Active Record, but
# not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
def dangerous_class_method?(method_name)
- BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
+ RESTRICTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
end
def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
@@ -167,12 +167,14 @@ module ActiveRecord
end
end
- # Regexp whitelist. Matches the following:
+ # Regexp for column names (with or without a table name prefix). Matches
+ # the following:
# "#{table_name}.#{column_name}"
# "#{column_name}"
- COLUMN_NAME_WHITELIST = /\A(?:\w+\.)?\w+\z/i
+ COLUMN_NAME = /\A(?:\w+\.)?\w+\z/i
- # Regexp whitelist. Matches the following:
+ # 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"
@@ -181,7 +183,7 @@ module ActiveRecord
# "#{column_name} #{direction}"
# "#{column_name} #{direction} NULLS FIRST"
# "#{column_name} NULLS LAST"
- COLUMN_NAME_ORDER_WHITELIST = /
+ COLUMN_NAME_WITH_ORDER = /
\A
(?:\w+\.)?
\w+
@@ -190,12 +192,10 @@ module ActiveRecord
\z
/ix
- def enforce_raw_sql_whitelist(args, whitelist: COLUMN_NAME_WHITELIST) # :nodoc:
+ def disallow_raw_sql!(args, permit: COLUMN_NAME) # :nodoc:
unexpected = args.reject do |arg|
- arg.kind_of?(Arel::Node) ||
- arg.is_a?(Arel::Nodes::SqlLiteral) ||
- arg.is_a?(Arel::Attributes::Attribute) ||
- arg.to_s.split(/\s*,\s*/).all? { |part| whitelist.match?(part) }
+ Arel.arel_node?(arg) ||
+ arg.to_s.split(/\s*,\s*/).all? { |part| permit.match?(part) }
end
return if unexpected.none?
@@ -272,9 +272,9 @@ module ActiveRecord
case name
when :to_partial_path
- name = "to_partial_path".freeze
+ name = "to_partial_path"
when :to_model
- name = "to_model".freeze
+ name = "to_model"
else
name = name.to_s
end
@@ -449,14 +449,6 @@ module ActiveRecord
defined?(@attributes) && @attributes.key?(attr_name)
end
- def attributes_with_values_for_create(attribute_names)
- attributes_with_values(attributes_for_create(attribute_names))
- end
-
- def attributes_with_values_for_update(attribute_names)
- attributes_with_values(attributes_for_update(attribute_names))
- end
-
def attributes_with_values(attribute_names)
attribute_names.each_with_object({}) do |name, attrs|
attrs[name] = _read_attribute(name)
@@ -465,7 +457,8 @@ module ActiveRecord
# Filters the primary keys and readonly attributes from the attribute names.
def attributes_for_update(attribute_names)
- attribute_names.reject do |name|
+ attribute_names &= self.class.column_names
+ attribute_names.delete_if do |name|
readonly_attribute?(name)
end
end
@@ -473,7 +466,8 @@ module ActiveRecord
# Filters out the primary keys, from the attribute names, when the primary
# key is to be generated (e.g. the id attribute has no value).
def attributes_for_create(attribute_names)
- attribute_names.reject do |name|
+ attribute_names &= self.class.column_names
+ attribute_names.delete_if do |name|
pk_attribute?(name) && id.nil?
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 233ee29fac..ebc2252c50 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -16,9 +16,6 @@ module ActiveRecord
class_attribute :partial_writes, instance_writer: false, default: true
- after_create { changes_applied }
- after_update { changes_applied }
-
# Attribute methods for "changed in last call to save?"
attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
attribute_method_prefix("saved_change_to_")
@@ -167,16 +164,20 @@ module ActiveRecord
result
end
- def _update_record(*)
- partial_writes? ? super(keys_for_partial_write) : super
+ def _update_record(attribute_names = attribute_names_for_partial_writes)
+ affected_rows = super
+ changes_applied
+ affected_rows
end
- def _create_record(*)
- partial_writes? ? super(keys_for_partial_write) : super
+ def _create_record(attribute_names = attribute_names_for_partial_writes)
+ id = super
+ changes_applied
+ id
end
- def keys_for_partial_write
- changed_attribute_names_to_save & self.class.column_names
+ def attribute_names_for_partial_writes
+ partial_writes? ? changed_attribute_names_to_save : attribute_names
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 0f7bcba564..903fe86e04 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -27,7 +27,7 @@ module ActiveRecord
# Making it frozen means that it doesn't get duped when used to
# key the @attributes in read_attribute.
def define_method_attribute(name)
- safe_name = name.unpack1("h*".freeze)
+ safe_name = name.unpack1("h*")
temp_method = "__temp__#{safe_name}"
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
@@ -59,7 +59,7 @@ module ActiveRecord
end
primary_key = self.class.primary_key
- name = primary_key if name == "id".freeze && primary_key
+ name = primary_key if name == "id" && primary_key
sync_with_transaction_state if name == primary_key
_read_attribute(name, &block)
end
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 d2b7817b45..294a3dc32c 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -73,7 +73,7 @@ module ActiveRecord
# `skip_time_zone_conversion_for_attributes` would not be picked up.
subclass.class_eval do
matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
- decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type|
+ decorate_matching_attribute_types(matcher, "_time_zone_conversion") do |type|
TimeZoneConverter.new(type)
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index c7521422bb..62743bc9d8 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -13,7 +13,7 @@ module ActiveRecord
private
def define_method_attribute=(name)
- safe_name = name.unpack1("h*".freeze)
+ safe_name = name.unpack1("h*")
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
@@ -40,7 +40,7 @@ module ActiveRecord
end
primary_key = self.class.primary_key
- name = primary_key if name == "id".freeze && primary_key
+ name = primary_key if name == "id" && primary_key
sync_with_transaction_state if name == primary_key
_write_attribute(name, value)
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index a405f05e0b..d77d76cb1e 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -149,7 +149,7 @@ module ActiveRecord
private
def define_non_cyclic_method(name, &block)
- return if method_defined?(name)
+ return if instance_methods(false).include?(name)
define_method(name) do |*args|
result = true; @_already_called ||= {}
# Loop prevention for validation of associations
@@ -392,7 +392,7 @@ module ActiveRecord
records -= records_to_destroy
end
- records.each_with_index do |record, index|
+ records.each do |record|
next if record.destroyed?
saved = true
@@ -401,11 +401,11 @@ module ActiveRecord
if autosave
saved = association.insert_record(record, false)
elsif !reflection.nested?
+ association_saved = association.insert_record(record)
+
if reflection.validate?
- valid = association_valid?(reflection, record, index)
- saved = valid ? association.insert_record(record, false) : false
- else
- association.insert_record(record)
+ errors.add(reflection.name) unless association_saved
+ saved = association_saved
end
end
elsif autosave
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 5169f312f5..db097cb930 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -22,6 +22,7 @@ require "active_record/explain_subscriber"
require "active_record/relation/delegation"
require "active_record/attributes"
require "active_record/type_caster"
+require "active_record/database_configurations"
module ActiveRecord #:nodoc:
# = Active Record
@@ -291,7 +292,6 @@ module ActiveRecord #:nodoc:
extend Aggregations::ClassMethods
include Core
- include DatabaseConfigurations
include Persistence
include ReadonlyAttributes
include ModelSchema
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index b6852bfc71..1bffe89875 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -318,7 +318,7 @@ module ActiveRecord
_run_touch_callbacks { super }
end
- def increment!(*, touch: nil) # :nodoc:
+ def increment!(attribute, by = 1, touch: nil) # :nodoc:
touch ? _run_touch_callbacks { super } : super
end
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
index dfba78614e..61581b0451 100644
--- a/activerecord/lib/active_record/collection_cache_key.rb
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -16,7 +16,7 @@ module ActiveRecord
collection = collection.send(:apply_join_dependency)
end
column_type = type_for_attribute(timestamp_column)
- column = connection.column_name_from_arel_node(collection.arel_attribute(timestamp_column))
+ column = connection.visitor.compile(collection.arel_attribute(timestamp_column))
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
if collection.has_limit_or_offset?
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 f721e91203..99934a0e31 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -188,7 +188,9 @@ module ActiveRecord
t0 = Time.now
elapsed = 0
loop do
- @cond.wait(timeout - elapsed)
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ @cond.wait(timeout - elapsed)
+ end
return remove if any?
@@ -729,7 +731,7 @@ module ActiveRecord
# this block can't be easily moved into attempt_to_checkout_all_existing_connections's
# rescue block, because doing so would put it outside of synchronize section, without
# being in a critical section thread_report might become inaccurate
- msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds".dup
+ msg = +"could not obtain ownership of all database connections in #{checkout_timeout} seconds"
thread_report = []
@connections.each do |conn|
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 7a9e7add24..1305216be2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/deprecation"
+
module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseLimits
@@ -12,11 +14,13 @@ module ActiveRecord
def column_name_length
64
end
+ deprecate :column_name_length
# Returns the maximum length of a table name.
def table_name_length
64
end
+ deprecate :table_name_length
# Returns the maximum allowed length for an index name. This
# limit is enforced by \Rails and is less than or equal to
@@ -36,16 +40,19 @@ module ActiveRecord
def columns_per_table
1024
end
+ deprecate :columns_per_table
# Returns the maximum number of indexes per table.
def indexes_per_table
16
end
+ deprecate :indexes_per_table
# Returns the maximum number of columns in a multicolumn index.
def columns_per_multicolumn_index
16
end
+ deprecate :columns_per_multicolumn_index
# Returns the maximum number of elements in an IN (x,y,z) clause.
# +nil+ means no limit.
@@ -57,11 +64,18 @@ module ActiveRecord
def sql_query_length
1048575
end
+ deprecate :sql_query_length
# Returns maximum number of joins in a single query.
def joins_per_query
256
end
+ deprecate :joins_per_query
+
+ private
+ def bind_params_length
+ 65535
+ end
end
end
end
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 41553cfa83..feacdf6931 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -20,7 +20,7 @@ module ActiveRecord
raise "Passing bind parameters with an arel AST is forbidden. " \
"The values must be stored on the AST directly"
end
- sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value
+ sql, binds = visitor.compile(arel_or_sql_string.ast, collector)
[sql.freeze, binds || []]
else
[arel_or_sql_string.dup.freeze, binds]
@@ -32,11 +32,11 @@ module ActiveRecord
# can be used to query the database repeatedly.
def cacheable_query(klass, arel) # :nodoc:
if prepared_statements
- sql, binds = visitor.accept(arel.ast, collector).value
+ sql, binds = visitor.compile(arel.ast, collector)
query = klass.query(sql)
else
- collector = PartialQueryCollector.new
- parts, binds = visitor.accept(arel.ast, collector).value
+ collector = klass.partial_query_collector
+ parts, binds = visitor.compile(arel.ast, collector)
query = klass.partial_query(parts)
end
[query, binds]
@@ -46,11 +46,16 @@ module ActiveRecord
def select_all(arel, name = nil, binds = [], preparable: nil)
arel = arel_from_relation(arel)
sql, binds = to_sql_and_binds(arel, binds)
+
if !prepared_statements || (arel.is_a?(String) && preparable.nil?)
preparable = false
+ elsif binds.length > bind_params_length
+ sql, binds = unprepared_statement { to_sql_and_binds(arel) }
+ preparable = false
else
preparable = visitor.preparable
end
+
if prepared_statements && preparable
select_prepared(sql, name, binds)
else
@@ -259,7 +264,9 @@ module ActiveRecord
attr_reader :transaction_manager #:nodoc:
- delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction, :commit_transaction, :rollback_transaction, to: :transaction_manager
+ delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction,
+ :commit_transaction, :rollback_transaction, :materialize_transactions,
+ :disable_lazy_transactions!, :enable_lazy_transactions!, to: :transaction_manager
def transaction_open?
current_transaction.open?
@@ -372,7 +379,7 @@ module ActiveRecord
build_fixture_sql(fixtures, table_name)
end.compact
- table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}".dup }
+ table_deletes = tables_to_delete.map { |table| +"DELETE FROM #{quote_table_name table}" }
total_sql = Array.wrap(combine_multi_statements(table_deletes + fixture_inserts))
disable_referential_integrity do
@@ -500,28 +507,6 @@ module ActiveRecord
value
end
end
-
- class PartialQueryCollector
- def initialize
- @parts = []
- @binds = []
- end
-
- def <<(str)
- @parts << str
- self
- end
-
- def add_bind(obj)
- @binds << obj
- @parts << Arel::Nodes::BindParam.new(1)
- self
- end
-
- def value
- [@parts, @binds]
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 98b1348135..07e86afe9a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -60,7 +60,7 @@ module ActiveRecord
# Quotes a string, escaping any ' (single quote) and \ (backslash)
# characters.
def quote_string(s)
- s.gsub('\\'.freeze, '\&\&'.freeze).gsub("'".freeze, "''".freeze) # ' (for ruby-mode)
+ s.gsub('\\', '\&\&').gsub("'", "''") # ' (for ruby-mode)
end
# Quotes the column name. Defaults to no quoting.
@@ -95,7 +95,7 @@ module ActiveRecord
end
def quoted_true
- "TRUE".freeze
+ "TRUE"
end
def unquoted_true
@@ -103,7 +103,7 @@ module ActiveRecord
end
def quoted_false
- "FALSE".freeze
+ "FALSE"
end
def unquoted_false
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 529c9d8ca6..9d9e8a4110 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -21,7 +21,7 @@ module ActiveRecord
private
def visit_AlterTable(o)
- sql = "ALTER TABLE #{quote_table_name(o.name)} ".dup
+ sql = +"ALTER TABLE #{quote_table_name(o.name)} "
sql << o.adds.map { |col| accept col }.join(" ")
sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(" ")
sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ")
@@ -29,17 +29,17 @@ module ActiveRecord
def visit_ColumnDefinition(o)
o.sql_type = type_to_sql(o.type, o.options)
- column_sql = "#{quote_column_name(o.name)} #{o.sql_type}".dup
+ column_sql = +"#{quote_column_name(o.name)} #{o.sql_type}"
add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key
column_sql
end
def visit_AddColumnDefinition(o)
- "ADD #{accept(o.column)}".dup
+ +"ADD #{accept(o.column)}"
end
def visit_TableDefinition(o)
- create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} ".dup
+ create_sql = +"CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} "
statements = o.columns.map { |c| accept c }
statements << accept(o.primary_keys) if o.primary_keys
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 582ac516c7..015204c056 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -527,7 +527,9 @@ module ActiveRecord
#
# See TableDefinition#column for details of the options you can use.
def column(column_name, type, options = {})
+ index_options = options.delete(:index)
@base.add_column(name, column_name, type, options)
+ index(column_name, index_options.is_a?(Hash) ? index_options : {}) if index_options
end
# Checks to see if a column exists.
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 3be0906f2a..723d8c318d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -211,13 +211,13 @@ module ActiveRecord
#
# ====== Add a backend specific option to the generated SQL (MySQL)
#
- # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
+ # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4')
#
# generates:
#
# CREATE TABLE suppliers (
# id bigint auto_increment PRIMARY KEY
- # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
#
# ====== Rename the primary key column
#
@@ -522,6 +522,9 @@ module ActiveRecord
# Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
# * <tt>:scale</tt> -
# Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
+ # * <tt>:collation</tt> -
+ # Specifies the collation for a <tt>:string</tt> or <tt>:text</tt> column. If not specified, the
+ # column will have the same collation as the table.
# * <tt>:comment</tt> -
# Specifies the comment for the column. This option is ignored by some backends.
#
@@ -599,6 +602,7 @@ module ActiveRecord
# The +type+ and +options+ parameters will be ignored if present. It can be helpful
# to provide these in a migration's +change+ method so it can be reverted.
# In that case, +type+ and +options+ will be used by #add_column.
+ # Indexes on the column are automatically removed.
def remove_column(table_name, column_name, type = nil, options = {})
execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, options)}"
end
@@ -980,11 +984,18 @@ module ActiveRecord
#
# remove_foreign_key :accounts, column: :owner_id
#
+ # Removes the foreign key on +accounts.owner_id+.
+ #
+ # remove_foreign_key :accounts, to_table: :owners
+ #
# Removes the foreign key named +special_fk_name+ on the +accounts+ table.
#
# remove_foreign_key :accounts, name: :special_fk_name
#
- # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
+ # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key
+ # with an addition of
+ # [<tt>:to_table</tt>]
+ # The name of the table that contains the referenced primary key.
def remove_foreign_key(from_table, options_or_to_table = {})
return unless supports_foreign_keys?
@@ -1369,7 +1380,7 @@ module ActiveRecord
sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
if versions.is_a?(Array)
- sql = "INSERT INTO #{sm_table} (version) VALUES\n".dup
+ sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
sql << ";\n\n"
sql
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index b59df2fff7..564b226b39 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -91,12 +91,14 @@ module ActiveRecord
end
class Transaction #:nodoc:
- attr_reader :connection, :state, :records, :savepoint_name
+ attr_reader :connection, :state, :records, :savepoint_name, :isolation_level
def initialize(connection, options, run_commit_callbacks: false)
@connection = connection
@state = TransactionState.new
@records = []
+ @isolation_level = options[:isolation]
+ @materialized = false
@joinable = options.fetch(:joinable, true)
@run_commit_callbacks = run_commit_callbacks
end
@@ -105,6 +107,14 @@ module ActiveRecord
records << record
end
+ def materialize!
+ @materialized = true
+ end
+
+ def materialized?
+ @materialized
+ end
+
def rollback_records
ite = records.uniq
while record = ite.shift
@@ -141,24 +151,30 @@ module ActiveRecord
end
class SavepointTransaction < Transaction
- def initialize(connection, savepoint_name, parent_transaction, options, *args)
- super(connection, options, *args)
+ def initialize(connection, savepoint_name, parent_transaction, *args)
+ super(connection, *args)
parent_transaction.state.add_child(@state)
- if options[:isolation]
+ if isolation_level
raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
end
- connection.create_savepoint(@savepoint_name = savepoint_name)
+
+ @savepoint_name = savepoint_name
+ end
+
+ def materialize!
+ connection.create_savepoint(savepoint_name)
+ super
end
def rollback
- connection.rollback_to_savepoint(savepoint_name)
+ connection.rollback_to_savepoint(savepoint_name) if materialized?
@state.rollback!
end
def commit
- connection.release_savepoint(savepoint_name)
+ connection.release_savepoint(savepoint_name) if materialized?
@state.commit!
end
@@ -166,22 +182,23 @@ module ActiveRecord
end
class RealTransaction < Transaction
- def initialize(connection, options, *args)
- super
- if options[:isolation]
- connection.begin_isolated_db_transaction(options[:isolation])
+ def materialize!
+ if isolation_level
+ connection.begin_isolated_db_transaction(isolation_level)
else
connection.begin_db_transaction
end
+
+ super
end
def rollback
- connection.rollback_db_transaction
+ connection.rollback_db_transaction if materialized?
@state.full_rollback!
end
def commit
- connection.commit_db_transaction
+ connection.commit_db_transaction if materialized?
@state.full_commit!
end
end
@@ -190,6 +207,9 @@ module ActiveRecord
def initialize(connection)
@stack = []
@connection = connection
+ @has_unmaterialized_transactions = false
+ @materializing_transactions = false
+ @lazy_transactions_enabled = true
end
def begin_transaction(options = {})
@@ -203,11 +223,41 @@ module ActiveRecord
run_commit_callbacks: run_commit_callbacks)
end
+ transaction.materialize! unless @connection.supports_lazy_transactions? && lazy_transactions_enabled?
@stack.push(transaction)
+ @has_unmaterialized_transactions = true if @connection.supports_lazy_transactions?
transaction
end
end
+ def disable_lazy_transactions!
+ materialize_transactions
+ @lazy_transactions_enabled = false
+ end
+
+ def enable_lazy_transactions!
+ @lazy_transactions_enabled = true
+ end
+
+ def lazy_transactions_enabled?
+ @lazy_transactions_enabled
+ end
+
+ def materialize_transactions
+ return if @materializing_transactions
+ return unless @has_unmaterialized_transactions
+
+ @connection.lock.synchronize do
+ begin
+ @materializing_transactions = true
+ @stack.each { |t| t.materialize! unless t.materialized? }
+ ensure
+ @materializing_transactions = false
+ end
+ @has_unmaterialized_transactions = false
+ end
+ end
+
def commit_transaction
@connection.lock.synchronize do
transaction = @stack.last
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index a4748dbeda..fa10f18cb7 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -65,7 +65,7 @@ module ActiveRecord
# Most of the methods in the adapter are useful during migrations. Most
# notably, the instance methods provided by SchemaStatements are very useful.
class AbstractAdapter
- ADAPTER_NAME = "Abstract".freeze
+ ADAPTER_NAME = "Abstract"
include ActiveSupport::Callbacks
define_callbacks :checkout, :checkin
@@ -80,6 +80,8 @@ module ActiveRecord
attr_reader :schema_cache, :owner, :logger, :prepared_statements, :lock
alias :in_use? :owner
+ set_callback :checkin, :after, :enable_lazy_transactions!
+
def self.type_cast_config_to_integer(config)
if config.is_a?(Integer)
config
@@ -119,6 +121,14 @@ module ActiveRecord
else
@prepared_statements = false
end
+
+ @advisory_locks_enabled = self.class.type_cast_config_to_boolean(
+ config.fetch(:advisory_locks, true)
+ )
+ end
+
+ def replica?
+ @config[:replica] || false
end
def migrations_paths # :nodoc:
@@ -152,7 +162,7 @@ module ActiveRecord
# this method must only be called while holding connection pool's mutex
def lease
if in_use?
- msg = "Cannot lease connection, ".dup
+ msg = +"Cannot lease connection, "
if @owner == Thread.current
msg << "it is already leased by the current thread."
else
@@ -338,6 +348,10 @@ module ActiveRecord
false
end
+ def supports_lazy_transactions?
+ false
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
@@ -346,6 +360,10 @@ module ActiveRecord
def enable_extension(name)
end
+ def advisory_locks_enabled? # :nodoc:
+ supports_advisory_locks? && @advisory_locks_enabled
+ end
+
# This is meant to be implemented by the adapters that support advisory
# locks
#
@@ -449,6 +467,7 @@ module ActiveRecord
# This is useful for when you need to call a proprietary method such as
# PostgreSQL's lo_* methods.
def raw_connection
+ disable_lazy_transactions!
@connection
end
@@ -475,11 +494,7 @@ module ActiveRecord
end
def column_name_for_operation(operation, node) # :nodoc:
- column_name_from_arel_node(node)
- end
-
- def column_name_from_arel_node(node) # :nodoc:
- visitor.accept(node, Arel::Collectors::SQLString.new).value
+ visitor.compile(node)
end
def default_index_type?(index) # :nodoc:
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 9de8242a58..09242a0f14 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -43,9 +43,11 @@ module ActiveRecord
}
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
- private def dealloc(stmt)
- stmt.close
- end
+ private
+
+ def dealloc(stmt)
+ stmt.close
+ end
end
def initialize(connection, logger, connection_options, config)
@@ -53,8 +55,8 @@ module ActiveRecord
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
- if version < "5.1.10"
- raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.1.10."
+ if version < "5.5.8"
+ raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.5.8."
end
end
@@ -114,6 +116,14 @@ module ActiveRecord
true
end
+ def supports_longer_index_key_prefix?
+ if mariadb?
+ version >= "10.2.2"
+ else
+ version >= "5.7.9"
+ end
+ end
+
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
end
@@ -127,7 +137,7 @@ module ActiveRecord
end
def index_algorithms
- { default: "ALGORITHM = DEFAULT".dup, copy: "ALGORITHM = COPY".dup, inplace: "ALGORITHM = INPLACE".dup }
+ { default: +"ALGORITHM = DEFAULT", copy: +"ALGORITHM = COPY", inplace: +"ALGORITHM = INPLACE" }
end
# HELPER METHODS ===========================================
@@ -180,6 +190,8 @@ module ActiveRecord
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
+ materialize_transactions
+
log(sql, name) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@connection.query(sql)
@@ -239,7 +251,7 @@ module ActiveRecord
end
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
- # Charset defaults to utf8.
+ # Charset defaults to utf8mb4.
#
# Example:
# create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
@@ -248,8 +260,12 @@ module ActiveRecord
def create_database(name, options = {})
if options[:collation]
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
+ elsif options[:charset]
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}"
+ elsif supports_longer_index_key_prefix?
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`"
else
- execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
+ raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
end
end
@@ -376,7 +392,7 @@ module ActiveRecord
def add_index(table_name, column_name, options = {}) #:nodoc:
index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
- sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}".dup
+ sql = +"CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
execute add_sql_comment!(sql, comment)
end
@@ -769,7 +785,7 @@ module ActiveRecord
# https://dev.mysql.com/doc/refman/5.7/en/set-names.html
# (trailing comma because variable_assignments will always have content)
if @config[:encoding]
- encoding = "NAMES #{@config[:encoding]}".dup
+ encoding = +"NAMES #{@config[:encoding]}"
encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
encoding << ", "
end
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 204691006c..2e7a78215a 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -114,8 +114,7 @@ module ActiveRecord
class Resolver # :nodoc:
attr_reader :configurations
- # Accepts a hash two layers deep, keys on the first layer represent
- # environments such as "production". Keys must be strings.
+ # Accepts a list of db config objects.
def initialize(configurations)
@configurations = configurations
end
@@ -136,33 +135,14 @@ module ActiveRecord
# Resolver.new(configurations).resolve(:production)
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
#
- def resolve(config)
- if config
- resolve_connection config
- elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
- resolve_symbol_connection env.to_sym
+ def resolve(config_or_env, pool_name = nil)
+ if config_or_env
+ resolve_connection config_or_env, pool_name
else
raise AdapterNotSpecified
end
end
- # Expands each key in @configurations hash into fully resolved hash
- def resolve_all
- config = configurations.dup
-
- if env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
- env_config = config[env] if config[env].is_a?(Hash) && !(config[env].key?("adapter") || config[env].key?("url"))
- end
-
- config.merge! env_config if env_config
-
- config.each do |key, value|
- config[key] = resolve(value) if value
- end
-
- config
- end
-
# Returns an instance of ConnectionSpecification for a given adapter.
# Accepts a hash one layer deep that contains all connection information.
#
@@ -176,7 +156,9 @@ module ActiveRecord
# # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
#
def spec(config)
- spec = resolve(config).symbolize_keys
+ pool_name = config if config.is_a?(Symbol)
+
+ spec = resolve(config, pool_name).symbolize_keys
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
@@ -211,7 +193,6 @@ module ActiveRecord
end
private
-
# Returns fully resolved connection, accepts hash, string or symbol.
# Always returns a hash.
#
@@ -232,29 +213,42 @@ module ActiveRecord
# Resolver.new({}).resolve_connection("postgresql://localhost/foo")
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
#
- def resolve_connection(spec)
- case spec
+ def resolve_connection(config_or_env, pool_name = nil)
+ case config_or_env
when Symbol
- resolve_symbol_connection spec
+ resolve_symbol_connection config_or_env, pool_name
when String
- resolve_url_connection spec
+ resolve_url_connection config_or_env
when Hash
- resolve_hash_connection spec
+ resolve_hash_connection config_or_env
+ else
+ resolve_connection config_or_env
end
end
- # Takes the environment such as +:production+ or +:development+.
+ # Takes the environment such as +:production+ or +:development+ and a
+ # pool name the corresponds to the name given by the connection pool
+ # to the connection. That pool name is merged into the hash with the
+ # name key.
+ #
# This requires that the @configurations was initialized with a key that
# matches.
#
- # Resolver.new("production" => {}).resolve_symbol_connection(:production)
- # # => {}
+ # configurations = #<ActiveRecord::DatabaseConfigurations:0x00007fd9fdace3e0
+ # @configurations=[
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd9fdace250
+ # @env_name="production", @spec_name="primary", @config={"database"=>"my_db"}>
+ # ]>
#
- def resolve_symbol_connection(spec)
- if config = configurations[spec.to_s]
- resolve_connection(config).merge("name" => spec.to_s)
+ # Resolver.new(configurations).resolve_symbol_connection(:production, "primary")
+ # # => { "database" => "my_db" }
+ def resolve_symbol_connection(env_name, pool_name)
+ db_config = configurations.find_db_config(env_name)
+
+ if db_config
+ resolve_connection(db_config.config).merge("name" => pool_name.to_s)
else
- raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}")
+ raise(AdapterNotSpecified, "'#{env_name}' database is not configured. Available: #{configurations.configurations.map(&:env_name).join(", ")}")
end
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 d89eeb7f54..684c7042a7 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -29,6 +29,8 @@ module ActiveRecord
end
def exec_query(sql, name = "SQL", binds = [], prepare: false)
+ materialize_transactions
+
if without_prepared_statement?(binds)
execute_and_free(sql, name) do |result|
ActiveRecord::Result.new(result.fields, result.to_a) if result
@@ -41,6 +43,8 @@ module ActiveRecord
end
def exec_delete(sql, name = nil, binds = [])
+ materialize_transactions
+
if without_prepared_statement?(binds)
execute_and_free(sql, name) { @connection.affected_rows }
else
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
index be038403b8..75564a61d6 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
@@ -5,7 +5,7 @@ module ActiveRecord
module MySQL
module Quoting # :nodoc:
def quote_column_name(name)
- @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`".freeze
+ @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
end
def quote_table_name(name)
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 c9ea653b77..82ed320617 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
@@ -17,7 +17,7 @@ module ActiveRecord
end
def visit_ChangeColumnDefinition(o)
- change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}".dup
+ change_column_sql = +"CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
add_column_position!(change_column_sql, column_options(o.column))
end
@@ -64,7 +64,7 @@ module ActiveRecord
def index_in_create(table_name, column_name, options)
index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options)
- add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})".dup, comment)
+ add_sql_comment!((+"#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})"), comment)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
index 1cf210d85b..e167c01802 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -121,7 +121,7 @@ module ActiveRecord
def data_source_sql(name = nil, type: nil)
scope = quoted_scope(name, type: type)
- sql = "SELECT table_name FROM information_schema.tables".dup
+ sql = +"SELECT table_name FROM information_schema.tables"
sql << " WHERE table_schema = #{scope[:schema]}"
sql << " AND table_name = #{scope[:name]}" if scope[:name]
sql << " AND table_type = #{scope[:type]}" if scope[:type]
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 544d720428..9bdaa00336 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -3,7 +3,7 @@
require "active_record/connection_adapters/abstract_mysql_adapter"
require "active_record/connection_adapters/mysql/database_statements"
-gem "mysql2", ">= 0.4.4", "< 0.6.0"
+gem "mysql2", ">= 0.4.4"
require "mysql2"
module ActiveRecord
@@ -14,7 +14,7 @@ module ActiveRecord
config[:flags] ||= 0
if config[:flags].kind_of? Array
- config[:flags].push "FOUND_ROWS".freeze
+ config[:flags].push "FOUND_ROWS"
else
config[:flags] |= Mysql2::Client::FOUND_ROWS
end
@@ -32,7 +32,7 @@ module ActiveRecord
module ConnectionAdapters
class Mysql2Adapter < AbstractMysqlAdapter
- ADAPTER_NAME = "Mysql2".freeze
+ ADAPTER_NAME = "Mysql2"
include MySQL::DatabaseStatements
@@ -58,6 +58,10 @@ module ActiveRecord
true
end
+ def supports_lazy_transactions?
+ true
+ end
+
# HELPER METHODS ===========================================
def each_hash(result) # :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 8db2a645af..6bd6b67165 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -58,6 +58,8 @@ module ActiveRecord
# Queries the database and returns the results in an Array-like object
def query(sql, name = nil) #:nodoc:
+ materialize_transactions
+
log(sql, name) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
result_as_array @connection.async_exec(sql)
@@ -70,6 +72,8 @@ module ActiveRecord
# Note: the PG::Result object is manually memory managed; if you don't
# need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
def execute(sql, name = nil)
+ materialize_transactions
+
log(sql, name) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@connection.async_exec(sql)
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 26abeea7ed..6fbeaa2b9e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -33,7 +33,13 @@ module ActiveRecord
def cast(value)
if value.is_a?(::String)
- value = @pg_decoder.decode(value)
+ value = begin
+ @pg_decoder.decode(value)
+ rescue TypeError
+ # malformed array string is treated as [], will raise in PG 2.0 gem
+ # this keeps a consistent implementation
+ []
+ end
end
type_cast_array(value, :cast)
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
index 231278c184..83c21ba6ea 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/core_ext/array/extract"
+
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
@@ -16,12 +18,12 @@ module ActiveRecord
def run(records)
nodes = records.reject { |row| @store.key? row["oid"].to_i }
- mapped, nodes = nodes.partition { |row| @store.key? row["typname"] }
- ranges, nodes = nodes.partition { |row| row["typtype"] == "r".freeze }
- enums, nodes = nodes.partition { |row| row["typtype"] == "e".freeze }
- domains, nodes = nodes.partition { |row| row["typtype"] == "d".freeze }
- arrays, nodes = nodes.partition { |row| row["typinput"] == "array_in".freeze }
- composites, nodes = nodes.partition { |row| row["typelem"].to_i != 0 }
+ mapped = nodes.extract! { |row| @store.key? row["typname"] }
+ ranges = nodes.extract! { |row| row["typtype"] == "r" }
+ enums = nodes.extract! { |row| row["typtype"] == "e" }
+ domains = nodes.extract! { |row| row["typtype"] == "d" }
+ arrays = nodes.extract! { |row| row["typinput"] == "array_in" }
+ composites = nodes.extract! { |row| row["typelem"].to_i != 0 }
mapped.each { |row| register_mapped_type(row) }
enums.each { |row| register_enum_type(row) }
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 26b8113b0d..fae3ddbad4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -686,7 +686,7 @@ module ActiveRecord
def change_column_sql(table_name, column_name, type, options = {})
quoted_column_name = quote_column_name(column_name)
sql_type = type_to_sql(type, options)
- sql = "ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
+ sql = +"ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
end
@@ -700,6 +700,11 @@ module ActiveRecord
sql
end
+ def add_column_for_alter(table_name, column_name, type, options = {})
+ return super unless options.key?(:comment)
+ [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
+ end
+
def change_column_for_alter(table_name, column_name, type, options = {})
sqls = [change_column_sql(table_name, column_name, type, options)]
sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default)
@@ -708,7 +713,6 @@ module ActiveRecord
sqls
end
-
# Changes the default value of a table column.
def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc:
column = column_for(table_name, column_name)
@@ -753,7 +757,7 @@ module ActiveRecord
scope = quoted_scope(name, type: type)
scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
- sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup
+ sql = +"SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
sql << " WHERE n.nspname = #{scope[:schema]}"
sql << " AND c.relname = #{scope[:name]}" if scope[:name]
sql << " AND c.relkind IN (#{scope[:type]})"
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 ffd3be26b0..cd69d28139 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
@@ -17,7 +17,7 @@ module ActiveRecord
end
def sql_type
- super.gsub(/\[\]$/, "".freeze)
+ super.gsub(/\[\]$/, "")
end
def ==(other)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index fdf6f75108..bc6eb11572 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -4,6 +4,14 @@
gem "pg", ">= 0.18", "< 2.0"
require "pg"
+# Use async_exec instead of exec_params on pg versions before 1.1
+class ::PG::Connection
+ unless self.public_method_defined?(:async_exec_params)
+ remove_method :exec_params
+ alias exec_params async_exec
+ end
+end
+
require "active_record/connection_adapters/abstract_adapter"
require "active_record/connection_adapters/statement_pool"
require "active_record/connection_adapters/postgresql/column"
@@ -70,7 +78,7 @@ module ActiveRecord
# In addition, default connection parameters of libpq can be set per environment variables.
# See https://www.postgresql.org/docs/current/static/libpq-envars.html .
class PostgreSQLAdapter < AbstractAdapter
- ADAPTER_NAME = "PostgreSQL".freeze
+ ADAPTER_NAME = "PostgreSQL"
NATIVE_DATABASE_TYPES = {
primary_key: "bigserial primary key",
@@ -326,6 +334,10 @@ module ActiveRecord
postgresql_version >= 90400
end
+ def supports_lazy_transactions?
+ true
+ end
+
def get_advisory_lock(lock_id) # :nodoc:
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
@@ -436,7 +448,7 @@ module ActiveRecord
end
end
- def get_oid_type(oid, fmod, column_name, sql_type = "".freeze)
+ def get_oid_type(oid, fmod, column_name, sql_type = "")
if !type_map.key?(oid)
load_additional_types([oid])
end
@@ -525,13 +537,13 @@ module ActiveRecord
# Quoted types
when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
# The default 'now'::date is CURRENT_DATE
- if $1 == "now".freeze && $2 == "date".freeze
+ if $1 == "now" && $2 == "date"
nil
else
- $1.gsub("''".freeze, "'".freeze)
+ $1.gsub("''", "'")
end
# Boolean types
- when "true".freeze, "false".freeze
+ when "true", "false"
default
# Numeric types
when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
@@ -597,15 +609,19 @@ module ActiveRecord
end
def exec_no_cache(sql, name, binds)
+ materialize_transactions
+
type_casted_binds = type_casted_binds(binds)
log(sql, name, binds, type_casted_binds) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
- @connection.async_exec(sql, type_casted_binds)
+ @connection.exec_params(sql, type_casted_binds)
end
end
end
def exec_cache(sql, name, binds)
+ materialize_transactions
+
stmt_key = prepare_statement(sql)
type_casted_binds = type_casted_binds(binds)
@@ -639,7 +655,7 @@ module ActiveRecord
#
# Check here for more details:
# https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
- CACHED_PLAN_HEURISTIC = "cached plan must not change result type".freeze
+ CACHED_PLAN_HEURISTIC = "cached plan must not change result type"
def is_cached_plan_failure?(e)
pgerror = e.cause
code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
index abedf01f10..b2dcdb5373 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -13,7 +13,7 @@ module ActiveRecord
end
def quote_column_name(name)
- @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}").freeze
+ @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
end
def quoted_time(value)
@@ -26,19 +26,19 @@ module ActiveRecord
end
def quoted_true
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "1".freeze : "'t'".freeze
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "1" : "'t'"
end
def unquoted_true
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 1 : "t".freeze
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 1 : "t"
end
def quoted_false
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "0".freeze : "'f'".freeze
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "0" : "'f'"
end
def unquoted_false
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 0 : "f".freeze
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 0 : "f"
end
private
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
index 24e7bc65fa..48277f0ae2 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
@@ -21,19 +21,24 @@ module ActiveRecord
WHERE name = #{quote(row['name'])} AND type = 'index'
SQL
- /\sWHERE\s+(?<where>.+)$/i =~ index_sql
+ /\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?\z/i =~ index_sql
columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
col["name"]
end
- # Add info on sort order for columns (only desc order is explicitly specified, asc is
- # the default)
orders = {}
- if index_sql # index_sql can be null in case of primary key indexes
- index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column|
- orders[order_column] = :desc
- }
+
+ if columns.any?(&:nil?) # index created with an expression
+ columns = expressions
+ else
+ # Add info on sort order for columns (only desc order is explicitly specified,
+ # asc is the default)
+ if index_sql # index_sql can be null in case of primary key indexes
+ index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column|
+ orders[order_column] = :desc
+ }
+ end
end
IndexDefinition.new(
@@ -81,7 +86,7 @@ module ActiveRecord
scope = quoted_scope(name, type: type)
scope[:type] ||= "'table','view'"
- sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'".dup
+ sql = +"SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
sql << " AND name = #{scope[:name]}" if scope[:name]
sql << " AND type IN (#{scope[:type]})"
sql
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index bee74dc33d..81882f6cc1 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -56,7 +56,7 @@ module ActiveRecord
#
# * <tt>:database</tt> - Path to the database file.
class SQLite3Adapter < AbstractAdapter
- ADAPTER_NAME = "SQLite".freeze
+ ADAPTER_NAME = "SQLite"
include SQLite3::Quoting
include SQLite3::SchemaStatements
@@ -125,6 +125,10 @@ module ActiveRecord
true
end
+ def supports_expression_index?
+ sqlite_version >= "3.9.0"
+ end
+
def requires_reloading?
true
end
@@ -186,6 +190,10 @@ module ActiveRecord
true
end
+ def supports_lazy_transactions?
+ true
+ end
+
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity # :nodoc:
@@ -212,6 +220,8 @@ module ActiveRecord
end
def exec_query(sql, name = nil, binds = [], prepare: false)
+ materialize_transactions
+
type_casted_binds = type_casted_binds(binds)
log(sql, name, binds, type_casted_binds) do
@@ -252,6 +262,8 @@ module ActiveRecord
end
def execute(sql, name = nil) #:nodoc:
+ materialize_transactions
+
log(sql, name) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@connection.execute(sql)
@@ -409,12 +421,23 @@ module ActiveRecord
def alter_table(table_name, options = {})
altered_table_name = "a#{table_name}"
- caller = lambda { |definition| yield definition if block_given? }
+ foreign_keys = foreign_keys(table_name)
+
+ caller = lambda do |definition|
+ rename = options[:rename] || {}
+ foreign_keys.each do |fk|
+ if column = rename[fk.options[:column]]
+ fk.options[:column] = column
+ end
+ definition.foreign_key(fk.to_table, fk.options)
+ end
+
+ yield definition if block_given?
+ end
transaction do
disable_referential_integrity do
- move_table(table_name, altered_table_name,
- options.merge(temporary: true))
+ move_table(table_name, altered_table_name, options.merge(temporary: true))
move_table(altered_table_name, table_name, &caller)
end
end
@@ -446,6 +469,7 @@ module ActiveRecord
primary_key: column_name == from_primary_key
)
end
+
yield @definition if block_given?
end
copy_table_indexes(from, to, options[:rename] || {})
@@ -463,9 +487,12 @@ module ActiveRecord
name = name[1..-1]
end
- to_column_names = columns(to).map(&:name)
- columns = index.columns.map { |c| rename[c] || c }.select do |column|
- to_column_names.include?(column)
+ columns = index.columns
+ if columns.is_a?(Array)
+ to_column_names = columns(to).map(&:name)
+ columns = columns.map { |c| rename[c] || c }.select do |column|
+ to_column_names.include?(column)
+ end
end
unless columns.empty?
@@ -549,7 +576,7 @@ module ActiveRecord
column
end
else
- basic_structure.to_hash
+ basic_structure.to_a
end
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index ee0e651912..18114f9e1c 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -46,45 +46,18 @@ module ActiveRecord
#
# The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
# may be returned on an error.
- def establish_connection(config = nil)
+ def establish_connection(config_or_env = nil)
raise "Anonymous class is not allowed." unless name
- config ||= DEFAULT_ENV.call.to_sym
- spec_name = self == Base ? "primary" : name
- self.connection_specification_name = spec_name
+ config_or_env ||= DEFAULT_ENV.call.to_sym
+ pool_name = self == Base ? "primary" : name
+ self.connection_specification_name = pool_name
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
- spec = resolver.resolve(config).symbolize_keys
- spec[:name] = spec_name
+ config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
+ config_hash[:name] = pool_name
- # use the primary config if a config is not passed in and
- # it's a three tier config
- spec = spec[spec_name.to_sym] if spec[spec_name.to_sym]
-
- connection_handler.establish_connection(spec)
- end
-
- class MergeAndResolveDefaultUrlConfig # :nodoc:
- def initialize(raw_configurations)
- @raw_config = raw_configurations.dup
- @env = DEFAULT_ENV.call.to_s
- end
-
- # Returns fully resolved connection hashes.
- # Merges connection information from `ENV['DATABASE_URL']` if available.
- def resolve
- ConnectionAdapters::ConnectionSpecification::Resolver.new(config).resolve_all
- end
-
- private
- def config
- @raw_config.dup.tap do |cfg|
- if url = ENV["DATABASE_URL"]
- cfg[@env] ||= {}
- cfg[@env]["url"] ||= url
- end
- end
- end
+ connection_handler.establish_connection(config_hash)
end
# Returns the connection currently associated with the class. This can
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index c983bc0d93..392602bc0f 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -3,11 +3,14 @@
require "active_support/core_ext/hash/indifferent_access"
require "active_support/core_ext/string/filters"
require "concurrent/map"
+require "set"
module ActiveRecord
module Core
extend ActiveSupport::Concern
+ FILTERED = "[FILTERED]" # :nodoc:
+
included do
##
# :singleton-method:
@@ -26,7 +29,7 @@ module ActiveRecord
##
# Contains the database configuration - as is typically stored in config/database.yml -
- # as a Hash.
+ # as an ActiveRecord::DatabaseConfigurations object.
#
# For example, the following database.yml...
#
@@ -40,22 +43,18 @@ module ActiveRecord
#
# ...would result in ActiveRecord::Base.configurations to look like this:
#
- # {
- # 'development' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/development.sqlite3'
- # },
- # 'production' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/production.sqlite3'
- # }
- # }
+ # #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
+ # @spec_name="primary", @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>,
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
+ # @spec_name="primary", @config={"adapter"=>"mysql2", "database"=>"db/production.sqlite3"}>
+ # ]>
def self.configurations=(config)
- @@configurations = ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
+ @@configurations = ActiveRecord::DatabaseConfigurations.new(config)
end
self.configurations = {}
- # Returns fully resolved configurations hash
+ # Returns fully resolved ActiveRecord::DatabaseConfigurations object
def self.configurations
@@configurations
end
@@ -127,6 +126,8 @@ module ActiveRecord
class_attribute :default_connection_handler, instance_writer: false
+ self.filter_attributes = []
+
def self.connection_handler
ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
end
@@ -138,7 +139,7 @@ module ActiveRecord
self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
end
- module ClassMethods # :nodoc:
+ module ClassMethods
def initialize_find_by_cache # :nodoc:
@find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
end
@@ -215,7 +216,7 @@ module ActiveRecord
generated_association_methods
end
- def generated_association_methods
+ def generated_association_methods # :nodoc:
@generated_association_methods ||= begin
mod = const_set(:GeneratedAssociationMethods, Module.new)
private_constant :GeneratedAssociationMethods
@@ -225,8 +226,22 @@ module ActiveRecord
end
end
+ # Returns columns which shouldn't be exposed while calling +#inspect+.
+ def filter_attributes
+ if defined?(@filter_attributes)
+ @filter_attributes
+ else
+ superclass.filter_attributes
+ end
+ end
+
+ # Specifies columns which shouldn't be exposed while calling +#inspect+.
+ def filter_attributes=(attributes_names)
+ @filter_attributes = attributes_names.map(&:to_s).to_set
+ end
+
# Returns a string like 'Post(id:integer, title:string, body:text)'
- def inspect
+ def inspect # :nodoc:
if self == Base
super
elsif abstract_class?
@@ -242,7 +257,7 @@ module ActiveRecord
end
# Overwrite the default class equality method to provide support for decorated models.
- def ===(object)
+ def ===(object) # :nodoc:
object.is_a?(self)
end
@@ -496,7 +511,11 @@ module ActiveRecord
inspection = if defined?(@attributes) && @attributes
self.class.attribute_names.collect do |name|
if has_attribute?(name)
- "#{name}: #{attribute_for_inspect(name)}"
+ if filter_attribute?(name)
+ "#{name}: #{ActiveRecord::Core::FILTERED}"
+ else
+ "#{name}: #{attribute_for_inspect(name)}"
+ end
end
end.compact.join(", ")
else
@@ -514,13 +533,16 @@ module ActiveRecord
if defined?(@attributes) && @attributes
column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
pp.seplist(column_names, proc { pp.text "," }) do |column_name|
- column_value = read_attribute(column_name)
pp.breakable " "
pp.group(1) do
pp.text column_name
pp.text ":"
pp.breakable
- pp.pp column_value
+ if filter_attribute?(column_name)
+ pp.text ActiveRecord::Core::FILTERED
+ else
+ pp.pp read_attribute(column_name)
+ end
end
end
else
@@ -571,5 +593,9 @@ module ActiveRecord
def custom_inspect_method_defined?
self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
end
+
+ def filter_attribute?(attribute_name)
+ self.class.filter_attributes.include?(attribute_name) && !read_attribute(attribute_name).nil?
+ end
end
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index c7f0077a76..27c1b7a311 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -159,14 +159,11 @@ module ActiveRecord
end
private
-
- def _create_record(*)
+ def _create_record(attribute_names = self.attribute_names)
id = super
each_counter_cached_associations do |association|
- if send(association.reflection.name)
- association.increment_counters
- end
+ association.increment_counters
end
id
@@ -179,9 +176,7 @@ module ActiveRecord
each_counter_cached_associations do |association|
foreign_key = association.reflection.foreign_key.to_sym
unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
- if send(association.reflection.name)
- association.decrement_counters
- end
+ association.decrement_counters
end
end
end
diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb
index ffeed45030..fa1589511e 100644
--- a/activerecord/lib/active_record/database_configurations.rb
+++ b/activerecord/lib/active_record/database_configurations.rb
@@ -1,63 +1,186 @@
# frozen_string_literal: true
+require "active_record/database_configurations/database_config"
+require "active_record/database_configurations/hash_config"
+require "active_record/database_configurations/url_config"
+
module ActiveRecord
- module DatabaseConfigurations # :nodoc:
- class DatabaseConfig
- attr_reader :env_name, :spec_name, :config
+ # ActiveRecord::DatabaseConfigurations returns an array of DatabaseConfig
+ # objects (either a HashConfig or UrlConfig) that are constructed from the
+ # application's database configuration hash or url string.
+ class DatabaseConfigurations
+ attr_reader :configurations
+ delegate :any?, to: :configurations
+
+ def initialize(configurations = {})
+ @configurations = build_configs(configurations)
+ end
- def initialize(env_name, spec_name, config)
- @env_name = env_name
- @spec_name = spec_name
- @config = config
+ # Collects the configs for the environment and optionally the specification
+ # name passed in. To include replica configurations pass `include_replicas: true`.
+ #
+ # If a spec name is provided a single DatabaseConfig object will be
+ # returned, otherwise an array of DatabaseConfig objects will be
+ # returned that corresponds with the environment and type requested.
+ #
+ # Options:
+ #
+ # <tt>env_name:</tt> The environment name. Defaults to nil which will collect
+ # configs for all environments.
+ # <tt>spec_name:</tt> The specification name (ie primary, animals, etc.). Defaults
+ # to +nil+.
+ # <tt>include_replicas:</tt> Determines whether to include replicas in the
+ # the returned list. Most of the time we're only iterating over the write
+ # connection (i.e. migrations don't need to run for the write and read connection).
+ # Defaults to +false+.
+ def configs_for(env_name: nil, spec_name: nil, include_replicas: false)
+ configs = env_with_configs(env_name)
+
+ unless include_replicas
+ configs = configs.select do |db_config|
+ !db_config.replica?
+ end
+ end
+
+ if spec_name
+ configs.find do |db_config|
+ db_config.spec_name == spec_name
+ end
+ else
+ configs
end
end
- # Selects the config for the specified environment and specification name
+ # Returns the config hash that corresponds with the environment
+ #
+ # If the application has multiple databases `default_hash` will
+ # return the first config hash for the environment.
+ #
+ # { database: "my_db", adapter: "mysql2" }
+ def default_hash(env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s)
+ default = find_db_config(env)
+ default.config if default
+ end
+ alias :[] :default_hash
+
+ # Returns a single DatabaseConfig object based on the requested environment.
#
- # For example if passed :development, and :animals it will select the database
- # under the :development and :animals configuration level
- def self.config_for_env_and_spec(environment, specification_name, configs = ActiveRecord::Base.configurations) # :nodoc:
- configs_for(environment, configs).find do |db_config|
- db_config.spec_name == specification_name
+ # If the application has multiple databases `find_db_config` will return
+ # the first DatabaseConfig for the environment.
+ def find_db_config(env)
+ configurations.find do |db_config|
+ db_config.env_name == env.to_s ||
+ (db_config.for_current_env? && db_config.spec_name == env.to_s)
end
end
- # Collects the configs for the environment passed in.
+ # Returns the DatabaseConfigurations object as a Hash.
+ def to_h
+ configs = configurations.reverse.inject({}) do |memo, db_config|
+ memo.merge(db_config.to_legacy_hash)
+ end
+
+ Hash[configs.to_a.reverse]
+ end
+
+ # Checks if the application's configurations are empty.
#
- # If a block is given returns the specification name and configuration
- # otherwise returns an array of DatabaseConfig structs for the environment.
- def self.configs_for(env, configs = ActiveRecord::Base.configurations, &blk) # :nodoc:
- env_with_configs = db_configs(configs).select do |db_config|
- db_config.env_name == env
+ # Aliased to blank?
+ def empty?
+ configurations.empty?
+ end
+ alias :blank? :empty?
+
+ private
+ def env_with_configs(env = nil)
+ if env
+ configurations.select { |db_config| db_config.env_name == env }
+ else
+ configurations
+ end
end
- if block_given?
- env_with_configs.each do |env_with_config|
- yield env_with_config.spec_name, env_with_config.config
+ def build_configs(configs)
+ return configs.configurations if configs.is_a?(DatabaseConfigurations)
+
+ build_db_config = configs.each_pair.flat_map do |env_name, config|
+ walk_configs(env_name.to_s, "primary", config)
+ end.compact
+
+ if url = ENV["DATABASE_URL"]
+ build_url_config(url, build_db_config)
+ else
+ build_db_config
end
- else
- env_with_configs
end
- end
- # Given an env, spec and config creates DatabaseConfig structs with
- # each attribute set.
- def self.walk_configs(env_name, spec_name, config) # :nodoc:
- if config["database"] || config["url"] || config["adapter"]
- DatabaseConfig.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)
+ def walk_configs(env_name, spec_name, config)
+ case config
+ when String
+ build_db_config_from_string(env_name, spec_name, config)
+ when Hash
+ build_db_config_from_hash(env_name, spec_name, config.stringify_keys)
end
end
- end
- # Walks all the configs passed in and returns an array
- # of DatabaseConfig structs for each configuration.
- def self.db_configs(configs = ActiveRecord::Base.configurations) # :nodoc:
- configs.each_pair.flat_map do |env_name, config|
- walk_configs(env_name, "primary", config)
+ def build_db_config_from_string(env_name, spec_name, config)
+ begin
+ url = config
+ uri = URI.parse(url)
+ if uri.try(:scheme)
+ ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url)
+ end
+ rescue URI::InvalidURIError
+ ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
+ end
+ end
+
+ def build_db_config_from_hash(env_name, spec_name, config)
+ if url = config["url"]
+ config_without_url = config.dup
+ 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
+ end
+ end
+
+ def build_url_config(url, configs)
+ env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
+
+ if original_config = configs.find(&:for_current_env?)
+ if original_config.url_config?
+ configs
+ else
+ configs.map do |config|
+ ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, config.spec_name, url, config.config)
+ end
+ end
+ else
+ configs + [ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, "primary", url)]
+ end
+ end
+
+ def method_missing(method, *args, &blk)
+ if Hash.method_defined?(method)
+ ActiveSupport::Deprecation.warn \
+ "Returning a hash from ActiveRecord::Base.configurations is deprecated. Therefore calling `#{method}` on the hash is also deprecated. Please switch to using the `configs_for` method instead to collect and iterate over database configurations."
+ end
+
+ case method
+ when :each, :first
+ configurations.send(method, *args, &blk)
+ when :fetch
+ configs_for(env_name: args.first)
+ when :values
+ configurations.map(&:config)
+ else
+ super
+ end
end
- end
end
end
diff --git a/activerecord/lib/active_record/database_configurations/database_config.rb b/activerecord/lib/active_record/database_configurations/database_config.rb
new file mode 100644
index 0000000000..adc37cc439
--- /dev/null
+++ b/activerecord/lib/active_record/database_configurations/database_config.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module ActiveRecord
+ class DatabaseConfigurations
+ # ActiveRecord::Base.configurations will return either a HashConfig or
+ # UrlConfig respectively. It will never return a DatabaseConfig object,
+ # as this is the parent class for the types of database configuration objects.
+ class DatabaseConfig # :nodoc:
+ attr_reader :env_name, :spec_name
+
+ def initialize(env_name, spec_name)
+ @env_name = env_name
+ @spec_name = spec_name
+ end
+
+ def replica?
+ raise NotImplementedError
+ end
+
+ def migrations_paths
+ raise NotImplementedError
+ end
+
+ def url_config?
+ false
+ end
+
+ def to_legacy_hash
+ { env_name => config }
+ end
+
+ def for_current_env?
+ env_name == ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/database_configurations/hash_config.rb b/activerecord/lib/active_record/database_configurations/hash_config.rb
new file mode 100644
index 0000000000..c176a62458
--- /dev/null
+++ b/activerecord/lib/active_record/database_configurations/hash_config.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module ActiveRecord
+ class DatabaseConfigurations
+ # A HashConfig object is created for each database configuration entry that
+ # is created from a hash.
+ #
+ # A hash config:
+ #
+ # { "development" => { "database" => "db_name" } }
+ #
+ # Becomes:
+ #
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
+ # @env_name="development", @spec_name="primary", @config={"database"=>"db_name"}>
+ #
+ # Options are:
+ #
+ # <tt>:env_name</tt> - The Rails environment, ie "development"
+ # <tt>:spec_name</tt> - The specification name. In a standard two-tier
+ # database configuration this will default to "primary". In a multiple
+ # database three-tier database configuration this corresponds to the name
+ # used in the second tier, for example "primary_readonly".
+ # <tt>:config</tt> - The config hash. This is the hash that contains the
+ # database adapter, name, and other important information for database
+ # connections.
+ class HashConfig < DatabaseConfig
+ attr_reader :config
+
+ def initialize(env_name, spec_name, config)
+ super(env_name, spec_name)
+ @config = config
+ end
+
+ # Determines whether a database configuration is for a replica / readonly
+ # connection. If the `replica` key is present in the config, `replica?` will
+ # return +true+.
+ def replica?
+ config["replica"]
+ end
+
+ # The migrations paths for a database configuration. If the
+ # `migrations_paths` key is present in the config, `migrations_paths`
+ # will return its value.
+ def migrations_paths
+ config["migrations_paths"]
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/database_configurations/url_config.rb b/activerecord/lib/active_record/database_configurations/url_config.rb
new file mode 100644
index 0000000000..81917fc4c1
--- /dev/null
+++ b/activerecord/lib/active_record/database_configurations/url_config.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+module ActiveRecord
+ class DatabaseConfigurations
+ # A UrlConfig object is created for each database configuration
+ # entry that is created from a URL. This can either be a URL string
+ # or a hash with a URL in place of the config hash.
+ #
+ # A URL config:
+ #
+ # postgres://localhost/foo
+ #
+ # Becomes:
+ #
+ # #<ActiveRecord::DatabaseConfigurations::UrlConfig:0x00007fdc3238f340
+ # @env_name="default_env", @spec_name="primary",
+ # @config={"adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost"},
+ # @url="postgres://localhost/foo">
+ #
+ # Options are:
+ #
+ # <tt>:env_name</tt> - The Rails environment, ie "development"
+ # <tt>:spec_name</tt> - The specification name. In a standard two-tier
+ # database configuration this will default to "primary". In a multiple
+ # database three-tier database configuration this corresponds to the name
+ # used in the second tier, for example "primary_readonly".
+ # <tt>:url</tt> - The database URL.
+ # <tt>:config</tt> - The config hash. This is the hash that contains the
+ # database adapter, name, and other important information for database
+ # connections.
+ class UrlConfig < DatabaseConfig
+ attr_reader :url, :config
+
+ def initialize(env_name, spec_name, url, config = {})
+ super(env_name, spec_name)
+ @config = build_config(config, url)
+ @url = url
+ end
+
+ def url_config? # :nodoc:
+ true
+ end
+
+ # Determines whether a database configuration is for a replica / readonly
+ # connection. If the `replica` key is present in the config, `replica?` will
+ # return +true+.
+ def replica?
+ config["replica"]
+ end
+
+ # The migrations paths for a database configuration. If the
+ # `migrations_paths` key is present in the config, `migrations_paths`
+ # will return its value.
+ def migrations_paths
+ config["migrations_paths"]
+ end
+
+ private
+ def build_config(original_config, url)
+ if /^jdbc:/.match?(url)
+ hash = { "url" => url }
+ else
+ hash = ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url).to_hash
+ end
+
+ if original_config[env_name]
+ original_config[env_name].merge(hash)
+ else
+ original_config.merge(hash)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 7ccb938888..919e96cd7a 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -18,7 +18,7 @@ module ActiveRecord
# Returns a formatted string ready to be logged.
def exec_explain(queries) # :nodoc:
str = queries.map do |sql, binds|
- msg = "EXPLAIN for: #{sql}".dup
+ msg = +"EXPLAIN for: #{sql}"
unless binds.empty?
msg << " "
msg << binds.map { |attr| render_bind(attr) }.inspect
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 6e0f1a0dfb..0d1fdcfb28 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -892,6 +892,7 @@ module ActiveRecord
def fixtures(*fixture_set_names)
if fixture_set_names.first == :all
+ raise StandardError, "No fixture path found. Please set `#{self}.fixture_path`." if fixture_path.blank?
fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq
fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
else
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 6891c575c7..138fd1cf53 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -55,6 +55,10 @@ module ActiveRecord
if has_attribute?(inheritance_column)
subclass = subclass_from_attributes(attributes)
+ if subclass.nil? && scope_attributes = current_scope&.scope_for_create
+ subclass = subclass_from_attributes(scope_attributes)
+ end
+
if subclass.nil? && base_class?
subclass = subclass_from_attributes(column_defaults)
end
@@ -176,7 +180,7 @@ module ActiveRecord
# 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)
- if type_name.start_with?("::".freeze)
+ if type_name.start_with?("::")
# If the type is prefixed with a scope operator then we assume that
# the type_name is an absolute reference.
ActiveSupport::Dependencies.constantize(type_name)
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 7f096bb532..4a3a31fc95 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -61,7 +61,7 @@ module ActiveRecord
end
private
- def _create_record(attribute_names = self.attribute_names, *)
+ def _create_record(attribute_names = self.attribute_names)
if locking_enabled?
# We always want to persist the locking version, even if we don't detect
# a change from the default, since the database might have no default
@@ -165,7 +165,7 @@ module ActiveRecord
def inherited(subclass)
subclass.class_eval do
is_lock_column = ->(name, _) { lock_optimistically && name == locking_column }
- decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type|
+ decorate_matching_attribute_types(is_lock_column, "_optimistic_locking") do |type|
LockingType.new(type)
end
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 1ae6840921..6b84431343 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -4,6 +4,8 @@ module ActiveRecord
class LogSubscriber < ActiveSupport::LogSubscriber
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
+ class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
+
def self.runtime=(value)
ActiveRecord::RuntimeRegistry.sql_runtime = value
end
@@ -100,21 +102,15 @@ module ActiveRecord
end
def log_query_source
- location = extract_query_source_location(caller_locations)
-
- if location
- source = "#{location.path}:#{location.lineno}"
- source = source.sub("#{::Rails.root}/", "") if defined?(::Rails.root)
+ source = extract_query_source_location(caller)
+ if source
logger.debug(" ↳ #{source}")
end
end
- RAILS_GEM_ROOT = File.expand_path("../../..", __dir__) + "/"
- PATHS_TO_IGNORE = /\A(#{RAILS_GEM_ROOT}|#{RbConfig::CONFIG["rubylibdir"]})/
-
def extract_query_source_location(locations)
- locations.find { |line| line.absolute_path && !line.absolute_path.match?(PATHS_TO_IGNORE) }
+ backtrace_cleaner.clean(locations).first
end
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index d68ed3cad9..6e5a610642 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -140,8 +140,8 @@ module ActiveRecord
end
class ConcurrentMigrationError < MigrationError #:nodoc:
- DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running.".freeze
- RELEASE_LOCK_FAILED_MESSAGE = "Failed to release advisory lock".freeze
+ DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running."
+ RELEASE_LOCK_FAILED_MESSAGE = "Failed to release advisory lock"
def initialize(message = DEFAULT_MESSAGE)
super
@@ -161,7 +161,7 @@ module ActiveRecord
class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
def initialize(env = "production")
- msg = "You are attempting to run a destructive action against your '#{env}' database.\n".dup
+ msg = +"You are attempting to run a destructive action against your '#{env}' database.\n"
msg << "If you are sure you want to continue, run the same command with the environment variable:\n"
msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
super(msg)
@@ -170,7 +170,7 @@ module ActiveRecord
class EnvironmentMismatchError < ActiveRecordError
def initialize(current: nil, stored: nil)
- msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n".dup
+ msg = +"You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
msg << "You are running in `#{ current }` environment. "
msg << "If you are sure you want to continue, first set the environment using:\n\n"
msg << " rails db:environment:set"
@@ -678,15 +678,13 @@ module ActiveRecord
if connection.respond_to? :revert
connection.revert { yield }
else
- recorder = CommandRecorder.new(connection)
+ recorder = command_recorder
@connection = recorder
suppress_messages do
connection.revert { yield }
end
@connection = recorder.delegate
- recorder.commands.each do |cmd, args, block|
- send(cmd, *args, &block)
- end
+ recorder.replay(self)
end
end
end
@@ -891,7 +889,7 @@ module ActiveRecord
source_migrations.each do |migration|
source = File.binread(migration.filename)
inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n"
- magic_comments = "".dup
+ magic_comments = +""
loop do
# If we have a magic comment in the original migration,
# insert our comment after the first newline(end of the magic comment line)
@@ -962,6 +960,10 @@ module ActiveRecord
yield
end
end
+
+ def command_recorder
+ CommandRecorder.new(connection)
+ end
end
# MigrationProxy is used to defer loading of the actual migration classes
@@ -1169,7 +1171,7 @@ module ActiveRecord
def migrations_path=(path)
ActiveSupport::Deprecation.warn \
- "ActiveRecord::Migrator.migrations_paths= is now deprecated and will be removed in Rails 6.0." \
+ "`ActiveRecord::Migrator.migrations_path=` is now deprecated and will be removed in Rails 6.0. " \
"You can set the `migrations_paths` on the `connection` instead through the `database.yml`."
self.migrations_paths = [path]
end
@@ -1299,7 +1301,7 @@ module ActiveRecord
record_version_state_after_migrating(migration.version)
end
rescue => e
- msg = "An error has occurred, ".dup
+ msg = +"An error has occurred, "
msg << "this and " if use_transaction?(migration)
msg << "all later migrations canceled:\n\n#{e}"
raise StandardError, msg, e.backtrace
@@ -1357,7 +1359,7 @@ module ActiveRecord
end
def use_advisory_lock?
- Base.connection.supports_advisory_locks?
+ Base.connection.advisory_locks_enabled?
end
def with_advisory_lock
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 087632b10f..82f5121d94 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -108,11 +108,17 @@ module ActiveRecord
yield delegate.update_table_definition(table_name, self)
end
+ def replay(migration)
+ commands.each do |cmd, args, block|
+ migration.send(cmd, *args, &block)
+ end
+ end
+
private
module StraightReversions # :nodoc:
private
- { transaction: :transaction,
+ {
execute_block: :execute_block,
create_table: :drop_table,
create_join_table: :drop_join_table,
@@ -133,6 +139,17 @@ module ActiveRecord
include StraightReversions
+ def invert_transaction(args)
+ sub_recorder = CommandRecorder.new(delegate)
+ sub_recorder.revert { yield }
+
+ invertions_proc = proc {
+ sub_recorder.replay(self)
+ }
+
+ [:transaction, args, invertions_proc]
+ end
+
def invert_drop_table(args, &block)
if args.size == 1 && block == nil
raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)."
@@ -214,11 +231,24 @@ module ActiveRecord
end
def invert_remove_foreign_key(args)
- from_table, to_table, remove_options = args
- raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil? || to_table.is_a?(Hash)
+ from_table, options_or_to_table, options_or_nil = args
+
+ to_table = if options_or_to_table.is_a?(Hash)
+ options_or_to_table[:to_table]
+ else
+ options_or_to_table
+ end
+
+ remove_options = if options_or_to_table.is_a?(Hash)
+ options_or_to_table.except(:to_table)
+ else
+ options_or_nil
+ end
+
+ raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil?
reversed_args = [from_table, to_table]
- reversed_args << remove_options if remove_options
+ reversed_args << remove_options if remove_options.present?
[:add_foreign_key, reversed_args]
end
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index 0edaaa0cf9..8f6fcfcaea 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -16,6 +16,21 @@ module ActiveRecord
V6_0 = Current
class V5_2 < V6_0
+ module CommandRecorder
+ def invert_transaction(args, &block)
+ [:transaction, args, block]
+ end
+ end
+
+ private
+
+ def command_recorder
+ recorder = super
+ class << recorder
+ prepend CommandRecorder
+ end
+ recorder
+ end
end
class V5_1 < V5_2
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 694ff85fa1..9b985e049b 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -375,7 +375,7 @@ module ActiveRecord
# default values when instantiating the Active Record object for this table.
def column_defaults
load_schema
- @column_defaults ||= _default_attributes.to_hash
+ @column_defaults ||= _default_attributes.deep_dup.to_hash
end
def _default_attributes # :nodoc:
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 05963e5546..6eb2bfb452 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -70,17 +70,6 @@ module ActiveRecord
instantiate_instance_of(klass, attributes, column_types, &block)
end
- # Given a class, an attributes hash, +instantiate_instance_of+ returns a
- # new instance of the class. Accepts only keys as strings.
- #
- # This is private, don't call it. :)
- #
- # :nodoc:
- def instantiate_instance_of(klass, attributes, column_types = {}, &block)
- attributes = klass.attributes_builder.build_from_database(attributes, column_types)
- klass.allocate.init_from_db(attributes, &block)
- end
-
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
# The resulting object is returned whether the object was saved successfully to the database or not.
#
@@ -216,6 +205,13 @@ module ActiveRecord
end
private
+ # Given a class, an attributes hash, +instantiate_instance_of+ returns a
+ # new instance of the class. Accepts only keys as strings.
+ def instantiate_instance_of(klass, attributes, column_types = {}, &block)
+ attributes = klass.attributes_builder.build_from_database(attributes, column_types)
+ klass.allocate.init_from_db(attributes, &block)
+ end
+
# Called by +instantiate+ to decide which class to use for a new
# record instance.
#
@@ -718,7 +714,6 @@ module ActiveRecord
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
def _update_record(attribute_names = self.attribute_names)
- attribute_names &= self.class.column_names
attribute_names = attributes_for_update(attribute_names)
if attribute_names.empty?
@@ -737,10 +732,12 @@ module ActiveRecord
# Creates a record with values matching those of the instance attributes
# and returns its id.
def _create_record(attribute_names = self.attribute_names)
- attribute_names &= self.class.column_names
- attributes_values = attributes_with_values_for_create(attribute_names)
+ attribute_names = attributes_for_create(attribute_names)
+
+ new_id = self.class._insert_record(
+ attributes_with_values(attribute_names)
+ )
- new_id = self.class._insert_record(attributes_values)
self.id ||= new_id if self.class.primary_key
@new_record = false
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 009f412234..81ad9ef3a2 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -77,6 +77,10 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
end
+ initializer "active_record.backtrace_cleaner" do
+ ActiveSupport.on_load(:active_record) { LogSubscriber.backtrace_cleaner = ::Rails.backtrace_cleaner }
+ end
+
initializer "active_record.migration_error" do
if config.active_record.delete(:migration_error) == :page_load
config.app_middleware.insert_after ::ActionDispatch::Callbacks,
@@ -84,6 +88,31 @@ module ActiveRecord
end
end
+ initializer "Check for cache versioning support" do
+ config.after_initialize do |app|
+ ActiveSupport.on_load(:active_record) do
+ if app.config.active_record.cache_versioning && Rails.cache
+ unless Rails.cache.class.try(:supports_cache_versioning?)
+ raise <<-end_error
+
+You're using a cache store that doesn't support native cache versioning.
+Your best option is to upgrade to a newer version of #{Rails.cache.class}
+that supports cache versioning (#{Rails.cache.class}.supports_cache_versioning? #=> true).
+
+Next best, switch to a different cache store that does support cache versioning:
+https://guides.rubyonrails.org/caching_with_rails.html#cache-stores.
+
+To keep using the current cache store, you can turn off cache versioning entirely:
+
+ config.active_record.cache_versioning = false
+
+end_error
+ end
+ end
+ end
+ end
+ end
+
initializer "active_record.check_schema_cache_dump" do
if config.active_record.delete(:use_schema_cache_dump)
config.after_initialize do |app|
@@ -108,6 +137,14 @@ module ActiveRecord
end
end
+ initializer "active_record.define_attribute_methods" do |app|
+ config.after_initialize do
+ ActiveSupport.on_load(:active_record) do
+ descendants.each(&:define_attribute_methods) if app.config.eager_load
+ end
+ end
+ end
+
initializer "active_record.warn_on_records_fetched_greater_than" do
if config.active_record.warn_on_records_fetched_greater_than
ActiveSupport.on_load(:active_record) do
@@ -176,9 +213,7 @@ end_warning
end
initializer "active_record.set_executor_hooks" do
- ActiveSupport.on_load(:active_record) do
- ActiveRecord::QueryCache.install_executor_hooks
- end
+ ActiveRecord::QueryCache.install_executor_hooks
end
initializer "active_record.add_watchable_files" do |app|
@@ -231,5 +266,11 @@ MSG
end
end
end
+
+ initializer "active_record.set_filter_attributes" do
+ ActiveSupport.on_load(:active_record) do
+ self.filter_attributes += Rails.application.config.filter_parameters
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 8b7d18fb3d..be5ef350a9 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -26,7 +26,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
desc "Create #{spec_name} database for current environment"
task spec_name => :load_config do
- db_config = ActiveRecord::DatabaseConfigurations.config_for_env_and_spec(Rails.env, spec_name)
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
ActiveRecord::Tasks::DatabaseTasks.create(db_config.config)
end
end
@@ -45,7 +45,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
desc "Drop #{spec_name} database for current environment"
task spec_name => [:load_config, :check_protected_environments] do
- db_config = ActiveRecord::DatabaseConfigurations.config_for_env_and_spec(Rails.env, spec_name)
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
ActiveRecord::Tasks::DatabaseTasks.drop(db_config.config)
end
end
@@ -73,8 +73,8 @@ db_namespace = namespace :db do
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
task migrate: :load_config do
- ActiveRecord::DatabaseConfigurations.configs_for(Rails.env) do |spec_name, config|
- ActiveRecord::Base.establish_connection(config)
+ ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
+ ActiveRecord::Base.establish_connection(db_config.config)
ActiveRecord::Tasks::DatabaseTasks.migrate
end
db_namespace["_dump"].invoke
@@ -99,7 +99,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
desc "Migrate #{spec_name} database for current environment"
task spec_name => :load_config do
- db_config = ActiveRecord::DatabaseConfigurations.config_for_env_and_spec(Rails.env, spec_name)
+ 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.migrate
end
@@ -274,11 +274,10 @@ db_namespace = namespace :db 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::DatabaseConfigurations.configs_for(Rails.env) do |spec_name, config|
- filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(spec_name, :ruby)
+ 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(config)
+ ActiveRecord::Base.establish_connection(db_config.config)
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end
end
@@ -314,11 +313,10 @@ 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::DatabaseConfigurations.configs_for(Rails.env) do |spec_name, config|
- ActiveRecord::Base.establish_connection(config)
- filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(spec_name, :sql)
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(config, filename)
-
+ ActiveRecord::Base.configurations.configs_for(env_name: Rails.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
@@ -356,22 +354,30 @@ db_namespace = namespace :db do
begin
should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
ActiveRecord::Schema.verbose = false
- ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :ruby, ENV["SCHEMA"], "test"
+ ActiveRecord::Base.configurations.configs_for(env_name: "test").each do |db_config|
+ filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :ruby)
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config.config, :ruby, filename, "test")
+ end
ensure
if should_reconnect
- ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env])
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations.default_hash(ActiveRecord::Tasks::DatabaseTasks.env))
end
end
end
# desc "Recreate the test database from an existent structure.sql file"
task load_structure: %w(db:test:purge) do
- ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :sql, ENV["SCHEMA"], "test"
+ ActiveRecord::Base.configurations.configs_for(env_name: "test").each do |db_config|
+ filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :sql)
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config.config, :sql, filename, "test")
+ end
end
# desc "Empty the test database"
task purge: %w(load_config check_protected_environments) do
- ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations["test"]
+ ActiveRecord::Base.configurations.configs_for(env_name: "test").each do |db_config|
+ ActiveRecord::Tasks::DatabaseTasks.purge(db_config.config)
+ end
end
# desc 'Load the test schema'
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 6d2f75a3ae..b2110f727c 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -612,9 +612,21 @@ module ActiveRecord
# returns either +nil+ or the inverse association name that it finds.
def automatic_inverse_of
- if can_find_inverse_of_automatically?(self)
- inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
+ return unless can_find_inverse_of_automatically?(self)
+ inverse_name_candidates =
+ if options[:as]
+ [options[:as]]
+ else
+ active_record_name = active_record.name.demodulize
+ [active_record_name, ActiveSupport::Inflector.pluralize(active_record_name)]
+ end
+
+ inverse_name_candidates.map! do |candidate|
+ ActiveSupport::Inflector.underscore(candidate).to_sym
+ end
+
+ inverse_name_candidates.detect do |inverse_name|
begin
reflection = klass._reflect_on_association(inverse_name)
rescue NameError
@@ -623,9 +635,7 @@ module ActiveRecord
reflection = false
end
- if valid_inverse_reflection?(reflection)
- return inverse_name
- end
+ valid_inverse_reflection?(reflection)
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 2d3e1eaa08..d8cff30b88 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -62,7 +62,7 @@ module ActiveRecord
# user = users.new { |user| user.name = 'Oscar' }
# user.name # => Oscar
def new(attributes = nil, &block)
- scoping { klass.new(scope_for_create(attributes), &block) }
+ scoping { klass.new(attributes, &block) }
end
alias build new
@@ -87,11 +87,7 @@ module ActiveRecord
# users.create(name: nil) # validation on name
# # => #<User id: nil, name: nil, ...>
def create(attributes = nil, &block)
- if attributes.is_a?(Array)
- attributes.collect { |attr| create(attr, &block) }
- else
- scoping { klass.create(scope_for_create(attributes), &block) }
- end
+ scoping { klass.create(attributes, &block) }
end
# Similar to #create, but calls
@@ -101,11 +97,7 @@ module ActiveRecord
# Expects arguments in the same format as
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!].
def create!(attributes = nil, &block)
- if attributes.is_a?(Array)
- attributes.collect { |attr| create!(attr, &block) }
- else
- scoping { klass.create!(scope_for_create(attributes), &block) }
- end
+ scoping { klass.create!(attributes, &block) }
end
def first_or_create(attributes = nil, &block) # :nodoc:
@@ -315,10 +307,7 @@ module ActiveRecord
# Please check unscoped if you want to remove all previous scopes (including
# the default_scope) during the execution of a block.
def scoping
- previous, klass.current_scope = klass.current_scope(true), self unless @delegate_to_klass
- yield
- ensure
- klass.current_scope = previous unless @delegate_to_klass
+ @delegate_to_klass ? yield : klass._scoping(self) { yield }
end
def _exec_scope(*args, &block) # :nodoc:
@@ -359,15 +348,20 @@ module ActiveRecord
end
stmt = Arel::UpdateManager.new
-
- stmt.set Arel.sql(@klass.sanitize_sql_for_assignment(updates))
stmt.table(table)
- if has_join_values? || offset_value
+ if updates.is_a?(Hash)
+ stmt.set _substitute_values(updates)
+ else
+ stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
+ end
+
+ if has_join_values?
@klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
else
stmt.key = arel_attribute(primary_key)
stmt.take(arel.limit)
+ stmt.offset(arel.offset)
stmt.order(*arel.orders)
stmt.wheres = arel.constraints
end
@@ -375,26 +369,33 @@ module ActiveRecord
@klass.connection.update stmt, "#{@klass} Update All"
end
- def update(attributes) # :nodoc:
- each { |record| record.update(attributes) }
+ def update(id = :all, attributes) # :nodoc:
+ if id == :all
+ each { |record| record.update(attributes) }
+ else
+ klass.update(id, attributes)
+ end
end
def update_counters(counters) # :nodoc:
touch = counters.delete(:touch)
- updates = counters.map do |counter_name, value|
- operator = value < 0 ? "-" : "+"
- quoted_column = connection.quote_column_name(counter_name)
- "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
+ updates = {}
+ counters.each do |counter_name, value|
+ attr = arel_attribute(counter_name)
+ bind = predicate_builder.build_bind_attribute(attr.name, value.abs)
+ expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attr), 0)
+ expr = value < 0 ? expr - bind : expr + bind
+ updates[counter_name] = expr.expr
end
if touch
names = touch if touch != true
touch_updates = klass.touch_attributes_with_time(*names)
- updates << klass.sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty?
+ updates.merge!(touch_updates) unless touch_updates.empty?
end
- update_all updates.join(", ")
+ update_all updates
end
# Touches all records in the current relation without instantiating records first with the updated_at/on attributes
@@ -421,14 +422,12 @@ module ActiveRecord
# Person.where(name: 'David').touch_all
# # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
def touch_all(*names, time: nil)
- updates = touch_attributes_with_time(*names, time: time)
-
if klass.locking_enabled?
- quoted_locking_column = connection.quote_column_name(klass.locking_column)
- updates = sanitize_sql_for_assignment(updates) + ", #{quoted_locking_column} = COALESCE(#{quoted_locking_column}, 0) + 1"
+ names << { time: time }
+ update_counters(klass.locking_column => 1, touch: names)
+ else
+ update_all klass.touch_attributes_with_time(*names, time: time)
end
-
- update_all(updates)
end
# Destroys the records by instantiating each
@@ -486,9 +485,13 @@ module ActiveRecord
stmt = Arel::DeleteManager.new
stmt.from(table)
- if has_join_values? || has_limit_or_offset?
+ if has_join_values?
@klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
else
+ stmt.key = arel_attribute(primary_key)
+ stmt.take(arel.limit)
+ stmt.offset(arel.offset)
+ stmt.order(*arel.orders)
stmt.wheres = arel.constraints
end
@@ -550,10 +553,8 @@ module ActiveRecord
where_clause.to_h(relation_table_name)
end
- def scope_for_create(attributes = nil)
- scope = where_values_hash.merge!(create_with_value.stringify_keys)
- scope.merge!(attributes) if attributes
- scope
+ def scope_for_create
+ where_values_hash.merge!(create_with_value.stringify_keys)
end
# Returns true if relation needs eager loading.
@@ -636,6 +637,16 @@ module ActiveRecord
end
private
+ def _substitute_values(values)
+ values.map do |name, value|
+ attr = arel_attribute(name)
+ unless Arel.arel_node?(value)
+ type = klass.type_for_attribute(attr.name)
+ value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
+ end
+ [attr, value]
+ end
+ end
def has_join_values?
joins_values.any? || left_outer_joins_values.any?
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 40fe39fa9d..0fa5ba2e50 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -190,7 +190,7 @@ module ActiveRecord
relation = apply_join_dependency
relation.pluck(*column_names)
else
- enforce_raw_sql_whitelist(column_names)
+ disallow_raw_sql!(column_names)
relation = spawn
relation.select_values = column_names.map { |cn|
@klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 488f71cdde..8f657840f5 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -17,7 +17,7 @@ module ActiveRecord
delegate = Class.new(klass) {
include ClassSpecificRelation
}
- mangled_name = klass.name.gsub("::".freeze, "_".freeze)
+ mangled_name = klass.name.gsub("::", "_")
const_set mangled_name, delegate
private_constant mangled_name
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index c5562c1ff0..6f420fe6bb 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -338,14 +338,14 @@ module ActiveRecord
name = @klass.name
if ids.nil?
- error = "Couldn't find #{name}".dup
+ error = +"Couldn't find #{name}"
error << " with#{conditions}" if conditions
raise RecordNotFound.new(error, name, key)
elsif Array(ids).size == 1
error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
raise RecordNotFound.new(error, name, key, ids)
else
- error = "Couldn't find all #{name.pluralize} with '#{key}': ".dup
+ error = +"Couldn't find all #{name.pluralize} with '#{key}': "
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
raise RecordNotFound.new(error, name, key, ids)
@@ -398,7 +398,7 @@ module ActiveRecord
def limited_ids_for(relation)
values = @klass.connection.columns_for_distinct(
- connection.column_name_from_arel_node(arel_attribute(primary_key)),
+ connection.visitor.compile(arel_attribute(primary_key)),
relation.order_values
)
@@ -416,7 +416,7 @@ module ActiveRecord
raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
expects_array = ids.first.kind_of?(Array)
- return ids.first if expects_array && ids.first.empty?
+ return [] if expects_array && ids.first.empty?
ids = ids.flatten.compact.uniq
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 10e4779ca4..4de7465128 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -152,10 +152,10 @@ module ActiveRecord
def merge_multi_values
if other.reordering_value
# override any order specified in the original relation
- relation.reorder! other.order_values
+ relation.reorder!(*other.order_values)
elsif other.order_values.any?
# merge in order_values from relation
- relation.order! other.order_values
+ relation.order!(*other.order_values)
end
extensions = other.extensions - relation.extensions
@@ -171,9 +171,7 @@ module ActiveRecord
end
def merge_clauses
- if relation.from_clause.empty? && !other.from_clause.empty?
- relation.from_clause = other.from_clause
- end
+ relation.from_clause = other.from_clause if replace_from_clause?
where_clause = relation.where_clause.merge(other.where_clause)
relation.where_clause = where_clause unless where_clause.empty?
@@ -181,6 +179,11 @@ module ActiveRecord
having_clause = relation.having_clause.merge(other.having_clause)
relation.having_clause = having_clause unless having_clause.empty?
end
+
+ def replace_from_clause?
+ relation.from_clause.empty? && !other.from_clause.empty? &&
+ relation.klass.base_class == other.klass.base_class
+ end
end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index f734cd0ad8..b59ff912fe 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -27,7 +27,7 @@ module ActiveRecord
key
else
key = key.to_s
- key.split(".".freeze).first if key.include?(".".freeze)
+ key.split(".").first if key.include?(".")
end
end.compact
end
@@ -115,11 +115,11 @@ module ActiveRecord
def convert_dot_notation_to_hash(attributes)
dot_notation = attributes.select do |k, v|
- k.include?(".".freeze) && !v.is_a?(Hash)
+ k.include?(".") && !v.is_a?(Hash)
end
dot_notation.each_key do |key|
- table_name, column_name = key.split(".".freeze)
+ table_name, column_name = key.split(".")
value = attributes.delete(key)
attributes[table_name] ||= {}
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index 64bf83e3c1..fadb3c420d 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/core_ext/array/extract"
+
module ActiveRecord
class PredicateBuilder
class ArrayHandler # :nodoc:
@@ -11,18 +13,19 @@ module ActiveRecord
return attribute.in([]) if value.empty?
values = value.map { |x| x.is_a?(Base) ? x.id : x }
- nils, values = values.partition(&:nil?)
- ranges, values = values.partition { |v| v.is_a?(Range) }
+ nils = values.extract!(&:nil?)
+ ranges = values.extract! { |v| v.is_a?(Range) }
values_predicate =
case values.length
when 0 then NullPredicate
when 1 then predicate_builder.build(attribute, values.first)
else
- bind_values = values.map do |v|
- predicate_builder.build_bind_attribute(attribute.name, v)
- end
- attribute.in(bind_values)
+ values.map! do |v|
+ bind = predicate_builder.build_bind_attribute(attribute.name, v)
+ bind if bind.value.boundable?
+ end.compact!
+ values.empty? ? NullPredicate : attribute.in(values)
end
unless nils.empty?
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 74103d6cdd..b12497fd0a 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -953,7 +953,7 @@ module ActiveRecord
arel.having(having_clause.ast) unless having_clause.empty?
if limit_value
limit_attribute = ActiveModel::Attribute.with_cast_value(
- "LIMIT".freeze,
+ "LIMIT",
connection.sanitize_limit(limit_value),
Type.default_value,
)
@@ -961,7 +961,7 @@ module ActiveRecord
end
if offset_value
offset_attribute = ActiveModel::Attribute.with_cast_value(
- "OFFSET".freeze,
+ "OFFSET",
offset_value.to_i,
Type.default_value,
)
@@ -1147,9 +1147,9 @@ module ActiveRecord
end
order_args.flatten!
- @klass.enforce_raw_sql_whitelist(
+ @klass.disallow_raw_sql!(
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
- whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
+ permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
)
validate_order_args(order_args)
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 7f1c2fd7eb..da6d10b6ec 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -21,7 +21,7 @@ module ActiveRecord
# ]
#
# # Get an array of hashes representing the result (column => value):
- # result.to_hash
+ # result.to_a
# # => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
# {"id" => 2, "title" => "title_2", "body" => "body_2"},
# ...
@@ -65,9 +65,12 @@ module ActiveRecord
end
end
- # Returns an array of hashes representing each row record.
def to_hash
- hash_rows
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ `ActiveRecord::Result#to_hash` has been renamed to `to_a`.
+ `to_hash` is deprecated and will be removed in Rails 6.1.
+ MSG
+ to_a
end
alias :map! :map
@@ -83,6 +86,8 @@ module ActiveRecord
hash_rows
end
+ alias :to_a :to_ary
+
def [](idx)
hash_rows[idx]
end
@@ -140,6 +145,8 @@ module ActiveRecord
# We freeze the strings to prevent them getting duped when
# used as keys in ActiveRecord::Base's @attributes hash
columns = @columns.map(&:-@)
+ length = columns.length
+
@rows.map { |row|
# In the past we used Hash[columns.zip(row)]
# though elegant, the verbose way is much more efficient
@@ -148,8 +155,6 @@ module ActiveRecord
hash = {}
index = 0
- length = columns.length
-
while index < length
hash[columns[index]] = row[index]
index += 1
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index c6c268855e..3485d9e557 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -61,8 +61,8 @@ module ActiveRecord
# # => "id ASC"
def sanitize_sql_for_order(condition)
if condition.is_a?(Array) && condition.first.to_s.include?("?")
- enforce_raw_sql_whitelist([condition.first],
- whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
+ disallow_raw_sql!([condition.first],
+ permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
)
# Ensure we aren't dealing with a subclass of String that might
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index 01ac56570a..9eba1254a4 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -12,14 +12,6 @@ module ActiveRecord
end
module ClassMethods # :nodoc:
- def current_scope(skip_inherited_scope = false)
- ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
- end
-
- def current_scope=(scope)
- ScopeRegistry.set_value_for(:current_scope, self, scope)
- end
-
# Collects attributes from scopes that should be applied when creating
# an AR instance for the particular class this is called on.
def scope_attributes
@@ -30,6 +22,15 @@ module ActiveRecord
def scope_attributes?
current_scope
end
+
+ private
+ def current_scope(skip_inherited_scope = false)
+ ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
+ end
+
+ def current_scope=(scope)
+ ScopeRegistry.set_value_for(:current_scope, self, scope)
+ end
end
def populate_with_current_scope_attributes # :nodoc:
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 8c612df27a..6caf9b3251 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -31,7 +31,14 @@ module ActiveRecord
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
# }
def unscoped
- block_given? ? relation.scoping { yield } : relation
+ block_given? ? _scoping(relation) { yield } : relation
+ end
+
+ def _scoping(relation) # :nodoc:
+ previous, self.current_scope = current_scope(true), relation
+ yield
+ ensure
+ self.current_scope = previous
end
# Are there attributes associated with this scope?
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index a784001587..573d97b819 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -24,13 +24,13 @@ module ActiveRecord
# You can define a scope that applies to all finders using
# {default_scope}[rdoc-ref:Scoping::Default::ClassMethods#default_scope].
def all
- current_scope = self.current_scope
+ scope = current_scope
- if current_scope
- if self == current_scope.klass
- current_scope.clone
+ if scope
+ if self == scope.klass
+ scope.clone
else
- relation.merge!(current_scope)
+ relation.merge!(scope)
end
else
default_scoped
@@ -38,9 +38,7 @@ module ActiveRecord
end
def scope_for_association(scope = relation) # :nodoc:
- current_scope = self.current_scope
-
- if current_scope && current_scope.empty_scope?
+ if current_scope&.empty_scope?
scope
else
default_scoped(scope)
@@ -182,15 +180,13 @@ module ActiveRecord
if body.respond_to?(:to_proc)
singleton_class.send(:define_method, name) do |*args|
- scope = all
- scope = scope._exec_scope(*args, &body)
+ scope = all._exec_scope(*args, &body)
scope = scope.extending(extension) if extension
scope
end
else
singleton_class.send(:define_method, name) do |*args|
- scope = all
- scope = scope.scoping { body.call(*args) || scope }
+ scope = body.call(*args) || all
scope = scope.extending(extension) if extension
scope
end
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index b41d3504fd..1b1736dcab 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -44,7 +44,7 @@ module ActiveRecord
def initialize(values)
@values = values
@indexes = values.each_with_index.find_all { |thing, i|
- Arel::Nodes::BindParam === thing
+ Substitute === thing
}.map(&:last)
end
@@ -56,6 +56,28 @@ module ActiveRecord
end
end
+ class PartialQueryCollector
+ def initialize
+ @parts = []
+ @binds = []
+ end
+
+ def <<(str)
+ @parts << str
+ self
+ end
+
+ def add_bind(obj)
+ @binds << obj
+ @parts << Substitute.new
+ self
+ end
+
+ def value
+ [@parts, @binds]
+ end
+ end
+
def self.query(sql)
Query.new(sql)
end
@@ -64,6 +86,10 @@ module ActiveRecord
PartialQuery.new(values)
end
+ def self.partial_query_collector
+ PartialQueryCollector.new
+ end
+
class Params # :nodoc:
def bind; Substitute.new; end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index fd36c0abd2..5e29085aff 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_record/database_configurations"
+
module ActiveRecord
module Tasks # :nodoc:
class DatabaseAlreadyExists < StandardError; end # :nodoc:
@@ -101,16 +103,21 @@ module ActiveRecord
@env ||= Rails.env
end
+ def spec
+ @spec ||= "primary"
+ end
+
def seed_loader
@seed_loader ||= Rails.application
end
def current_config(options = {})
options.reverse_merge! env: env
+ options[:spec] ||= "primary"
if options.has_key?(:config)
@current_config = options[:config]
else
- @current_config ||= ActiveRecord::Base.configurations[options[:env]]
+ @current_config ||= ActiveRecord::Base.configurations.configs_for(env_name: options[:env], spec_name: options[:spec]).config
end
end
@@ -122,7 +129,7 @@ module ActiveRecord
$stderr.puts "Database '#{configuration['database']}' already exists" if verbose?
rescue Exception => error
$stderr.puts error
- $stderr.puts "Couldn't create database for #{configuration.inspect}"
+ $stderr.puts "Couldn't create '#{configuration['database']}' database. Please check your configuration."
raise
end
@@ -135,8 +142,8 @@ module ActiveRecord
end
def for_each
- databases = Rails.application.config.load_database_yaml
- database_configs = ActiveRecord::DatabaseConfigurations.configs_for(Rails.env, databases)
+ databases = Rails.application.config.database_configuration
+ 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
return if database_configs.count == 1
@@ -180,9 +187,11 @@ module ActiveRecord
scope = ENV["SCOPE"]
verbose_was, Migration.verbose = Migration.verbose, verbose?
+
Base.connection.migration_context.migrate(target_version) do |migration|
scope.blank? || scope == migration.scope
end
+
ActiveRecord::Base.clear_cache!
ensure
Migration.verbose = verbose_was
@@ -198,8 +207,8 @@ module ActiveRecord
ENV["VERSION"].to_i if ENV["VERSION"] && !ENV["VERSION"].empty?
end
- def charset_current(environment = env)
- charset ActiveRecord::Base.configurations[environment]
+ def charset_current(environment = env, specification_name = spec)
+ charset ActiveRecord::Base.configurations.configs_for(env_name: environment, spec_name: specification_name).config
end
def charset(*arguments)
@@ -207,8 +216,8 @@ module ActiveRecord
class_for_adapter(configuration["adapter"]).new(*arguments).charset
end
- def collation_current(environment = env)
- collation ActiveRecord::Base.configurations[environment]
+ def collation_current(environment = env, specification_name = spec)
+ collation ActiveRecord::Base.configurations.configs_for(env_name: environment, spec_name: specification_name).config
end
def collation(*arguments)
@@ -298,7 +307,7 @@ module ActiveRecord
def check_schema_file(filename)
unless File.exist?(filename)
- message = %{#{filename} doesn't exist yet. Run `rails db:migrate` to create it, then try again.}.dup
+ message = +%{#{filename} doesn't exist yet. Run `rails db:migrate` to create it, then try again.}
message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails.root)
Kernel.abort message
end
@@ -342,14 +351,15 @@ module ActiveRecord
environments << "test" if environment == "development"
environments.each do |env|
- ActiveRecord::DatabaseConfigurations.configs_for(env) do |spec_name, configuration|
- yield configuration, spec_name, env
+ ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config|
+ yield db_config.config, db_config.spec_name, env
end
end
end
def each_local_configuration
- ActiveRecord::Base.configurations.each_value do |configuration|
+ ActiveRecord::Base.configurations.configs_for.each do |db_config|
+ configuration = db_config.config
next unless configuration["database"]
if local_database?(configuration)
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index eddc6fa223..1c1b29b5e1 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -104,7 +104,7 @@ module ActiveRecord
end
def run_cmd_error(cmd, args, action)
- msg = "failed to execute: `#{cmd}`\n".dup
+ msg = +"failed to execute: `#{cmd}`\n"
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
msg
end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 533e6953a4..8acb11f75f 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -6,8 +6,8 @@ module ActiveRecord
module Tasks # :nodoc:
class PostgreSQLDatabaseTasks # :nodoc:
DEFAULT_ENCODING = ENV["CHARSET"] || "utf8"
- ON_ERROR_STOP_1 = "ON_ERROR_STOP=1".freeze
- SQL_COMMENT_BEGIN = "--".freeze
+ ON_ERROR_STOP_1 = "ON_ERROR_STOP=1"
+ SQL_COMMENT_BEGIN = "--"
delegate :connection, :establish_connection, :clear_active_connections!,
to: ActiveRecord::Base
@@ -82,7 +82,7 @@ module ActiveRecord
def structure_load(filename, extra_flags)
set_psql_env
- args = ["-v", ON_ERROR_STOP_1, "-q", "-f", filename]
+ args = ["-v", ON_ERROR_STOP_1, "-q", "-X", "-f", filename]
args.concat(Array(extra_flags)) if extra_flags
args << configuration["database"]
run_cmd("psql", args, "loading")
@@ -115,7 +115,7 @@ module ActiveRecord
end
def run_cmd_error(cmd, args, action)
- msg = "failed to execute:\n".dup
+ msg = +"failed to execute:\n"
msg << "#{cmd} #{args.join(' ')}\n\n"
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
msg
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
index d0bad05176..a82cea80ca 100644
--- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -67,7 +67,7 @@ module ActiveRecord
end
def run_cmd_error(cmd, args)
- msg = "failed to execute:\n".dup
+ msg = +"failed to execute:\n"
msg << "#{cmd} #{args.join(' ')}\n\n"
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
msg
diff --git a/activerecord/lib/active_record/test_databases.rb b/activerecord/lib/active_record/test_databases.rb
index 5b4efe22c9..999830ba79 100644
--- a/activerecord/lib/active_record/test_databases.rb
+++ b/activerecord/lib/active_record/test_databases.rb
@@ -5,31 +5,32 @@ require "active_support/testing/parallelization"
module ActiveRecord
module TestDatabases # :nodoc:
ActiveSupport::Testing::Parallelization.after_fork_hook do |i|
- create_and_load_schema(i, spec_name: Rails.env)
+ create_and_load_schema(i, env_name: Rails.env)
end
- ActiveSupport::Testing::Parallelization.run_cleanup_hook do |_|
- drop(spec_name: Rails.env)
+ ActiveSupport::Testing::Parallelization.run_cleanup_hook do
+ drop(env_name: Rails.env)
end
- def self.create_and_load_schema(i, spec_name:)
+ def self.create_and_load_schema(i, env_name:)
old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
- connection_spec = ActiveRecord::Base.configurations[spec_name]
-
- connection_spec["database"] += "-#{i}"
- ActiveRecord::Tasks::DatabaseTasks.create(connection_spec)
- ActiveRecord::Tasks::DatabaseTasks.load_schema(connection_spec)
+ ActiveRecord::Base.configurations.configs_for(env_name: env_name).each do |db_config|
+ db_config.config["database"] += "-#{i}"
+ ActiveRecord::Tasks::DatabaseTasks.create(db_config.config)
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config.config, ActiveRecord::Base.schema_format, nil, env_name, db_config.spec_name)
+ end
ensure
- ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env])
+ ActiveRecord::Base.establish_connection(Rails.env.to_sym)
ENV["VERBOSE"] = old
end
- def self.drop(spec_name:)
+ def self.drop(env_name:)
old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
- connection_spec = ActiveRecord::Base.configurations[spec_name]
- ActiveRecord::Tasks::DatabaseTasks.drop(connection_spec)
+ ActiveRecord::Base.configurations.configs_for(env_name: env_name).each do |db_config|
+ ActiveRecord::Tasks::DatabaseTasks.drop(db_config.config)
+ end
ensure
ENV["VERBOSE"] = old
end
diff --git a/activerecord/lib/arel.rb b/activerecord/lib/arel.rb
index 7d04e1cac6..dab785738e 100644
--- a/activerecord/lib/arel.rb
+++ b/activerecord/lib/arel.rb
@@ -35,6 +35,11 @@ module Arel # :nodoc: all
def self.star
sql "*"
end
+
+ def self.arel_node?(value)
+ value.is_a?(Arel::Node) || value.is_a?(Arel::Attribute) || value.is_a?(Arel::Nodes::SqlLiteral)
+ end
+
## Convenience Alias
Node = Arel::Nodes::Node
end
diff --git a/activerecord/lib/arel/collectors/composite.rb b/activerecord/lib/arel/collectors/composite.rb
index d040d8598d..0533544993 100644
--- a/activerecord/lib/arel/collectors/composite.rb
+++ b/activerecord/lib/arel/collectors/composite.rb
@@ -24,8 +24,7 @@ module Arel # :nodoc: all
[left.value, right.value]
end
- protected
-
+ private
attr_reader :left, :right
end
end
diff --git a/activerecord/lib/arel/collectors/plain_string.rb b/activerecord/lib/arel/collectors/plain_string.rb
index 687d7fbf2f..c0e9fff399 100644
--- a/activerecord/lib/arel/collectors/plain_string.rb
+++ b/activerecord/lib/arel/collectors/plain_string.rb
@@ -4,7 +4,7 @@ module Arel # :nodoc: all
module Collectors
class PlainString
def initialize
- @str = "".dup
+ @str = +""
end
def value
diff --git a/activerecord/lib/arel/collectors/sql_string.rb b/activerecord/lib/arel/collectors/sql_string.rb
index c293a89a74..54e1e562c2 100644
--- a/activerecord/lib/arel/collectors/sql_string.rb
+++ b/activerecord/lib/arel/collectors/sql_string.rb
@@ -15,10 +15,6 @@ module Arel # :nodoc: all
@bind_index += 1
self
end
-
- def compile(bvs)
- value
- end
end
end
end
diff --git a/activerecord/lib/arel/collectors/substitute_binds.rb b/activerecord/lib/arel/collectors/substitute_binds.rb
index 3f40eec8a8..4b894bc4b1 100644
--- a/activerecord/lib/arel/collectors/substitute_binds.rb
+++ b/activerecord/lib/arel/collectors/substitute_binds.rb
@@ -21,8 +21,7 @@ module Arel # :nodoc: all
delegate.value
end
- protected
-
+ private
attr_reader :quoter, :delegate
end
end
diff --git a/activerecord/lib/arel/delete_manager.rb b/activerecord/lib/arel/delete_manager.rb
index 2def581009..fdba937d64 100644
--- a/activerecord/lib/arel/delete_manager.rb
+++ b/activerecord/lib/arel/delete_manager.rb
@@ -2,6 +2,8 @@
module Arel # :nodoc: all
class DeleteManager < Arel::TreeManager
+ include TreeManager::StatementMethods
+
def initialize
super
@ast = Nodes::DeleteStatement.new
@@ -12,14 +14,5 @@ module Arel # :nodoc: all
@ast.relation = relation
self
end
-
- def take(limit)
- @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit
- self
- end
-
- def wheres=(list)
- @ast.wheres = list
- end
end
end
diff --git a/activerecord/lib/arel/factory_methods.rb b/activerecord/lib/arel/factory_methods.rb
index b828bc274e..83ec23e403 100644
--- a/activerecord/lib/arel/factory_methods.rb
+++ b/activerecord/lib/arel/factory_methods.rb
@@ -41,5 +41,9 @@ module Arel # :nodoc: all
def lower(column)
Nodes::NamedFunction.new "LOWER", [Nodes.build_quoted(column)]
end
+
+ def coalesce(*exprs)
+ Nodes::NamedFunction.new "COALESCE", exprs
+ end
end
end
diff --git a/activerecord/lib/arel/nodes/bind_param.rb b/activerecord/lib/arel/nodes/bind_param.rb
index 53c5563d93..91e9b2b70f 100644
--- a/activerecord/lib/arel/nodes/bind_param.rb
+++ b/activerecord/lib/arel/nodes/bind_param.rb
@@ -3,7 +3,7 @@
module Arel # :nodoc: all
module Nodes
class BindParam < Node
- attr_accessor :value
+ attr_reader :value
def initialize(value)
@value = value
diff --git a/activerecord/lib/arel/nodes/delete_statement.rb b/activerecord/lib/arel/nodes/delete_statement.rb
index eaac05e2f6..a419975335 100644
--- a/activerecord/lib/arel/nodes/delete_statement.rb
+++ b/activerecord/lib/arel/nodes/delete_statement.rb
@@ -3,8 +3,7 @@
module Arel # :nodoc: all
module Nodes
class DeleteStatement < Arel::Nodes::Node
- attr_accessor :left, :right
- attr_accessor :limit
+ attr_accessor :left, :right, :orders, :limit, :offset, :key
alias :relation :left
alias :relation= :left=
@@ -15,6 +14,10 @@ module Arel # :nodoc: all
super()
@left = relation
@right = wheres
+ @orders = []
+ @limit = nil
+ @offset = nil
+ @key = nil
end
def initialize_copy(other)
@@ -24,13 +27,17 @@ module Arel # :nodoc: all
end
def hash
- [self.class, @left, @right].hash
+ [self.class, @left, @right, @orders, @limit, @offset, @key].hash
end
def eql?(other)
self.class == other.class &&
self.left == other.left &&
- self.right == other.right
+ self.right == other.right &&
+ self.orders == other.orders &&
+ self.limit == other.limit &&
+ self.offset == other.offset &&
+ self.key == other.key
end
alias :== :eql?
end
diff --git a/activerecord/lib/arel/nodes/node.rb b/activerecord/lib/arel/nodes/node.rb
index 2b9b1e9828..8086102bde 100644
--- a/activerecord/lib/arel/nodes/node.rb
+++ b/activerecord/lib/arel/nodes/node.rb
@@ -8,16 +8,6 @@ module Arel # :nodoc: all
include Arel::FactoryMethods
include Enumerable
- if $DEBUG
- def _caller
- @caller
- end
-
- def initialize
- @caller = caller.dup
- end
- end
-
###
# Factory method to create a Nodes::Not node that has the recipient of
# the caller as a child.
diff --git a/activerecord/lib/arel/nodes/select_core.rb b/activerecord/lib/arel/nodes/select_core.rb
index 2defe61974..73461ff683 100644
--- a/activerecord/lib/arel/nodes/select_core.rb
+++ b/activerecord/lib/arel/nodes/select_core.rb
@@ -3,13 +3,12 @@
module Arel # :nodoc: all
module Nodes
class SelectCore < Arel::Nodes::Node
- attr_accessor :top, :projections, :wheres, :groups, :windows
+ attr_accessor :projections, :wheres, :groups, :windows
attr_accessor :havings, :source, :set_quantifier
def initialize
super()
@source = JoinSource.new nil
- @top = nil
# https://ronsavage.github.io/SQL/sql-92.bnf.html#set%20quantifier
@set_quantifier = nil
@@ -43,7 +42,7 @@ module Arel # :nodoc: all
def hash
[
- @source, @top, @set_quantifier, @projections,
+ @source, @set_quantifier, @projections,
@wheres, @groups, @havings, @windows
].hash
end
@@ -51,7 +50,6 @@ module Arel # :nodoc: all
def eql?(other)
self.class == other.class &&
self.source == other.source &&
- self.top == other.top &&
self.set_quantifier == other.set_quantifier &&
self.projections == other.projections &&
self.wheres == other.wheres &&
diff --git a/activerecord/lib/arel/nodes/unary.rb b/activerecord/lib/arel/nodes/unary.rb
index a3c0045897..00639304e4 100644
--- a/activerecord/lib/arel/nodes/unary.rb
+++ b/activerecord/lib/arel/nodes/unary.rb
@@ -37,7 +37,6 @@ module Arel # :nodoc: all
On
Ordering
RollUp
- Top
}.each do |name|
const_set(name, Class.new(Unary))
end
diff --git a/activerecord/lib/arel/nodes/update_statement.rb b/activerecord/lib/arel/nodes/update_statement.rb
index 5184b1180f..cfaa19e392 100644
--- a/activerecord/lib/arel/nodes/update_statement.rb
+++ b/activerecord/lib/arel/nodes/update_statement.rb
@@ -3,8 +3,7 @@
module Arel # :nodoc: all
module Nodes
class UpdateStatement < Arel::Nodes::Node
- attr_accessor :relation, :wheres, :values, :orders, :limit
- attr_accessor :key
+ attr_accessor :relation, :wheres, :values, :orders, :limit, :offset, :key
def initialize
@relation = nil
@@ -12,6 +11,7 @@ module Arel # :nodoc: all
@values = []
@orders = []
@limit = nil
+ @offset = nil
@key = nil
end
@@ -22,7 +22,7 @@ module Arel # :nodoc: all
end
def hash
- [@relation, @wheres, @values, @orders, @limit, @key].hash
+ [@relation, @wheres, @values, @orders, @limit, @offset, @key].hash
end
def eql?(other)
@@ -32,6 +32,7 @@ module Arel # :nodoc: all
self.values == other.values &&
self.orders == other.orders &&
self.limit == other.limit &&
+ self.offset == other.offset &&
self.key == other.key
end
alias :== :eql?
diff --git a/activerecord/lib/arel/select_manager.rb b/activerecord/lib/arel/select_manager.rb
index 22a04b00c6..a2b2838a3d 100644
--- a/activerecord/lib/arel/select_manager.rb
+++ b/activerecord/lib/arel/select_manager.rb
@@ -222,10 +222,8 @@ module Arel # :nodoc: all
def take(limit)
if limit
@ast.limit = Nodes::Limit.new(limit)
- @ctx.top = Nodes::Top.new(limit)
else
@ast.limit = nil
- @ctx.top = nil
end
self
end
@@ -252,9 +250,9 @@ module Arel # :nodoc: all
end
private
- def collapse(exprs, existing = nil)
- exprs = exprs.unshift(existing.expr) if existing
- exprs = exprs.compact.map { |expr|
+ def collapse(exprs)
+ exprs = exprs.compact
+ exprs.map! { |expr|
if String === expr
# FIXME: Don't do this automatically
Arel.sql(expr)
diff --git a/activerecord/lib/arel/table.rb b/activerecord/lib/arel/table.rb
index 686fcdf962..c40c68715a 100644
--- a/activerecord/lib/arel/table.rb
+++ b/activerecord/lib/arel/table.rb
@@ -104,8 +104,7 @@ module Arel # :nodoc: all
!type_caster.nil?
end
- protected
-
+ private
attr_reader :type_caster
end
end
diff --git a/activerecord/lib/arel/tree_manager.rb b/activerecord/lib/arel/tree_manager.rb
index ed47b09a37..0476399618 100644
--- a/activerecord/lib/arel/tree_manager.rb
+++ b/activerecord/lib/arel/tree_manager.rb
@@ -4,6 +4,40 @@ module Arel # :nodoc: all
class TreeManager
include Arel::FactoryMethods
+ module StatementMethods
+ def take(limit)
+ @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit
+ self
+ end
+
+ def offset(offset)
+ @ast.offset = Nodes::Offset.new(Nodes.build_quoted(offset)) if offset
+ self
+ end
+
+ def order(*expr)
+ @ast.orders = expr
+ self
+ end
+
+ def key=(key)
+ @ast.key = Nodes.build_quoted(key)
+ end
+
+ def key
+ @ast.key
+ end
+
+ def wheres=(exprs)
+ @ast.wheres = exprs
+ end
+
+ def where(expr)
+ @ast.wheres << expr
+ self
+ end
+ end
+
attr_reader :ast
def initialize
diff --git a/activerecord/lib/arel/update_manager.rb b/activerecord/lib/arel/update_manager.rb
index fe444343ba..a809dbb307 100644
--- a/activerecord/lib/arel/update_manager.rb
+++ b/activerecord/lib/arel/update_manager.rb
@@ -2,30 +2,14 @@
module Arel # :nodoc: all
class UpdateManager < Arel::TreeManager
+ include TreeManager::StatementMethods
+
def initialize
super
@ast = Nodes::UpdateStatement.new
@ctx = @ast
end
- def take(limit)
- @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit
- self
- end
-
- def key=(key)
- @ast.key = Nodes.build_quoted(key)
- end
-
- def key
- @ast.key
- end
-
- def order(*expr)
- @ast.orders = expr
- self
- end
-
###
# UPDATE +table+
def table(table)
@@ -33,15 +17,6 @@ module Arel # :nodoc: all
self
end
- def wheres=(exprs)
- @ast.wheres = exprs
- end
-
- def where(expr)
- @ast.wheres << expr
- self
- end
-
def set(values)
if String === values
@ast.values = [values]
diff --git a/activerecord/lib/arel/visitors/depth_first.rb b/activerecord/lib/arel/visitors/depth_first.rb
index bcf8f8f980..8f65d303ac 100644
--- a/activerecord/lib/arel/visitors/depth_first.rb
+++ b/activerecord/lib/arel/visitors/depth_first.rb
@@ -34,7 +34,6 @@ module Arel # :nodoc: all
alias :visit_Arel_Nodes_Ordering :unary
alias :visit_Arel_Nodes_Ascending :unary
alias :visit_Arel_Nodes_Descending :unary
- alias :visit_Arel_Nodes_Top :unary
alias :visit_Arel_Nodes_UnqualifiedColumn :unary
def function(o)
@@ -136,12 +135,10 @@ module Arel # :nodoc: all
alias :visit_Arel_Nodes_True :terminal
alias :visit_Arel_Nodes_False :terminal
alias :visit_BigDecimal :terminal
- alias :visit_Bignum :terminal
alias :visit_Class :terminal
alias :visit_Date :terminal
alias :visit_DateTime :terminal
alias :visit_FalseClass :terminal
- alias :visit_Fixnum :terminal
alias :visit_Float :terminal
alias :visit_Integer :terminal
alias :visit_NilClass :terminal
diff --git a/activerecord/lib/arel/visitors/dot.rb b/activerecord/lib/arel/visitors/dot.rb
index d352b81914..9054f0159b 100644
--- a/activerecord/lib/arel/visitors/dot.rb
+++ b/activerecord/lib/arel/visitors/dot.rb
@@ -81,7 +81,6 @@ module Arel # :nodoc: all
alias :visit_Arel_Nodes_Not :unary
alias :visit_Arel_Nodes_Offset :unary
alias :visit_Arel_Nodes_On :unary
- alias :visit_Arel_Nodes_Top :unary
alias :visit_Arel_Nodes_UnqualifiedColumn :unary
alias :visit_Arel_Nodes_Preceding :unary
alias :visit_Arel_Nodes_Following :unary
@@ -212,7 +211,6 @@ module Arel # :nodoc: all
alias :visit_TrueClass :visit_String
alias :visit_FalseClass :visit_String
alias :visit_Integer :visit_String
- alias :visit_Fixnum :visit_String
alias :visit_BigDecimal :visit_String
alias :visit_Float :visit_String
alias :visit_Symbol :visit_String
diff --git a/activerecord/lib/arel/visitors/mssql.rb b/activerecord/lib/arel/visitors/mssql.rb
index 9aedc51d15..d564e19089 100644
--- a/activerecord/lib/arel/visitors/mssql.rb
+++ b/activerecord/lib/arel/visitors/mssql.rb
@@ -12,13 +12,6 @@ module Arel # :nodoc: all
private
- # `top` wouldn't really work here. I.e. User.select("distinct first_name").limit(10) would generate
- # "select top 10 distinct first_name from users", which is invalid query! it should be
- # "select distinct top 10 first_name from users"
- def visit_Arel_Nodes_Top(o)
- ""
- end
-
def visit_Arel_Visitors_MSSQL_RowNumber(o, collector)
collector << "ROW_NUMBER() OVER (ORDER BY "
inject_join(o.children, collector, ", ") << ") as _row_num"
diff --git a/activerecord/lib/arel/visitors/mysql.rb b/activerecord/lib/arel/visitors/mysql.rb
index 37bfb661f0..eb8a449079 100644
--- a/activerecord/lib/arel/visitors/mysql.rb
+++ b/activerecord/lib/arel/visitors/mysql.rb
@@ -37,6 +37,10 @@ module Arel # :nodoc: all
visit o.expr, collector
end
+ def visit_Arel_Nodes_UnqualifiedColumn(o, collector)
+ visit o.expr, collector
+ end
+
###
# :'(
# http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214
@@ -52,14 +56,31 @@ module Arel # :nodoc: all
super
end
- def visit_Arel_Nodes_UpdateStatement(o, collector)
- collector << "UPDATE "
- collector = visit o.relation, collector
+ def visit_Arel_Nodes_Concat(o, collector)
+ collector << " CONCAT("
+ visit o.left, collector
+ collector << ", "
+ visit o.right, collector
+ collector << ") "
+ collector
+ end
+
+ def build_subselect(key, o)
+ subselect = super
- unless o.values.empty?
- collector << " SET "
- collector = inject_join o.values, collector, ", "
+ # Materialize subquery by adding distinct
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
+ subselect.distinct unless subselect.limit || subselect.offset || subselect.orders.any?
+
+ Nodes::SelectStatement.new.tap do |stmt|
+ core = stmt.cores.last
+ core.froms = Nodes::Grouping.new(subselect).as("__active_record_temp")
+ core.projections = [Arel.sql(quote_column_name(key.name))]
end
+ end
+
+ def collect_where_for(o, collector)
+ return super if o.offset
unless o.wheres.empty?
collector << " WHERE "
@@ -73,15 +94,6 @@ module Arel # :nodoc: all
maybe_visit o.limit, collector
end
-
- def visit_Arel_Nodes_Concat(o, collector)
- collector << " CONCAT("
- visit o.left, collector
- collector << ", "
- visit o.right, collector
- collector << ") "
- collector
- end
end
end
end
diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb
index 5986fd5576..0172204fc8 100644
--- a/activerecord/lib/arel/visitors/to_sql.rb
+++ b/activerecord/lib/arel/visitors/to_sql.rb
@@ -67,8 +67,8 @@ module Arel # :nodoc: all
@connection = connection
end
- def compile(node, &block)
- accept(node, Arel::Collectors::SQLString.new, &block).value
+ def compile(node, collector = Arel::Collectors::SQLString.new)
+ accept(node, collector).value
end
private
@@ -76,12 +76,8 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_DeleteStatement(o, collector)
collector << "DELETE FROM "
collector = visit o.relation, collector
- if o.wheres.any?
- collector << WHERE
- collector = inject_join o.wheres, collector, AND
- end
- maybe_visit o.limit, collector
+ collect_where_for(o, collector)
end
# FIXME: we should probably have a 2-pass visitor for this
@@ -92,17 +88,12 @@ module Arel # :nodoc: all
core.wheres = o.wheres
core.projections = [key]
stmt.limit = o.limit
+ stmt.offset = o.offset
stmt.orders = o.orders
stmt
end
def visit_Arel_Nodes_UpdateStatement(o, collector)
- if o.orders.empty? && o.limit.nil?
- wheres = o.wheres
- else
- wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])]
- end
-
collector << "UPDATE "
collector = visit o.relation, collector
unless o.values.empty?
@@ -110,12 +101,7 @@ module Arel # :nodoc: all
collector = inject_join o.values, collector, ", "
end
- unless wheres.empty?
- collector << " WHERE "
- collector = inject_join wheres, collector, " AND "
- end
-
- collector
+ collect_where_for(o, collector)
end
def visit_Arel_Nodes_InsertStatement(o, collector)
@@ -237,8 +223,6 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_SelectCore(o, collector)
collector << "SELECT"
- collector = maybe_visit o.top, collector
-
collector = maybe_visit o.set_quantifier, collector
collect_nodes_for o.projections, collector, SPACE
@@ -405,11 +389,6 @@ module Arel # :nodoc: all
visit o.expr, collector
end
- # FIXME: this does nothing on most databases, but does on MSSQL
- def visit_Arel_Nodes_Top(o, collector)
- collector
- end
-
def visit_Arel_Nodes_Lock(o, collector)
visit o.expr, collector
end
@@ -644,7 +623,7 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_Assignment(o, collector)
case o.right
- when Arel::Nodes::UnqualifiedColumn, Arel::Attributes::Attribute, Arel::Nodes::BindParam
+ when Arel::Nodes::Node, Arel::Attributes::Attribute
collector = visit o.left, collector
collector << " = "
visit o.right, collector
@@ -739,8 +718,6 @@ module Arel # :nodoc: all
end
alias :visit_Arel_Nodes_SqlLiteral :literal
- alias :visit_Bignum :literal
- alias :visit_Fixnum :literal
alias :visit_Integer :literal
def quoted(o, a)
@@ -823,6 +800,21 @@ module Arel # :nodoc: all
}
end
+ def collect_where_for(o, collector)
+ if o.orders.empty? && o.limit.nil? && o.offset.nil?
+ wheres = o.wheres
+ else
+ wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])]
+ end
+
+ unless wheres.empty?
+ collector << " WHERE "
+ collector = inject_join wheres, collector, " AND "
+ end
+
+ collector
+ end
+
def infix_value(o, collector, value)
collector = visit o.left, collector
collector << value
diff --git a/activerecord/lib/rails/generators/active_record/migration.rb b/activerecord/lib/rails/generators/active_record/migration.rb
index 4ceb502c5d..cbb88d571d 100644
--- a/activerecord/lib/rails/generators/active_record/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration.rb
@@ -25,11 +25,24 @@ module ActiveRecord
def db_migrate_path
if defined?(Rails.application) && Rails.application
- Rails.application.config.paths["db/migrate"].to_ary.first
+ configured_migrate_path || default_migrate_path
else
"db/migrate"
end
end
+
+ def default_migrate_path
+ Rails.application.config.paths["db/migrate"].to_ary.first
+ end
+
+ def configured_migrate_path
+ return unless database = options[:database]
+ config = ActiveRecord::Base.configurations.configs_for(
+ env_name: Rails.env,
+ spec_name: database,
+ )
+ config&.migrations_paths
+ end
end
end
end
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 a07b00ef79..dd79bcf542 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -8,6 +8,7 @@ module ActiveRecord
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
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."
def create_migration_file
set_local_assigns!
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 25e54f3ac8..eac504f9f1 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -14,6 +14,7 @@ module ActiveRecord
class_option :parent, type: :string, desc: "The parent class for the generated model"
class_option :indexes, type: :boolean, default: true, desc: "Add indexes for references and belongs_to columns"
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 model's migration. By default, the current environment's primary database is used."
# creates the migration file for the model.
def create_migration_file
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 59b99351d1..64c2b51f83 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "cases/helper"
+require "support/connection_helper"
require "models/book"
require "models/post"
require "models/author"
@@ -10,6 +11,7 @@ module ActiveRecord
class AdapterTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
+ @connection.materialize_transactions
end
##
@@ -225,7 +227,7 @@ module ActiveRecord
post = Post.create!(title: "foo", body: "bar")
expected = @connection.select_all("SELECT * FROM posts WHERE id = #{post.id}")
result = @connection.select_all("SELECT * FROM posts WHERE id = #{Arel::Nodes::BindParam.new(nil).to_sql}", nil, [[nil, post.id]])
- assert_equal expected.to_hash, result.to_hash
+ assert_equal expected.to_a, result.to_a
end
def test_insert_update_delete_with_legacy_binds
@@ -288,7 +290,7 @@ module ActiveRecord
def test_log_invalid_encoding
error = assert_raises RuntimeError do
@connection.send :log, "SELECT 'ы' FROM DUAL" do
- raise "ы".dup.force_encoding(Encoding::ASCII_8BIT)
+ raise (+"ы").force_encoding(Encoding::ASCII_8BIT)
end
end
@@ -299,6 +301,34 @@ module ActiveRecord
def test_supports_multi_insert_is_deprecated
assert_deprecated { @connection.supports_multi_insert? }
end
+
+ def test_column_name_length_is_deprecated
+ assert_deprecated { @connection.column_name_length }
+ end
+
+ def test_table_name_length_is_deprecated
+ assert_deprecated { @connection.table_name_length }
+ end
+
+ def test_columns_per_table_is_deprecated
+ assert_deprecated { @connection.columns_per_table }
+ end
+
+ def test_indexes_per_table_is_deprecated
+ assert_deprecated { @connection.indexes_per_table }
+ end
+
+ def test_columns_per_multicolumn_index_is_deprecated
+ assert_deprecated { @connection.columns_per_multicolumn_index }
+ end
+
+ def test_sql_query_length_is_deprecated
+ assert_deprecated { @connection.sql_query_length }
+ end
+
+ def test_joins_per_query_is_deprecated
+ assert_deprecated { @connection.joins_per_query }
+ end
end
class AdapterForeignKeyTest < ActiveRecord::TestCase
@@ -417,3 +447,27 @@ module ActiveRecord
end
end
end
+
+if ActiveRecord::Base.connection.supports_advisory_locks?
+ class AdvisoryLocksEnabledTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
+ def test_advisory_locks_enabled?
+ assert ActiveRecord::Base.connection.advisory_locks_enabled?
+
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(
+ orig_connection.merge(advisory_locks: false)
+ )
+
+ assert_not ActiveRecord::Base.connection.advisory_locks_enabled?
+
+ ActiveRecord::Base.establish_connection(
+ orig_connection.merge(advisory_locks: true)
+ )
+
+ assert ActiveRecord::Base.connection.advisory_locks_enabled?
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 6fc9df5083..261fee13eb 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -106,7 +106,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end
def test_create_mysql_database_with_encoding
- assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
+ assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8mb4`", create_database(:matt)
assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, charset: "latin1")
assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT COLLATE `utf8mb4_bin`", create_database(:matt_aimonetti, collation: "utf8mb4_bin")
end
diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
index aa870349be..c32475c683 100644
--- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -9,8 +9,8 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
repair_validations(CollationTest)
def test_columns_include_collation_different_from_table
- assert_equal "utf8_bin", CollationTest.columns_hash["string_cs_column"].collation
- assert_equal "utf8_general_ci", CollationTest.columns_hash["string_ci_column"].collation
+ assert_equal "utf8mb4_bin", CollationTest.columns_hash["string_cs_column"].collation
+ assert_equal "utf8mb4_general_ci", CollationTest.columns_hash["string_ci_column"].collation
end
def test_case_sensitive
diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
index d0c57de65d..0bdbefdfb9 100644
--- a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
@@ -32,20 +32,20 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase
end
test "add column with charset and collation" do
- @connection.add_column :charset_collations, :title, :string, charset: "utf8", collation: "utf8_bin"
+ @connection.add_column :charset_collations, :title, :string, charset: "utf8mb4", collation: "utf8mb4_bin"
column = @connection.columns(:charset_collations).find { |c| c.name == "title" }
assert_equal :string, column.type
- assert_equal "utf8_bin", column.collation
+ assert_equal "utf8mb4_bin", column.collation
end
test "change column with charset and collation" do
- @connection.add_column :charset_collations, :description, :string, charset: "utf8", collation: "utf8_unicode_ci"
- @connection.change_column :charset_collations, :description, :text, charset: "utf8", collation: "utf8_general_ci"
+ @connection.add_column :charset_collations, :description, :string, charset: "utf8mb4", collation: "utf8mb4_unicode_ci"
+ @connection.change_column :charset_collations, :description, :text, charset: "utf8mb4", collation: "utf8mb4_general_ci"
column = @connection.columns(:charset_collations).find { |c| c.name == "description" }
assert_equal :text, column.type
- assert_equal "utf8_general_ci", column.collation
+ assert_equal "utf8mb4_general_ci", column.collation
end
test "schema dump includes collation" do
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 726f58d58e..3103589186 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -104,8 +104,8 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_mysql_connection_collation_is_configured
- assert_equal "utf8_unicode_ci", @connection.show_variable("collation_connection")
- assert_equal "utf8_general_ci", ARUnit2Model.connection.show_variable("collation_connection")
+ assert_equal "utf8mb4_unicode_ci", @connection.show_variable("collation_connection")
+ assert_equal "utf8mb4_general_ci", ARUnit2Model.connection.show_variable("collation_connection")
end
def test_mysql_default_in_strict_mode
@@ -170,6 +170,8 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_logs_name_show_variable
+ ActiveRecord::Base.connection.materialize_transactions
+ @subscriber.logged.clear
@connection.show_variable "foo"
assert_equal "SCHEMA", @subscriber.logged[0][1]
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 308ad1d854..afd422881b 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -4,6 +4,8 @@ require "cases/helper"
class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
def setup
+ ActiveRecord::Base.connection.materialize_transactions
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
def execute(sql, name = nil) sql end
end
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 64bb6906cd..3988c2adca 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -49,7 +49,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
end
def test_type_cast_binary_value
- data = "\u001F\x8B".dup.force_encoding("BINARY")
+ data = (+"\u001F\x8B").force_encoding("BINARY")
assert_equal(data, @type.deserialize(data))
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 54b0dde7dc..70aa189893 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -15,8 +15,9 @@ module ActiveRecord
def setup
super
@subscriber = SQLSubscriber.new
- @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber)
@connection = ActiveRecord::Base.connection
+ @connection.materialize_transactions
+ @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber)
end
def teardown
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index 61e75e772d..75e5aaed53 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -52,10 +52,10 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
def test_money_type_cast
type = PostgresqlMoney.type_for_attribute("wealth")
- assert_equal(12345678.12, type.cast("$12,345,678.12".dup))
- assert_equal(12345678.12, type.cast("$12.345.678,12".dup))
- assert_equal(-1.15, type.cast("-$1.15".dup))
- assert_equal(-2.25, type.cast("($2.25)".dup))
+ assert_equal(12345678.12, type.cast(+"$12,345,678.12"))
+ assert_equal(12345678.12, type.cast(+"$12.345.678,12"))
+ assert_equal(-1.15, type.cast(+"-$1.15"))
+ assert_equal(-2.25, type.cast(+"($2.25)"))
end
def test_schema_dumping
@@ -65,7 +65,7 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
end
def test_create_and_update_money
- money = PostgresqlMoney.create(wealth: "987.65".dup)
+ money = PostgresqlMoney.create(wealth: +"987.65")
assert_equal 987.65, money.wealth
new_value = BigDecimal("123.45")
diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
index 0bcc214c24..ba477c63f4 100644
--- a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
@@ -101,7 +101,7 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase
@connection.extend ProgrammerMistake
assert_raises ArgumentError do
- @connection.disable_referential_integrity {}
+ @connection.disable_referential_integrity { }
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index d1d4d545a3..89052019f8 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -87,7 +87,7 @@ module ActiveRecord
def test_connection_no_db
assert_raises(ArgumentError) do
- Base.sqlite3_connection {}
+ Base.sqlite3_connection { }
end
end
@@ -167,7 +167,7 @@ module ActiveRecord
data binary
)
eosql
- str = "\x80".dup.force_encoding("ASCII-8BIT")
+ str = (+"\x80").force_encoding("ASCII-8BIT")
binary = DualEncoding.new name: "いただきます!", data: str
binary.save!
assert_equal str, binary.data
@@ -176,7 +176,7 @@ module ActiveRecord
end
def test_type_cast_should_not_mutate_encoding
- name = "hello".dup.force_encoding(Encoding::ASCII_8BIT)
+ name = (+"hello").force_encoding(Encoding::ASCII_8BIT)
Owner.create(name: name)
assert_equal Encoding::ASCII_8BIT, name.encoding
ensure
@@ -345,6 +345,42 @@ module ActiveRecord
end
end
+ if ActiveRecord::Base.connection.supports_expression_index?
+ def test_expression_index
+ with_example_table do
+ @conn.add_index "ex", "max(id, number)", name: "expression"
+ index = @conn.indexes("ex").find { |idx| idx.name == "expression" }
+ assert_equal "max(id, number)", index.columns
+ end
+ end
+
+ def test_expression_index_with_where
+ with_example_table do
+ @conn.add_index "ex", "id % 10, max(id, number)", name: "expression", where: "id > 1000"
+ index = @conn.indexes("ex").find { |idx| idx.name == "expression" }
+ assert_equal "id % 10, max(id, number)", index.columns
+ assert_equal "id > 1000", index.where
+ end
+ end
+
+ def test_complicated_expression
+ with_example_table do
+ @conn.execute "CREATE INDEX expression ON ex (id % 10, (CASE WHEN number > 0 THEN max(id, number) END))WHERE(id > 1000)"
+ index = @conn.indexes("ex").find { |idx| idx.name == "expression" }
+ assert_equal "id % 10, (CASE WHEN number > 0 THEN max(id, number) END)", index.columns
+ assert_equal "(id > 1000)", index.where
+ end
+ end
+
+ def test_not_everything_an_expression
+ with_example_table do
+ @conn.add_index "ex", "id, max(id, number)", name: "expression"
+ index = @conn.indexes("ex").find { |idx| idx.name == "expression" }
+ assert_equal "id, max(id, number)", index.columns
+ end
+ end
+ end
+
def test_primary_key
with_example_table do
assert_equal "id", @conn.primary_key("ex")
diff --git a/activerecord/test/cases/arel/collectors/sql_string_test.rb b/activerecord/test/cases/arel/collectors/sql_string_test.rb
index 380573494d..443c7eb54b 100644
--- a/activerecord/test/cases/arel/collectors/sql_string_test.rb
+++ b/activerecord/test/cases/arel/collectors/sql_string_test.rb
@@ -19,27 +19,21 @@ module Arel
collect(node).value
end
- def ast_with_binds(bv)
+ def ast_with_binds
table = Table.new(:users)
manager = Arel::SelectManager.new table
- manager.where(table[:age].eq(bv))
- manager.where(table[:name].eq(bv))
+ manager.where(table[:age].eq(Nodes::BindParam.new("hello")))
+ manager.where(table[:name].eq(Nodes::BindParam.new("world")))
manager.ast
end
def test_compile
- bv = Nodes::BindParam.new(1)
- collector = collect ast_with_binds bv
-
- sql = collector.compile ["hello", "world"]
+ sql = compile(ast_with_binds)
assert_equal 'SELECT FROM "users" WHERE "users"."age" = ? AND "users"."name" = ?', sql
end
def test_returned_sql_uses_utf8_encoding
- bv = Nodes::BindParam.new(1)
- collector = collect ast_with_binds bv
-
- sql = collector.compile ["hello", "world"]
+ sql = compile(ast_with_binds)
assert_equal sql.encoding, Encoding::UTF_8
end
end
diff --git a/activerecord/test/cases/arel/delete_manager_test.rb b/activerecord/test/cases/arel/delete_manager_test.rb
index 17a5271039..0bad02f4d2 100644
--- a/activerecord/test/cases/arel/delete_manager_test.rb
+++ b/activerecord/test/cases/arel/delete_manager_test.rb
@@ -15,6 +15,7 @@ module Arel
dm = Arel::DeleteManager.new
dm.take 10
dm.from table
+ dm.key = table[:id]
assert_match(/LIMIT 10/, dm.to_sql)
end
diff --git a/activerecord/test/cases/arel/select_manager_test.rb b/activerecord/test/cases/arel/select_manager_test.rb
index c17487ae88..5220950905 100644
--- a/activerecord/test/cases/arel/select_manager_test.rb
+++ b/activerecord/test/cases/arel/select_manager_test.rb
@@ -131,7 +131,7 @@ module Arel
right = table.alias
mgr = table.from
mgr.join(right).on("omg")
- mgr.to_sql.must_be_like %{ SELECT FROM "users" INNER JOIN "users" "users_2" ON omg }
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" INNER JOIN "users" "users_2" ON omg }
end
it "converts to sqlliterals with multiple items" do
@@ -139,7 +139,7 @@ module Arel
right = table.alias
mgr = table.from
mgr.join(right).on("omg", "123")
- mgr.to_sql.must_be_like %{ SELECT FROM "users" INNER JOIN "users" "users_2" ON omg AND 123 }
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" INNER JOIN "users" "users_2" ON omg AND 123 }
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/depth_first_test.rb b/activerecord/test/cases/arel/visitors/depth_first_test.rb
index 3baccc7316..f94ad521d7 100644
--- a/activerecord/test/cases/arel/visitors/depth_first_test.rb
+++ b/activerecord/test/cases/arel/visitors/depth_first_test.rb
@@ -33,7 +33,6 @@ module Arel
Arel::Nodes::Ordering,
Arel::Nodes::StringJoin,
Arel::Nodes::UnqualifiedColumn,
- Arel::Nodes::Top,
Arel::Nodes::Limit,
Arel::Nodes::Else,
].each do |klass|
diff --git a/activerecord/test/cases/arel/visitors/dot_test.rb b/activerecord/test/cases/arel/visitors/dot_test.rb
index 98f3bab620..6b3c132f83 100644
--- a/activerecord/test/cases/arel/visitors/dot_test.rb
+++ b/activerecord/test/cases/arel/visitors/dot_test.rb
@@ -37,7 +37,6 @@ module Arel
Arel::Nodes::Offset,
Arel::Nodes::Ordering,
Arel::Nodes::UnqualifiedColumn,
- Arel::Nodes::Top,
Arel::Nodes::Limit,
].each do |klass|
define_method("test_#{klass.name.gsub('::', '_')}") do
diff --git a/activerecord/test/cases/arel/visitors/to_sql_test.rb b/activerecord/test/cases/arel/visitors/to_sql_test.rb
index e8ac50bfa3..b6426a211e 100644
--- a/activerecord/test/cases/arel/visitors/to_sql_test.rb
+++ b/activerecord/test/cases/arel/visitors/to_sql_test.rb
@@ -221,7 +221,7 @@ module Arel
sql.must_be_like "foo AS bar"
end
- it "should visit_Bignum" do
+ it "should visit_Integer" do
compile 8787878092
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 0cc4ed7127..1fca1be181 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -452,15 +452,39 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_belongs_to_counter_with_assigning_nil
- post = Post.find(1)
- comment = Comment.find(1)
+ topic = Topic.create!(title: "debate")
+ reply = Reply.create!(title: "blah!", content: "world around!", topic: topic)
- assert_equal post.id, comment.post_id
- assert_equal 2, Post.find(post.id).comments.size
+ assert_equal topic.id, reply.parent_id
+ assert_equal 1, topic.reload.replies.size
- comment.post = nil
+ reply.topic = nil
+ reply.reload
- assert_equal 1, Post.find(post.id).comments.size
+ assert_equal topic.id, reply.parent_id
+ assert_equal 1, topic.reload.replies.size
+
+ reply.topic = nil
+ reply.save!
+
+ assert_equal 0, topic.reload.replies.size
+ end
+
+ def test_belongs_to_counter_with_assigning_new_object
+ topic = Topic.create!(title: "debate")
+ reply = Reply.create!(title: "blah!", content: "world around!", topic: topic)
+
+ assert_equal topic.id, reply.parent_id
+ assert_equal 1, topic.reload.replies_count
+
+ topic2 = reply.build_topic(title: "debate2")
+ reply.save!
+
+ assert_not_equal topic.id, reply.parent_id
+ assert_equal topic2.id, reply.parent_id
+
+ assert_equal 0, topic.reload.replies_count
+ assert_equal 1, topic2.reload.replies_count
end
def test_belongs_to_with_primary_key_counter
@@ -485,11 +509,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 0, debate2.reload.replies_count
reply.topic_with_primary_key = debate2
+ reply.save!
assert_equal 0, debate.reload.replies_count
assert_equal 1, debate2.reload.replies_count
reply.topic_with_primary_key = nil
+ reply.save!
assert_equal 0, debate.reload.replies_count
assert_equal 0, debate2.reload.replies_count
@@ -516,11 +542,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 1, Topic.find(topic2.id).replies.size
reply1.topic = nil
+ reply1.save!
assert_equal 0, Topic.find(topic1.id).replies.size
assert_equal 0, Topic.find(topic2.id).replies.size
reply1.topic = topic1
+ reply1.save!
assert_equal 1, Topic.find(topic1.id).replies.size
assert_equal 0, Topic.find(topic2.id).replies.size
@@ -550,7 +578,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_belongs_to_counter_after_save
topic = Topic.create!(title: "monday night")
- topic.replies.create!(title: "re: monday night", content: "football")
+
+ assert_queries(2) do
+ topic.replies.create!(title: "re: monday night", content: "football")
+ end
+
assert_equal 1, Topic.find(topic.id)[:replies_count]
topic.save!
@@ -584,8 +616,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
debate.touch(time: time)
debate2.touch(time: time)
- reply.parent_title = "debate"
- reply.save!
+ assert_queries(3) do
+ reply.parent_title = "debate"
+ reply.save!
+ end
assert_operator debate.reload.updated_at, :>, time
assert_operator debate2.reload.updated_at, :>, time
@@ -593,7 +627,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
debate.touch(time: time)
debate2.touch(time: time)
- reply.topic_with_primary_key = debate2
+ assert_queries(3) do
+ reply.topic_with_primary_key = debate2
+ reply.save!
+ end
assert_operator debate.reload.updated_at, :>, time
assert_operator debate2.reload.updated_at, :>, time
@@ -772,6 +809,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
reply = Reply.create(title: "re: zoom", content: "speedy quick!")
reply.topic = topic
+ reply.save!
assert_equal 1, topic.reload[:replies_count]
assert_equal 1, topic.replies.size
@@ -827,6 +865,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
silly = SillyReply.create(title: "gaga", content: "boo-boo")
silly.reply = reply
+ silly.save!
assert_equal 1, reply.reload[:replies_count]
assert_equal 1, reply.replies.size
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index e717621928..ba2104eb26 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -117,9 +117,8 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_has_many_sti_and_subclasses
- silly = SillyReply.new(title: "gaga", content: "boo-boo", parent_id: 1)
- silly.parent_id = 1
- assert silly.save
+ reply = Reply.new(title: "gaga", content: "boo-boo", parent_id: 1)
+ assert reply.save
topics = Topic.all.merge!(includes: :replies, order: ["topics.id", "replies_topics.id"]).to_a
assert_no_queries do
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 5b8d4722af..79b3b4a6ad 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -18,6 +18,7 @@ require "models/job"
require "models/subscriber"
require "models/subscription"
require "models/book"
+require "models/citation"
require "models/developer"
require "models/computer"
require "models/project"
@@ -29,6 +30,18 @@ require "models/sponsor"
require "models/mentor"
require "models/contract"
+class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
+ fixtures :citations
+
+ def test_preloading_too_many_ids
+ assert_equal Citation.count, Citation.preload(:citations).to_a.size
+ end
+
+ def test_eager_loading_too_may_ids
+ assert_equal Citation.count, Citation.eager_load(:citations).offset(0).size
+ end
+end
+
class EagerAssociationTest < ActiveRecord::TestCase
fixtures :posts, :comments, :authors, :essays, :author_addresses, :categories, :categories_posts,
:companies, :accounts, :tags, :taggings, :people, :readers, :categorizations,
@@ -1571,8 +1584,9 @@ class EagerAssociationTest < ActiveRecord::TestCase
# CollectionProxy#reader is expensive, so the preloader avoids calling it.
test "preloading has_many_through association avoids calling association.reader" do
- ActiveRecord::Associations::HasManyAssociation.any_instance.expects(:reader).never
- Author.preload(:readonly_comments).first!
+ assert_not_called_on_instance_of(ActiveRecord::Associations::HasManyAssociation, :reader) do
+ Author.preload(:readonly_comments).first!
+ end
end
test "preloading through a polymorphic association doesn't require the association to exist" do
@@ -1604,6 +1618,32 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ # Associations::Preloader#preloaders_on works with hash-like objects
+ test "preloading works with an object that responds to :to_hash" do
+ CustomHash = Class.new(Hash)
+
+ assert_nothing_raised do
+ Post.preload(CustomHash.new(comments: [{ author: :essays }])).first
+ end
+ end
+
+ # Associations::Preloader#preloaders_on works with string-like objects
+ test "preloading works with an object that responds to :to_str" do
+ CustomString = Class.new(String)
+
+ assert_nothing_raised do
+ Post.preload(CustomString.new("comments")).first
+ end
+ end
+
+ # Associations::Preloader#preloaders_on does not work with ranges
+ test "preloading fails when Range is passed" do
+ exception = assert_raises(ArgumentError) do
+ Post.preload(1..10).first
+ end
+ assert_equal("1..10 was not recognized for preload", exception.message)
+ end
+
private
def find_all_ordered(klass, include = nil)
klass.order("#{klass.table_name}.#{klass.primary_key}").includes(include).to_a
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 5eacb5a3d8..aef8f31112 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -89,6 +89,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
private
def extend!(model)
- ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) {}
+ ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) { }
end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index f414fbf64b..482302055d 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
@@ -781,7 +781,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_has_many_through_polymorphic_has_manys_works
- assert_equal [10, 20].to_set, pirates(:redbeard).treasure_estimates.map(&:price).to_set
+ assert_equal ["$10.00", "$20.00"].to_set, pirates(:redbeard).treasure_estimates.map(&:price).to_set
end
def test_symbols_as_keys
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 0ca902385a..0b44515e00 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -27,7 +27,6 @@ require "models/categorization"
require "models/minivan"
require "models/speedometer"
require "models/reference"
-require "models/job"
require "models/college"
require "models/student"
require "models/pirate"
@@ -114,7 +113,7 @@ end
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :author_addresses, :comments,
- :posts, :readers, :taggings, :cars, :jobs, :tags,
+ :posts, :readers, :taggings, :cars, :tags,
:categorizations, :zines, :interests
def setup
@@ -377,6 +376,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal invoice.id, line_item.invoice_id
end
+ class SpecialAuthor < ActiveRecord::Base
+ self.table_name = "authors"
+ has_many :books, class_name: "SpecialBook", foreign_key: :author_id
+ end
+
+ class SpecialBook < ActiveRecord::Base
+ self.table_name = "books"
+
+ belongs_to :author
+ enum read_status: { unread: 0, reading: 2, read: 3, forgotten: nil }
+ end
+
+ def test_association_enum_works_properly
+ author = SpecialAuthor.create!(name: "Test")
+ book = SpecialBook.create!(read_status: "reading")
+ author.books << book
+
+ assert_equal "reading", book.read_status
+ assert_not_equal 0, SpecialAuthor.joins(:books).where(books: { read_status: "reading" }).count
+ end
+
# When creating objects on the association, we must not do it within a scope (even though it
# would be convenient), because this would cause that scope to be applied to any callbacks etc.
def test_build_and_create_should_not_happen_within_scope
@@ -1177,6 +1197,38 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, topic.reload.replies.size
end
+ def test_counter_cache_updates_in_memory_after_update_with_inverse_of_disabled
+ topic = Topic.create!(title: "Zoom-zoom-zoom")
+
+ assert_equal 0, topic.replies_count
+
+ reply1 = Reply.create!(title: "re: zoom", content: "speedy quick!")
+ reply2 = Reply.create!(title: "re: zoom 2", content: "OMG lol!")
+
+ assert_queries(4) do
+ topic.replies << [reply1, reply2]
+ end
+
+ assert_equal 2, topic.replies_count
+ assert_equal 2, topic.reload.replies_count
+ end
+
+ def test_counter_cache_updates_in_memory_after_update_with_inverse_of_enabled
+ category = Category.create!(name: "Counter Cache")
+
+ assert_nil category.categorizations_count
+
+ categorization1 = Categorization.create!
+ categorization2 = Categorization.create!
+
+ assert_queries(4) do
+ category.categorizations << [categorization1, categorization2]
+ end
+
+ assert_equal 2, category.categorizations_count
+ assert_equal 2, category.reload.categorizations_count
+ end
+
def test_pushing_association_updates_counter_cache
topic = Topic.order("id ASC").first
reply = Reply.create!
@@ -2134,21 +2186,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_defining_has_many_association_with_delete_all_dependency_lazily_evaluates_target_class
- ActiveRecord::Reflection::AssociationReflection.any_instance.expects(:class_name).never
- class_eval(<<-EOF, __FILE__, __LINE__ + 1)
- class DeleteAllModel < ActiveRecord::Base
- has_many :nonentities, :dependent => :delete_all
- end
- EOF
+ assert_not_called_on_instance_of(
+ ActiveRecord::Reflection::AssociationReflection,
+ :class_name,
+ ) do
+ class_eval(<<-EOF, __FILE__, __LINE__ + 1)
+ class DeleteAllModel < ActiveRecord::Base
+ has_many :nonentities, :dependent => :delete_all
+ end
+ EOF
+ end
end
def test_defining_has_many_association_with_nullify_dependency_lazily_evaluates_target_class
- ActiveRecord::Reflection::AssociationReflection.any_instance.expects(:class_name).never
- class_eval(<<-EOF, __FILE__, __LINE__ + 1)
- class NullifyModel < ActiveRecord::Base
- has_many :nonentities, :dependent => :nullify
- end
- EOF
+ assert_not_called_on_instance_of(
+ ActiveRecord::Reflection::AssociationReflection,
+ :class_name,
+ ) do
+ class_eval(<<-EOF, __FILE__, __LINE__ + 1)
+ class NullifyModel < ActiveRecord::Base
+ has_many :nonentities, :dependent => :nullify
+ end
+ EOF
+ end
end
def test_attributes_are_being_set_when_initialized_from_has_many_association_with_where_clause
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 d5573b6d02..442f4a93d4 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -71,6 +71,15 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
club1.members.sort_by(&:id)
end
+ def test_preload_multiple_instances_of_the_same_record
+ club = Club.create!(name: "Aaron cool banana club")
+ Membership.create! club: club, member: Member.create!(name: "Aaron")
+ Membership.create! club: club, member: Member.create!(name: "Bob")
+
+ preloaded_clubs = Club.joins(:memberships).preload(:membership).to_a
+ assert_no_queries { preloaded_clubs.each(&:membership) }
+ end
+
def test_ordered_has_many_through
person_prime = Class.new(ActiveRecord::Base) do
def self.name; "Person"; end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index d7e898a1c0..9eea34d2b9 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -661,6 +661,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
self.table_name = "books"
belongs_to :author, class_name: "SpecialAuthor"
has_one :subscription, class_name: "SpecialSupscription", foreign_key: "subscriber_id"
+
+ enum status: [:proposed, :written, :published]
end
class SpecialAuthor < ActiveRecord::Base
@@ -678,6 +680,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
book = SpecialBook.create!(status: "published")
author.book = book
+ assert_equal "published", book.status
assert_not_equal 0, SpecialAuthor.joins(:book).where(books: { status: "published" }).count
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index da3a42e2b5..eb4dc73423 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -20,6 +20,8 @@ require "models/company"
require "models/project"
require "models/author"
require "models/post"
+require "models/department"
+require "models/hotel"
class AutomaticInverseFindingTests < ActiveRecord::TestCase
fixtures :ratings, :comments, :cars
@@ -724,6 +726,16 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
# fails because Interest does have the correct inverse_of
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Interest.first }
end
+
+ def test_favors_has_one_associations_for_inverse_of
+ inverse_name = Post.reflect_on_association(:author).inverse_of.name
+ assert_equal :post, inverse_name
+ end
+
+ def test_finds_inverse_of_for_plural_associations
+ inverse_name = Department.reflect_on_association(:hotel).inverse_of.name
+ assert_equal :departments, inverse_name
+ end
end
# NOTE - these tests might not be meaningful, ripped as they were from the parental_control plugin
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 739eb02e0c..081da95df7 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -80,7 +80,7 @@ class AssociationsTest < ActiveRecord::TestCase
def test_force_reload
firm = Firm.new("name" => "A New Firm, Inc")
firm.save
- firm.clients.each {} # forcing to load all clients
+ firm.clients.each { } # forcing to load all clients
assert firm.clients.empty?, "New firm shouldn't have client objects"
assert_equal 0, firm.clients.size, "New firm should have 0 clients"
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 3bc56694be..2632aec7ab 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -148,6 +148,20 @@ module ActiveRecord
assert_equal 2, klass.new.counter
end
+ test "procs for default values are evaluated even after column_defaults is called" do
+ klass = Class.new(OverloadedType) do
+ @@counter = 0
+ attribute :counter, :integer, default: -> { @@counter += 1 }
+ end
+
+ assert_equal 1, klass.new.counter
+
+ # column_defaults will increment the counter since the proc is called
+ klass.column_defaults
+
+ assert_equal 3, klass.new.counter
+ end
+
test "procs are memoized before type casting" do
klass = Class.new(OverloadedType) do
@@counter = 0
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index ade1f4b44d..db3a58eba9 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -14,6 +14,7 @@ require "models/line_item"
require "models/order"
require "models/parrot"
require "models/pirate"
+require "models/project"
require "models/ship"
require "models/ship_part"
require "models/tag"
@@ -558,6 +559,13 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
assert_equal no_of_clients + 1, Client.count
end
+ def test_parent_should_save_children_record_with_foreign_key_validation_set_in_before_save_callback
+ company = NewlyContractedCompany.new(name: "test")
+
+ assert company.save
+ assert_not_empty company.reload.new_contracts
+ end
+
def test_parent_should_not_get_saved_with_duplicate_children_records
assert_no_difference "Reply.count" do
assert_no_difference "SillyUniqueReply.count" do
@@ -568,7 +576,13 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
])
assert_not reply.save
- assert_not_empty reply.errors
+ assert_equal ["is invalid"], reply.errors[:silly_unique_replies]
+ assert_empty reply.silly_unique_replies.first.errors
+
+ assert_equal(
+ ["has already been taken"],
+ reply.silly_unique_replies.last.errors[:content]
+ )
end
end
end
@@ -1774,3 +1788,21 @@ class TestAutosaveAssociationOnAHasManyAssociationWithInverse < ActiveRecord::Te
assert_equal 1, comment.post_comments_count
end
end
+
+class TestAutosaveAssociationOnAHasManyAssociationDefinedInSubclassWithAcceptsNestedAttributes < ActiveRecord::TestCase
+ def test_should_update_children_when_asssociation_redefined_in_subclass
+ agency = Agency.create!(name: "Agency")
+ valid_project = Project.create!(firm: agency, name: "Initial")
+ agency.update!(
+ "projects_attributes" => {
+ "0" => {
+ "name" => "Updated",
+ "id" => valid_project.id
+ }
+ }
+ )
+ valid_project.reload
+
+ assert_equal "Updated", valid_project.name
+ end
+end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index d216fe16fa..09e5517449 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -282,11 +282,13 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_initialize_with_invalid_attribute
- Topic.new("title" => "test",
- "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31")
- rescue ActiveRecord::MultiparameterAssignmentErrors => ex
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ Topic.new("title" => "test",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00")
+ end
+
assert_equal(1, ex.errors.size)
- assert_equal("last_read", ex.errors[0].attribute)
+ assert_equal("written_on", ex.errors[0].attribute)
end
def test_create_after_initialize_without_block
@@ -853,8 +855,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal company, Company.find(company.id)
end
- # TODO: extend defaults tests to other databases!
- if current_adapter?(:PostgreSQLAdapter)
+ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter, :SQLite3Adapter)
def test_default
with_timezone_config default: :local do
default = Default.new
@@ -866,7 +867,10 @@ class BasicsTest < ActiveRecord::TestCase
# char types
assert_equal "Y", default.char1
assert_equal "a varchar field", default.char2
- assert_equal "a text field", default.char3
+ # Mysql text type can't have default value
+ unless current_adapter?(:Mysql2Adapter)
+ assert_equal "a text field", default.char3
+ end
end
end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index c8163901c6..d21218a997 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -24,7 +24,7 @@ class EachTest < ActiveRecord::TestCase
def test_each_should_not_return_query_chain_and_execute_only_one_query
assert_queries(1) do
- result = Post.find_each(batch_size: 100000) {}
+ result = Post.find_each(batch_size: 100000) { }
assert_nil result
end
end
@@ -155,7 +155,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified
- not_a_post = "not a post".dup
+ not_a_post = +"not a post"
def not_a_post.id; end
not_a_post.stub(:id, -> { raise StandardError.new("not_a_post had #id called on it") }) do
assert_nothing_raised do
@@ -183,7 +183,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_error_on_ignore_the_order
assert_raise(ArgumentError) do
- PostWithDefaultScope.find_in_batches(error_on_ignore: true) {}
+ PostWithDefaultScope.find_in_batches(error_on_ignore: true) { }
end
end
@@ -192,7 +192,7 @@ class EachTest < ActiveRecord::TestCase
prev = ActiveRecord::Base.error_on_ignored_order
ActiveRecord::Base.error_on_ignored_order = true
assert_nothing_raised do
- PostWithDefaultScope.find_in_batches(error_on_ignore: false) {}
+ PostWithDefaultScope.find_in_batches(error_on_ignore: false) { }
end
ensure
# Set back to default
@@ -204,7 +204,7 @@ class EachTest < ActiveRecord::TestCase
prev = ActiveRecord::Base.error_on_ignored_order
ActiveRecord::Base.error_on_ignored_order = true
assert_raise(ArgumentError) do
- PostWithDefaultScope.find_in_batches() {}
+ PostWithDefaultScope.find_in_batches() { }
end
ensure
# Set back to default
@@ -213,7 +213,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_not_error_by_default
assert_nothing_raised do
- PostWithDefaultScope.find_in_batches() {}
+ PostWithDefaultScope.find_in_batches() { }
end
end
@@ -228,7 +228,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_not_modify_passed_options
assert_nothing_raised do
- Post.find_in_batches({ batch_size: 42, start: 1 }.freeze) {}
+ Post.find_in_batches({ batch_size: 42, start: 1 }.freeze) { }
end
end
@@ -420,7 +420,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified
- not_a_post = "not a post".dup
+ not_a_post = +"not a post"
def not_a_post.id
raise StandardError.new("not_a_post had #id called on it")
end
@@ -446,7 +446,7 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_should_not_modify_passed_options
assert_nothing_raised do
- Post.in_batches({ of: 42, start: 1 }.freeze) {}
+ Post.in_batches({ of: 42, start: 1 }.freeze) { }
end
end
@@ -597,15 +597,15 @@ class EachTest < ActiveRecord::TestCase
table: table_alias,
predicate_builder: predicate_builder
)
- posts.find_each {}
+ posts.find_each { }
end
end
test ".find_each bypasses the query cache for its own queries" do
Post.cache do
assert_queries(2) do
- Post.find_each {}
- Post.find_each {}
+ Post.find_each { }
+ Post.find_each { }
end
end
end
@@ -624,8 +624,8 @@ class EachTest < ActiveRecord::TestCase
test ".find_in_batches bypasses the query cache for its own queries" do
Post.cache do
assert_queries(2) do
- Post.find_in_batches {}
- Post.find_in_batches {}
+ Post.find_in_batches { }
+ Post.find_in_batches { }
end
end
end
@@ -644,8 +644,8 @@ class EachTest < ActiveRecord::TestCase
test ".in_batches bypasses the query cache for its own queries" do
Post.cache do
assert_queries(2) do
- Post.in_batches {}
- Post.in_batches {}
+ Post.in_batches { }
+ Post.in_batches { }
end
end
end
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index d5376ece69..58abdb47f7 100644
--- a/activerecord/test/cases/binary_test.rb
+++ b/activerecord/test/cases/binary_test.rb
@@ -12,7 +12,7 @@ unless current_adapter?(:DB2Adapter)
FIXTURES = %w(flowers.jpg example.log test.txt)
def test_mixed_encoding
- str = "\x80".dup
+ str = +"\x80"
str.force_encoding("ASCII-8BIT")
binary = Binary.new name: "いただきます!", data: str
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 91cc49385c..9c1f7aaef2 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -34,6 +34,12 @@ if ActiveRecord::Base.connection.prepared_statements
ActiveSupport::Notifications.unsubscribe(@subscription)
end
+ def test_too_many_binds
+ bind_params_length = @connection.send(:bind_params_length)
+ topics = Topic.where(id: (1 .. bind_params_length + 1).to_a)
+ assert_equal Topic.count, topics.count
+ end
+
def test_bind_from_join_in_subquery
subquery = Author.joins(:thinking_posts).where(name: "David")
scope = Author.from(subquery, "authors").where(id: 1)
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 253c3099d6..0ea3fb86a6 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -480,7 +480,7 @@ class CallbacksTest < ActiveRecord::TestCase
def test_before_save_doesnt_allow_on_option
exception = assert_raises ArgumentError do
Class.new(ActiveRecord::Base) do
- before_save(on: :create) {}
+ before_save(on: :create) { }
end
end
assert_equal "Unknown key: :on. Valid keys are: :if, :unless, :prepend", exception.message
@@ -489,7 +489,7 @@ class CallbacksTest < ActiveRecord::TestCase
def test_around_save_doesnt_allow_on_option
exception = assert_raises ArgumentError do
Class.new(ActiveRecord::Base) do
- around_save(on: :create) {}
+ around_save(on: :create) { }
end
end
assert_equal "Unknown key: :on. Valid keys are: :if, :unless, :prepend", exception.message
@@ -498,7 +498,7 @@ class CallbacksTest < ActiveRecord::TestCase
def test_after_save_doesnt_allow_on_option
exception = assert_raises ArgumentError do
Class.new(ActiveRecord::Base) do
- after_save(on: :create) {}
+ after_save(on: :create) { }
end
end
assert_equal "Unknown key: :on. Valid keys are: :if, :unless, :prepend", exception.message
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index b8e623f17b..5e3447efde 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -28,13 +28,16 @@ module ActiveRecord
end
def test_establish_connection_uses_spec_name
+ old_config = ActiveRecord::Base.configurations
config = { "readonly" => { "adapter" => "sqlite3" } }
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config)
+ ActiveRecord::Base.configurations = config
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(ActiveRecord::Base.configurations)
spec = resolver.spec(:readonly)
@handler.establish_connection(spec.to_hash)
assert_not_nil @handler.retrieve_connection_pool("readonly")
ensure
+ ActiveRecord::Base.configurations = old_config
@handler.remove_connection("readonly")
end
@@ -148,6 +151,30 @@ module ActiveRecord
ActiveRecord::Base.configurations = @prev_configs
end
+ def test_symbolized_configurations_assignment
+ @prev_configs = ActiveRecord::Base.configurations
+ config = {
+ development: {
+ primary: {
+ adapter: "sqlite3",
+ database: "db/development.sqlite3",
+ },
+ },
+ test: {
+ primary: {
+ adapter: "sqlite3",
+ database: "db/test.sqlite3",
+ },
+ },
+ }
+ ActiveRecord::Base.configurations = config
+ ActiveRecord::Base.configurations.configs_for.each do |db_config|
+ assert_instance_of ActiveRecord::DatabaseConfigurations::HashConfig, db_config
+ end
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ end
+
def test_retrieve_connection
assert @handler.retrieve_connection(@spec_name)
end
diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
index 1b64324cc4..06c1c51724 100644
--- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -18,11 +18,14 @@ module ActiveRecord
end
def resolve_config(config)
- ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ configs.to_h
end
def resolve_spec(spec, config)
- ConnectionSpecification::Resolver.new(resolve_config(config)).resolve(spec)
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
+ resolver.resolve(spec, spec)
end
def test_resolver_with_database_uri_and_current_env_symbol_key
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index 0941ee3309..b9b5cc0e28 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -106,7 +106,7 @@ module ActiveRecord
def middleware(app)
lambda do |env|
a, b, c = executor.wrap { app.call(env) }
- [a, b, Rack::BodyProxy.new(c) {}]
+ [a, b, Rack::BodyProxy.new(c) { }]
end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 9ac03629c3..633d56e479 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -91,7 +91,9 @@ module ActiveRecord
end
def test_full_pool_exception
+ @pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout
@pool.size.times { assert @pool.checkout }
+
assert_raises(ConnectionTimeoutError) do
@pool.checkout
end
@@ -109,6 +111,44 @@ module ActiveRecord
assert_equal connection, t.join.value
end
+ def test_full_pool_blocking_shares_load_interlock
+ @pool.instance_variable_set(:@size, 1)
+
+ load_interlock_latch = Concurrent::CountDownLatch.new
+ connection_latch = Concurrent::CountDownLatch.new
+
+ able_to_get_connection = false
+ able_to_load = false
+
+ thread_with_load_interlock = Thread.new do
+ ActiveSupport::Dependencies.interlock.running do
+ load_interlock_latch.count_down
+ connection_latch.wait
+
+ @pool.with_connection do
+ able_to_get_connection = true
+ end
+ end
+ end
+
+ thread_with_last_connection = Thread.new do
+ @pool.with_connection do
+ connection_latch.count_down
+ load_interlock_latch.wait
+
+ ActiveSupport::Dependencies.interlock.loading do
+ able_to_load = true
+ end
+ end
+ end
+
+ thread_with_load_interlock.join
+ thread_with_last_connection.join
+
+ assert able_to_get_connection
+ assert able_to_load
+ end
+
def test_removing_releases_latch
cs = @pool.size.times.map { @pool.checkout }
t = Thread.new { @pool.checkout }
@@ -156,6 +196,48 @@ module ActiveRecord
@pool.connections.each { |conn| conn.close if conn.in_use? }
end
+ def test_idle_timeout_configuration
+ @pool.disconnect!
+ spec = ActiveRecord::Base.connection_pool.spec
+ spec.config.merge!(idle_timeout: "0.02")
+ @pool = ConnectionPool.new(spec)
+ idle_conn = @pool.checkout
+ @pool.checkin(idle_conn)
+
+ idle_conn.instance_variable_set(
+ :@idle_since,
+ Concurrent.monotonic_time - 0.01
+ )
+
+ @pool.flush
+ assert_equal 1, @pool.connections.length
+
+ idle_conn.instance_variable_set(
+ :@idle_since,
+ Concurrent.monotonic_time - 0.02
+ )
+
+ @pool.flush
+ assert_equal 0, @pool.connections.length
+ end
+
+ def test_disable_flush
+ @pool.disconnect!
+ spec = ActiveRecord::Base.connection_pool.spec
+ spec.config.merge!(idle_timeout: -5)
+ @pool = ConnectionPool.new(spec)
+ idle_conn = @pool.checkout
+ @pool.checkin(idle_conn)
+
+ idle_conn.instance_variable_set(
+ :@idle_since,
+ Concurrent.monotonic_time - 1
+ )
+
+ @pool.flush
+ assert_equal 1, @pool.connections.length
+ end
+
def test_flush
idle_conn = @pool.checkout
recent_conn = @pool.checkout
@@ -166,9 +248,10 @@ module ActiveRecord
assert_equal 3, @pool.connections.length
- def idle_conn.seconds_idle
- 1000
- end
+ idle_conn.instance_variable_set(
+ :@idle_since,
+ Concurrent.monotonic_time - 1000
+ )
@pool.flush(30)
@@ -578,7 +661,7 @@ module ActiveRecord
end
stuck_thread = Thread.new do
- pool.with_connection {}
+ pool.with_connection { }
end
# wait for stuck_thread to get in queue
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index 5b80f16a44..72be14f507 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -7,11 +7,15 @@ module ActiveRecord
class ConnectionSpecification
class ResolverTest < ActiveRecord::TestCase
def resolve(spec, config = {})
- Resolver.new(config).resolve(spec)
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
+ resolver.resolve(spec, spec)
end
def spec(spec, config = {})
- Resolver.new(config).spec(spec)
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
+ resolver.spec(spec)
end
def test_url_invalid_adapter
diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb
index 6e7ae2efb4..f7fbf3ee8a 100644
--- a/activerecord/test/cases/core_test.rb
+++ b/activerecord/test/cases/core_test.rb
@@ -36,7 +36,7 @@ class CoreTest < ActiveRecord::TestCase
def test_pretty_print_new
topic = Topic.new
- actual = "".dup
+ actual = +""
PP.pp(topic, StringIO.new(actual))
expected = <<~PRETTY
#<Topic:0xXXXXXX
@@ -65,7 +65,7 @@ class CoreTest < ActiveRecord::TestCase
def test_pretty_print_persisted
topic = topics(:first)
- actual = "".dup
+ actual = +""
PP.pp(topic, StringIO.new(actual))
expected = <<~PRETTY
#<Topic:0x\\w+
@@ -93,7 +93,7 @@ class CoreTest < ActiveRecord::TestCase
def test_pretty_print_uninitialized
topic = Topic.allocate
- actual = "".dup
+ actual = +""
PP.pp(topic, StringIO.new(actual))
expected = "#<Topic:XXXXXX not initialized>\n"
assert actual.start_with?(expected.split("XXXXXX").first)
@@ -106,7 +106,7 @@ class CoreTest < ActiveRecord::TestCase
"inspecting topic"
end
end
- actual = "".dup
+ actual = +""
PP.pp(subtopic.new, StringIO.new(actual))
assert_equal "inspecting topic\n", actual
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 83cc2aa319..b1ebd20d6b 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -567,8 +567,6 @@ class DirtyTest < ActiveRecord::TestCase
assert_not_nil pirate.previous_changes["updated_on"][1]
assert_not pirate.previous_changes.key?("parrot_id")
assert_not pirate.previous_changes.key?("created_on")
- ensure
- travel_back
end
class Testings < ActiveRecord::Base; end
@@ -879,6 +877,26 @@ class DirtyTest < ActiveRecord::TestCase
raise "changed? should be false" if changed?
raise "has_changes_to_save? should be false" if has_changes_to_save?
raise "saved_changes? should be true" unless saved_changes?
+ raise "id_in_database should not be nil" if id_in_database.nil?
+ end
+ end
+
+ person = klass.create!(first_name: "Sean")
+ assert_not_predicate person, :changed?
+ end
+
+ test "changed? in around callbacks after yield returns false" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "people"
+
+ around_create :check_around
+
+ def check_around
+ yield
+ raise "changed? should be false" if changed?
+ raise "has_changes_to_save? should be false" if has_changes_to_save?
+ raise "saved_changes? should be true" unless saved_changes?
+ raise "id_in_database should not be nil" if id_in_database.nil?
end
end
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index 82cc891970..79a0630193 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -40,7 +40,7 @@ if ActiveRecord::Base.connection.supports_explain?
assert_equal binds, queries[0][1]
end
- def test_collects_nothing_if_the_statement_is_not_whitelisted
+ def test_collects_nothing_if_the_statement_is_not_explainable
SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "SHOW max_identifier_length")
assert_empty queries
end
diff --git a/activerecord/test/cases/filter_attributes_test.rb b/activerecord/test/cases/filter_attributes_test.rb
new file mode 100644
index 0000000000..af5badd87d
--- /dev/null
+++ b/activerecord/test/cases/filter_attributes_test.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/admin"
+require "models/admin/user"
+require "models/admin/account"
+require "pp"
+
+class FilterAttributesTest < ActiveRecord::TestCase
+ fixtures :"admin/users", :"admin/accounts"
+
+ setup do
+ @previous_filter_attributes = ActiveRecord::Base.filter_attributes
+ ActiveRecord::Base.filter_attributes = [:name]
+ end
+
+ teardown do
+ ActiveRecord::Base.filter_attributes = @previous_filter_attributes
+ end
+
+ test "filter_attributes" do
+ Admin::User.all.each do |user|
+ assert_includes user.inspect, "name: [FILTERED]"
+ assert_equal 1, user.inspect.scan("[FILTERED]").length
+ end
+
+ Admin::Account.all.each do |account|
+ assert_includes account.inspect, "name: [FILTERED]"
+ assert_equal 1, account.inspect.scan("[FILTERED]").length
+ end
+ end
+
+ test "filter_attributes could be overwritten by models" do
+ Admin::Account.all.each do |account|
+ assert_includes account.inspect, "name: [FILTERED]"
+ assert_equal 1, account.inspect.scan("[FILTERED]").length
+ end
+
+ begin
+ previous_account_filter_attributes = Admin::Account.filter_attributes
+ Admin::Account.filter_attributes = []
+
+ # Above changes should not impact other models
+ Admin::User.all.each do |user|
+ assert_includes user.inspect, "name: [FILTERED]"
+ assert_equal 1, user.inspect.scan("[FILTERED]").length
+ end
+
+ Admin::Account.all.each do |account|
+ assert_not_includes account.inspect, "name: [FILTERED]"
+ assert_equal 0, account.inspect.scan("[FILTERED]").length
+ end
+ ensure
+ Admin::Account.filter_attributes = previous_account_filter_attributes
+ end
+ end
+
+ test "filter_attributes should not filter nil value" do
+ account = Admin::Account.new
+
+ assert_includes account.inspect, "name: nil"
+ assert_not_includes account.inspect, "name: [FILTERED]"
+ assert_equal 0, account.inspect.scan("[FILTERED]").length
+ end
+
+ test "filter_attributes on pretty_print" do
+ user = admin_users(:david)
+ actual = "".dup
+ PP.pp(user, StringIO.new(actual))
+
+ assert_includes actual, "name: [FILTERED]"
+ assert_equal 1, actual.scan("[FILTERED]").length
+ end
+
+ test "filter_attributes on pretty_print should not filter nil value" do
+ user = Admin::User.new
+ actual = "".dup
+ PP.pp(user, StringIO.new(actual))
+
+ assert_includes actual, "name: nil"
+ assert_not_includes actual, "name: [FILTERED]"
+ assert_equal 0, actual.scan("[FILTERED]").length
+ end
+end
diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb
index 59af4e6961..e0acd30c22 100644
--- a/activerecord/test/cases/finder_respond_to_test.rb
+++ b/activerecord/test/cases/finder_respond_to_test.rb
@@ -12,7 +12,7 @@ class FinderRespondToTest < ActiveRecord::TestCase
end
def test_should_preserve_normal_respond_to_behaviour_and_respond_to_newly_added_method
- class << Topic; self; end.send(:define_method, :method_added_for_finder_respond_to_test) {}
+ class << Topic; self; end.send(:define_method, :method_added_for_finder_respond_to_test) { }
assert_respond_to Topic, :method_added_for_finder_respond_to_test
ensure
class << Topic; self; end.send(:remove_method, :method_added_for_finder_respond_to_test)
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index e73c88dd5d..355fb4517f 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -371,7 +371,10 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_an_empty_array
- assert_equal [], Topic.find([])
+ empty_array = []
+ result = Topic.find(empty_array)
+ assert_equal [], result
+ assert_not_same empty_array, result
end
def test_find_doesnt_have_implicit_ordering
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index c65523d8c1..82ca15b415 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -974,7 +974,7 @@ class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase
connection_id: connection.object_id
}
- message_bus.instrument("!connection.active_record", payload) {}
+ message_bus.instrument("!connection.active_record", payload) { }
end
end
end
@@ -1344,3 +1344,19 @@ class SameNameDifferentDatabaseFixturesTest < ActiveRecord::TestCase
assert_kind_of OtherDog, other_dogs(:lassie)
end
end
+
+class NilFixturePathTest < ActiveRecord::TestCase
+ test "raises an error when all fixtures loaded" do
+ error = assert_raises(StandardError) do
+ TestCase = Class.new(ActiveRecord::TestCase)
+ TestCase.class_eval do
+ self.fixture_path = nil
+ fixtures :all
+ end
+ end
+ assert_equal <<~MSG.squish, error.message
+ No fixture path found.
+ Please set `NilFixturePathTest::TestCase.fixture_path`.
+ MSG
+ end
+end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 66f11fe5bd..68be685e4b 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -183,5 +183,3 @@ module InTimeZone
ActiveRecord::Base.time_zone_aware_attributes = old_tz
end
end
-
-require "mocha/minitest" # FIXME: stop using mocha
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 363beb4780..6cf17ac15d 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -22,6 +22,14 @@ module ActiveRecord
end
end
+ class InvertibleTransactionMigration < InvertibleMigration
+ def change
+ transaction do
+ super
+ end
+ end
+ end
+
class InvertibleRevertMigration < SilentMigration
def change
revert do
@@ -271,6 +279,14 @@ module ActiveRecord
assert_not revert.connection.table_exists?("horses")
end
+ def test_migrate_revert_transaction
+ migration = InvertibleTransactionMigration.new
+ migration.migrate :up
+ assert migration.connection.table_exists?("horses")
+ migration.migrate :down
+ assert_not migration.connection.table_exists?("horses")
+ end
+
def test_migrate_revert_change_column_default
migration1 = ChangeColumnDefault1.new
migration1.migrate(:up)
diff --git a/activerecord/test/cases/legacy_configurations_test.rb b/activerecord/test/cases/legacy_configurations_test.rb
new file mode 100644
index 0000000000..c36feb5116
--- /dev/null
+++ b/activerecord/test/cases/legacy_configurations_test.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+
+module ActiveRecord
+ class LegacyConfigurationsTest < ActiveRecord::TestCase
+ def test_can_turn_configurations_into_a_hash
+ assert ActiveRecord::Base.configurations.to_h.is_a?(Hash), "expected to be a hash but was not."
+ assert_equal ["arunit", "arunit2", "arunit_without_prepared_statements"].sort, ActiveRecord::Base.configurations.to_h.keys.sort
+ end
+
+ def test_each_is_deprecated
+ assert_deprecated do
+ ActiveRecord::Base.configurations.each do |db_config|
+ assert_equal "primary", db_config.spec_name
+ end
+ end
+ end
+
+ def test_first_is_deprecated
+ assert_deprecated do
+ db_config = ActiveRecord::Base.configurations.first
+ assert_equal "arunit", db_config.env_name
+ assert_equal "primary", db_config.spec_name
+ end
+ end
+
+ def test_fetch_is_deprecated
+ assert_deprecated do
+ db_config = ActiveRecord::Base.configurations.fetch("arunit").first
+ assert_equal "arunit", db_config.env_name
+ assert_equal "primary", db_config.spec_name
+ end
+ end
+
+ def test_values_are_deprecated
+ config_hashes = ActiveRecord::Base.configurations.configurations.map(&:config)
+ assert_deprecated do
+ assert_equal config_hashes, ActiveRecord::Base.configurations.values
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index f0126fdb0d..ae2597adc8 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -44,6 +44,7 @@ class LogSubscriberTest < ActiveRecord::TestCase
def setup
@old_logger = ActiveRecord::Base.logger
Developer.primary_key
+ ActiveRecord::Base.connection.materialize_transactions
super
ActiveRecord::LogSubscriber.attach_to(:active_record)
end
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index 034bf32165..c108d372d1 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -164,6 +164,14 @@ module ActiveRecord
end
end
+ def test_column_creates_column_with_index
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {}]
+ @connection.expect :add_index, nil, [:delete_me, :bar, {}]
+ t.column :bar, :integer, index: true
+ end
+ end
+
def test_index_creates_index
with_change_table do |t|
@connection.expect :add_index, nil, [:delete_me, :bar, {}]
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 3a11bb081b..01f8628fc5 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -117,13 +117,13 @@ module ActiveRecord
end
def test_invert_create_table_with_options_and_block
- block = Proc.new {}
+ block = Proc.new { }
drop_table = @recorder.inverse_of :create_table, [:people_reminders, id: false], &block
assert_equal [:drop_table, [:people_reminders, id: false], block], drop_table
end
def test_invert_drop_table
- block = Proc.new {}
+ block = Proc.new { }
create_table = @recorder.inverse_of :drop_table, [:people_reminders, id: false], &block
assert_equal [:create_table, [:people_reminders, id: false], block], create_table
end
@@ -145,7 +145,7 @@ module ActiveRecord
end
def test_invert_drop_join_table
- block = Proc.new {}
+ block = Proc.new { }
create_join_table = @recorder.inverse_of :drop_join_table, [:musics, :artists, table_name: :catalog], &block
assert_equal [:create_join_table, [:musics, :artists, table_name: :catalog], block], create_join_table
end
@@ -329,11 +329,24 @@ module ActiveRecord
assert_equal [:add_foreign_key, [:dogs, :people, primary_key: "person_id"]], enable
end
+ def test_invert_remove_foreign_key_with_primary_key_and_to_table_in_options
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, to_table: :people, primary_key: "uuid"]
+ assert_equal [:add_foreign_key, [:dogs, :people, primary_key: "uuid"]], enable
+ end
+
def test_invert_remove_foreign_key_with_on_delete_on_update
enable = @recorder.inverse_of :remove_foreign_key, [:dogs, :people, on_delete: :nullify, on_update: :cascade]
assert_equal [:add_foreign_key, [:dogs, :people, on_delete: :nullify, on_update: :cascade]], enable
end
+ def test_invert_remove_foreign_key_with_to_table_in_options
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, to_table: :people]
+ assert_equal [:add_foreign_key, [:dogs, :people]], enable
+
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, to_table: :people, column: :owner_id]
+ assert_equal [:add_foreign_key, [:dogs, :people, column: :owner_id]], enable
+ end
+
def test_invert_remove_foreign_key_is_irreversible_without_to_table
assert_raises ActiveRecord::IrreversibleMigration do
@recorder.inverse_of :remove_foreign_key, [:dogs, column: "owner_id"]
@@ -347,6 +360,16 @@ module ActiveRecord
@recorder.inverse_of :remove_foreign_key, [:dogs]
end
end
+
+ def test_invert_transaction_with_irreversible_inside_is_irreversible
+ assert_raises(ActiveRecord::IrreversibleMigration) do
+ @recorder.revert do
+ @recorder.transaction do
+ @recorder.execute "some sql"
+ end
+ end
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 69a50674af..017ee7951e 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -127,6 +127,20 @@ module ActiveRecord
assert_match(/LegacyMigration < ActiveRecord::Migration\[4\.2\]/, e.message)
end
+ def test_legacy_migrations_not_raise_exception_on_reverting_transaction
+ migration = Class.new(ActiveRecord::Migration[5.2]) {
+ def change
+ transaction do
+ execute "select 1"
+ end
+ end
+ }.new
+
+ assert_nothing_raised do
+ migration.migrate(:down)
+ end
+ end
+
if current_adapter?(:PostgreSQLAdapter)
class Testing < ActiveRecord::Base
end
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index c471dd1106..bb233fbf74 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -53,16 +53,49 @@ if ActiveRecord::Base.connection.supports_foreign_keys_in_create?
end
def test_change_column_of_parent_table
- foreign_keys = ActiveRecord::Base.connection.foreign_keys("astronauts")
rocket = Rocket.create!(name: "myrocket")
rocket.astronauts << Astronaut.create!
@connection.change_column_null :rockets, :name, false
+ foreign_keys = @connection.foreign_keys("astronauts")
+ assert_equal 1, foreign_keys.size
+
+ fk = foreign_keys.first
+ assert_equal "myrocket", Rocket.first.name
+ assert_equal "astronauts", fk.from_table
+ assert_equal "rockets", fk.to_table
+ end
+
+ def test_rename_column_of_child_table
+ rocket = Rocket.create!(name: "myrocket")
+ rocket.astronauts << Astronaut.create!
+
+ @connection.rename_column :astronauts, :name, :astronaut_name
+
+ foreign_keys = @connection.foreign_keys("astronauts")
+ assert_equal 1, foreign_keys.size
+
+ fk = foreign_keys.first
+ assert_equal "myrocket", Rocket.first.name
+ assert_equal "astronauts", fk.from_table
+ assert_equal "rockets", fk.to_table
+ end
+
+ def test_rename_reference_column_of_child_table
+ rocket = Rocket.create!(name: "myrocket")
+ rocket.astronauts << Astronaut.create!
+
+ @connection.rename_column :astronauts, :rocket_id, :new_rocket_id
+
+ foreign_keys = @connection.foreign_keys("astronauts")
+ assert_equal 1, foreign_keys.size
+
fk = foreign_keys.first
assert_equal "myrocket", Rocket.first.name
assert_equal "astronauts", fk.from_table
assert_equal "rockets", fk.to_table
+ assert_equal "new_rocket_id", fk.options[:column]
end
end
end
diff --git a/activerecord/test/cases/migration/pending_migrations_test.rb b/activerecord/test/cases/migration/pending_migrations_test.rb
index dedb5ea502..119bfd372a 100644
--- a/activerecord/test/cases/migration/pending_migrations_test.rb
+++ b/activerecord/test/cases/migration/pending_migrations_test.rb
@@ -25,7 +25,7 @@ module ActiveRecord
ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
assert_raises ActiveRecord::PendingMigrationError do
- CheckPending.new(Proc.new {}).call({})
+ CheckPending.new(Proc.new { }).call({})
end
end
@@ -34,7 +34,7 @@ module ActiveRecord
migrator = Base.connection.migration_context
capture(:stdout) { migrator.migrate }
- assert_nil CheckPending.new(Proc.new {}).call({})
+ assert_nil CheckPending.new(Proc.new { }).call({})
end
end
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index d1292dc53d..5d060c8899 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -87,7 +87,6 @@ class MigrationTest < ActiveRecord::TestCase
def test_migrator_versions
migrations_path = MIGRATIONS_ROOT + "/valid"
- old_path = ActiveRecord::Migrator.migrations_paths
migrator = ActiveRecord::MigrationContext.new(migrations_path)
migrator.up
@@ -100,24 +99,18 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::SchemaMigration.create!(version: 3)
assert_equal true, migrator.needs_migration?
- ensure
- ActiveRecord::MigrationContext.new(old_path)
end
def test_migration_detection_without_schema_migration_table
ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
migrations_path = MIGRATIONS_ROOT + "/valid"
- old_path = ActiveRecord::Migrator.migrations_paths
migrator = ActiveRecord::MigrationContext.new(migrations_path)
assert_equal true, migrator.needs_migration?
- ensure
- ActiveRecord::MigrationContext.new(old_path)
end
def test_any_migrations
- old_path = ActiveRecord::Migrator.migrations_paths
migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid")
assert_predicate migrator, :any_migrations?
@@ -125,8 +118,6 @@ class MigrationTest < ActiveRecord::TestCase
migrator_empty = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/empty")
assert_not_predicate migrator_empty, :any_migrations?
- ensure
- ActiveRecord::MigrationContext.new(old_path)
end
def test_migration_version
@@ -393,7 +384,6 @@ class MigrationTest < ActiveRecord::TestCase
def test_internal_metadata_stores_environment
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrations_path = MIGRATIONS_ROOT + "/valid"
- old_path = ActiveRecord::Migrator.migrations_paths
migrator = ActiveRecord::MigrationContext.new(migrations_path)
migrator.up
@@ -410,7 +400,6 @@ class MigrationTest < ActiveRecord::TestCase
migrator.up
assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
ensure
- migrator = ActiveRecord::MigrationContext.new(old_path)
ENV["RAILS_ENV"] = original_rails_env
ENV["RACK_ENV"] = original_rack_env
migrator.up
@@ -422,16 +411,12 @@ class MigrationTest < ActiveRecord::TestCase
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrations_path = MIGRATIONS_ROOT + "/valid"
- old_path = ActiveRecord::Migrator.migrations_paths
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrator = ActiveRecord::MigrationContext.new(migrations_path)
migrator.up
assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
assert_equal "bar", ActiveRecord::InternalMetadata[:foo]
- ensure
- migrator = ActiveRecord::MigrationContext.new(old_path)
- migrator.up
end
def test_proper_table_name_on_migration
@@ -793,12 +778,20 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
end
def test_adding_multiple_columns
- assert_queries(1) do
+ classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
+ expected_query_count = {
+ "Mysql2Adapter" => 1,
+ "PostgreSQLAdapter" => 2, # one for bulk change, one for comment
+ }.fetch(classname) {
+ raise "need an expected query count for #{classname}"
+ }
+
+ assert_queries(expected_query_count) do
with_bulk_change_table do |t|
t.column :name, :string
t.string :qualification, :experience
t.integer :age, default: 0
- t.date :birthdate
+ t.date :birthdate, comment: "This is a comment"
t.timestamps null: true
end
end
@@ -806,6 +799,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
assert_equal 8, columns.size
[:name, :qualification, :experience].each { |s| assert_equal :string, column(s).type }
assert_equal "0", column(:age).default
+ assert_equal "This is a comment", column(:birthdate).comment
end
def test_removing_columns
@@ -1150,7 +1144,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
def test_check_pending_with_stdlib_logger
old, ActiveRecord::Base.logger = ActiveRecord::Base.logger, ::Logger.new($stdout)
quietly do
- assert_nothing_raised { ActiveRecord::Migration::CheckPending.new(Proc.new {}).call({}) }
+ assert_nothing_raised { ActiveRecord::Migration::CheckPending.new(Proc.new { }).call({}) }
end
ensure
ActiveRecord::Base.logger = old
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index 873455cf67..30e199f1c5 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -100,7 +100,6 @@ class MigratorTest < ActiveRecord::TestCase
def test_finds_migrations_in_subdirectories
migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid_with_subdirectories").migrations
-
[[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
assert_equal migrations[i].version, pair.first
assert_equal migrations[i].name, pair.last
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index aa519fd332..bb1c1ea17d 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -217,6 +217,18 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
mean_pirate.parrot_attributes = { name: "James" }
assert_equal "James", mean_pirate.parrot.name
end
+
+ def test_should_not_create_duplicates_with_create_with
+ Man.accepts_nested_attributes_for(:interests)
+
+ assert_difference("Interest.count", 1) do
+ Man.create_with(
+ interests_attributes: [{ topic: "Pirate king" }]
+ ).find_or_create_by!(
+ name: "Monkey D. Luffy"
+ )
+ end
+ end
end
class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 7348a22dd3..8073cabae6 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -13,90 +13,15 @@ require "models/developer"
require "models/computer"
require "models/project"
require "models/minimalistic"
-require "models/warehouse_thing"
require "models/parrot"
require "models/minivan"
-require "models/owner"
require "models/person"
-require "models/pet"
require "models/ship"
-require "models/toy"
require "models/admin"
require "models/admin/user"
-require "rexml/document"
class PersistenceTest < ActiveRecord::TestCase
- fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :author_addresses, :categorizations, :categories, :posts, :minivans, :pets, :toys
-
- # Oracle UPDATE does not support ORDER BY
- unless current_adapter?(:OracleAdapter)
- def test_update_all_ignores_order_without_limit_from_association
- author = authors(:david)
- assert_nothing_raised do
- assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ])
- end
- end
-
- def test_update_all_doesnt_ignore_order
- assert_equal authors(:david).id + 1, authors(:mary).id # make sure there is going to be a duplicate PK error
- test_update_with_order_succeeds = lambda do |order|
- begin
- Author.order(order).update_all("id = id + 1")
- rescue ActiveRecord::ActiveRecordError
- false
- end
- end
-
- if test_update_with_order_succeeds.call("id DESC")
- assert_not test_update_with_order_succeeds.call("id ASC") # test that this wasn't a fluke and using an incorrect order results in an exception
- else
- # test that we're failing because the current Arel's engine doesn't support UPDATE ORDER BY queries is using subselects instead
- assert_sql(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC\)\z/i) do
- test_update_with_order_succeeds.call("id DESC")
- end
- end
- end
-
- def test_update_all_with_order_and_limit_updates_subset_only
- author = authors(:david)
- limited_posts = author.posts_sorted_by_id_limited
- assert_equal 1, limited_posts.size
- assert_equal 2, limited_posts.limit(2).size
- assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
- assert_equal "bulk update!", posts(:welcome).body
- assert_not_equal "bulk update!", posts(:thinking).body
- end
-
- def test_update_all_with_order_and_limit_and_offset_updates_subset_only
- author = authors(:david)
- limited_posts = author.posts_sorted_by_id_limited.offset(1)
- assert_equal 1, limited_posts.size
- assert_equal 2, limited_posts.limit(2).size
- assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
- assert_equal "bulk update!", posts(:thinking).body
- assert_not_equal "bulk update!", posts(:welcome).body
- end
-
- def test_delete_all_with_order_and_limit_deletes_subset_only
- author = authors(:david)
- limited_posts = Post.where(author: author).order(:id).limit(1)
- assert_equal 1, limited_posts.size
- assert_equal 2, limited_posts.limit(2).size
- assert_equal 1, limited_posts.delete_all
- assert_raise(ActiveRecord::RecordNotFound) { posts(:welcome) }
- assert posts(:thinking)
- end
-
- def test_delete_all_with_order_and_limit_and_offset_deletes_subset_only
- author = authors(:david)
- limited_posts = Post.where(author: author).order(:id).limit(1).offset(1)
- assert_equal 1, limited_posts.size
- assert_equal 2, limited_posts.limit(2).size
- assert_equal 1, limited_posts.delete_all
- assert_raise(ActiveRecord::RecordNotFound) { posts(:thinking) }
- assert posts(:welcome)
- end
- end
+ fixtures :topics, :companies, :developers, :accounts, :minimalistics, :authors, :author_addresses, :posts, :minivans
def test_update_many
topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
@@ -145,34 +70,6 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal Topic.count, Topic.delete_all
end
- def test_delete_all_with_joins_and_where_part_is_hash
- pets = Pet.joins(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.delete_all
- end
-
- def test_delete_all_with_joins_and_where_part_is_not_hash
- pets = Pet.joins(:toys).where("toys.name = ?", "Bone")
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.delete_all
- end
-
- def test_delete_all_with_left_joins
- pets = Pet.left_joins(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.delete_all
- end
-
- def test_delete_all_with_includes
- pets = Pet.includes(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.delete_all
- end
-
def test_increment_attribute
assert_equal 50, accounts(:signals37).credit_limit
accounts(:signals37).increment! :credit_limit
@@ -230,16 +127,9 @@ class PersistenceTest < ActiveRecord::TestCase
assert_operator previously_written_on, :<, topic.written_on
end
- def test_destroy_all
- conditions = "author_name = 'Mary'"
- topics_by_mary = Topic.all.merge!(where: conditions, order: "id").to_a
- assert_not_empty topics_by_mary
-
- assert_difference("Topic.count", -topics_by_mary.size) do
- destroyed = Topic.where(conditions).destroy_all.sort_by(&:id)
- assert_equal topics_by_mary, destroyed
- assert destroyed.all?(&:frozen?), "destroyed topics should be frozen"
- end
+ def test_increment_with_no_arg
+ topic = topics(:first)
+ assert_raises(ArgumentError) { topic.increment! }
end
def test_destroy_many
@@ -638,32 +528,6 @@ class PersistenceTest < ActiveRecord::TestCase
assert_nil Topic.find(2).last_read
end
- def test_update_all_with_joins
- pets = Pet.joins(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.update_all(name: "Bob")
- end
-
- def test_update_all_with_left_joins
- pets = Pet.left_joins(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.update_all(name: "Bob")
- end
-
- def test_update_all_with_includes
- pets = Pet.includes(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.update_all(name: "Bob")
- end
-
- def test_update_all_with_non_standard_table_name
- assert_equal 1, WarehouseThing.where(id: 1).update_all(["value = ?", 0])
- assert_equal 0, WarehouseThing.find(1).value
- end
-
def test_delete_new_record
client = Client.new(name: "37signals")
client.delete
@@ -1185,21 +1049,19 @@ class PersistenceTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.disable_query_cache!
end
- class SaveTest < ActiveRecord::TestCase
- def test_save_touch_false
- pet = Pet.create!(
- name: "Bob",
- created_at: 1.day.ago,
- updated_at: 1.day.ago)
+ def test_save_touch_false
+ parrot = Parrot.create!(
+ name: "Bob",
+ created_at: 1.day.ago,
+ updated_at: 1.day.ago)
- created_at = pet.created_at
- updated_at = pet.updated_at
+ created_at = parrot.created_at
+ updated_at = parrot.updated_at
- pet.name = "Barb"
- pet.save!(touch: false)
- assert_equal pet.created_at, created_at
- assert_equal pet.updated_at, updated_at
- end
+ parrot.name = "Barb"
+ parrot.save!(touch: false)
+ assert_equal parrot.created_at, created_at
+ assert_equal parrot.updated_at, updated_at
end
def test_reset_column_information_resets_children
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 69be091869..3eb4e04cb7 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -55,16 +55,6 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_cache :off
end
- private def with_temporary_connection_pool
- old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
- new_pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new ActiveRecord::Base.connection_pool.spec
- ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = new_pool
-
- yield
- ensure
- ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = old_pool
- end
-
def test_query_cache_across_threads
with_temporary_connection_pool do
begin
@@ -495,6 +485,17 @@ class QueryCacheTest < 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
+ ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = new_pool
+
+ yield
+ ensure
+ ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = old_pool
+ end
+
def middleware(&app)
executor = Class.new(ActiveSupport::Executor)
ActiveRecord::QueryCache.install_executor_hooks executor
diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb
index b034fe3e3b..b630f782bc 100644
--- a/activerecord/test/cases/reaper_test.rb
+++ b/activerecord/test/cases/reaper_test.rb
@@ -61,9 +61,9 @@ module ActiveRecord
def test_reaping_frequency_configuration
spec = ActiveRecord::Base.connection_pool.spec.dup
- spec.config[:reaping_frequency] = 100
+ spec.config[:reaping_frequency] = "10.01"
pool = ConnectionPool.new spec
- assert_equal 100, pool.reaper.frequency
+ assert_equal 10.01, pool.reaper.frequency
end
def test_connection_pool_starts_reaper
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index 3f3d41980c..a8030c2d64 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -5,7 +5,7 @@ require "models/post"
require "models/comment"
module ActiveRecord
- module DelegationWhitelistTests
+ module ArrayDelegationTests
ARRAY_DELEGATES = [
:+, :-, :|, :&, :[], :shuffle,
:all?, :collect, :compact, :detect, :each, :each_cons, :each_with_index,
@@ -38,7 +38,7 @@ module ActiveRecord
end
class DelegationAssociationTest < ActiveRecord::TestCase
- include DelegationWhitelistTests
+ include ArrayDelegationTests
include DeprecatedArelDelegationTests
def target
@@ -47,7 +47,7 @@ module ActiveRecord
end
class DelegationRelationTest < ActiveRecord::TestCase
- include DelegationWhitelistTests
+ include ArrayDelegationTests
include DeprecatedArelDelegationTests
def target
diff --git a/activerecord/test/cases/relation/delete_all_test.rb b/activerecord/test/cases/relation/delete_all_test.rb
new file mode 100644
index 0000000000..446d7621ea
--- /dev/null
+++ b/activerecord/test/cases/relation/delete_all_test.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/author"
+require "models/post"
+require "models/pet"
+require "models/toy"
+
+class DeleteAllTest < ActiveRecord::TestCase
+ fixtures :authors, :author_addresses, :posts, :pets, :toys
+
+ def test_destroy_all
+ davids = Author.where(name: "David")
+
+ # Force load
+ assert_equal [authors(:david)], davids.to_a
+ assert_predicate davids, :loaded?
+
+ assert_difference("Author.count", -1) do
+ destroyed = davids.destroy_all
+ assert_equal [authors(:david)], destroyed
+ assert_predicate destroyed.first, :frozen?
+ end
+
+ assert_equal [], davids.to_a
+ assert_predicate davids, :loaded?
+ end
+
+ def test_delete_all
+ davids = Author.where(name: "David")
+
+ assert_difference("Author.count", -1) { davids.delete_all }
+ assert_not_predicate davids, :loaded?
+ end
+
+ def test_delete_all_loaded
+ davids = Author.where(name: "David")
+
+ # Force load
+ assert_equal [authors(:david)], davids.to_a
+ assert_predicate davids, :loaded?
+
+ assert_difference("Author.count", -1) { davids.delete_all }
+
+ assert_equal [], davids.to_a
+ assert_predicate davids, :loaded?
+ end
+
+ def test_delete_all_with_unpermitted_relation_raises_error
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.distinct.delete_all }
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all }
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.having("SUM(id) < 3").delete_all }
+ end
+
+ def test_delete_all_with_joins_and_where_part_is_hash
+ pets = Pet.joins(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
+ end
+
+ def test_delete_all_with_joins_and_where_part_is_not_hash
+ pets = Pet.joins(:toys).where("toys.name = ?", "Bone")
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
+ end
+
+ def test_delete_all_with_left_joins
+ pets = Pet.left_joins(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
+ end
+
+ def test_delete_all_with_includes
+ pets = Pet.includes(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
+ end
+
+ unless current_adapter?(:OracleAdapter)
+ def test_delete_all_with_order_and_limit_deletes_subset_only
+ author = authors(:david)
+ limited_posts = Post.where(author: author).order(:id).limit(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { posts(:welcome) }
+ assert posts(:thinking)
+ end
+
+ def test_delete_all_with_order_and_limit_and_offset_deletes_subset_only
+ author = authors(:david)
+ limited_posts = Post.where(author: author).order(:id).limit(1).offset(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { posts(:thinking) }
+ assert posts(:welcome)
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index f53ef1fe35..224e4f39a8 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -121,6 +121,20 @@ class RelationMergingTest < ActiveRecord::TestCase
relation = relation.merge(Post.from("posts"))
assert_not_empty relation.from_clause
end
+
+ def test_merging_with_from_clause_on_different_class
+ assert Comment.joins(:post).merge(Post.from("posts")).first
+ end
+
+ def test_merging_with_order_with_binds
+ relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
+ assert_equal ["title LIKE '%suffix'"], relation.order_values
+ end
+
+ def test_merging_with_order_without_binds
+ relation = Post.all.merge(Post.order(Arel.sql("title LIKE '%?'")))
+ assert_equal ["title LIKE '%?'"], relation.order_values
+ end
end
class MergingDifferentRelationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/relation/update_all_test.rb b/activerecord/test/cases/relation/update_all_test.rb
new file mode 100644
index 0000000000..09c365f31b
--- /dev/null
+++ b/activerecord/test/cases/relation/update_all_test.rb
@@ -0,0 +1,239 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/author"
+require "models/category"
+require "models/comment"
+require "models/computer"
+require "models/developer"
+require "models/post"
+require "models/person"
+require "models/pet"
+require "models/toy"
+require "models/topic"
+require "models/tag"
+require "models/tagging"
+require "models/warehouse_thing"
+
+class UpdateAllTest < ActiveRecord::TestCase
+ fixtures :authors, :author_addresses, :comments, :developers, :posts, :people, :pets, :toys, :tags, :taggings, "warehouse-things"
+
+ class TopicWithCallbacks < ActiveRecord::Base
+ self.table_name = :topics
+ cattr_accessor :topic_count
+ before_update { |topic| topic.author_name = "David" if topic.author_name.blank? }
+ after_update { |topic| topic.class.topic_count = topic.class.count }
+ end
+
+ def test_update_all_with_scope
+ tag = Tag.first
+ Post.tagged_with(tag.id).update_all(title: "rofl")
+ posts = Post.tagged_with(tag.id).all.to_a
+ assert_operator posts.length, :>, 0
+ posts.each { |post| assert_equal "rofl", post.title }
+ end
+
+ def test_update_all_with_non_standard_table_name
+ assert_equal 1, WarehouseThing.where(id: 1).update_all(["value = ?", 0])
+ assert_equal 0, WarehouseThing.find(1).value
+ end
+
+ def test_update_all_with_blank_argument
+ assert_raises(ArgumentError) { Comment.update_all({}) }
+ end
+
+ def test_update_all_with_joins
+ pets = Pet.joins(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.update_all(name: "Bob")
+ end
+
+ def test_update_all_with_left_joins
+ pets = Pet.left_joins(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.update_all(name: "Bob")
+ end
+
+ def test_update_all_with_includes
+ pets = Pet.includes(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.update_all(name: "Bob")
+ end
+
+ def test_update_all_with_joins_and_limit
+ comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).limit(1)
+ assert_equal 1, comments.update_all(post_id: posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:greetings).post
+ end
+
+ def test_update_all_with_joins_and_limit_and_order
+ comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("comments.id").limit(1)
+ assert_equal 1, comments.update_all(post_id: posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:greetings).post
+ assert_equal posts(:welcome), comments(:more_greetings).post
+ end
+
+ def test_update_all_with_joins_and_offset
+ all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id)
+ count = all_comments.count
+ comments = all_comments.offset(1)
+
+ assert_equal count - 1, comments.update_all(post_id: posts(:thinking).id)
+ end
+
+ def test_update_all_with_joins_and_offset_and_order
+ all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("posts.id", "comments.id")
+ count = all_comments.count
+ comments = all_comments.offset(1)
+
+ assert_equal count - 1, comments.update_all(post_id: posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:more_greetings).post
+ assert_equal posts(:welcome), comments(:greetings).post
+ end
+
+ def test_update_counters_with_joins
+ assert_nil pets(:parrot).integer
+
+ Pet.joins(:toys).where(toys: { name: "Bone" }).update_counters(integer: 1)
+
+ assert_equal 1, pets(:parrot).reload.integer
+ end
+
+ def test_touch_all_updates_records_timestamps
+ david = developers(:david)
+ david_previously_updated_at = david.updated_at
+ jamis = developers(:jamis)
+ jamis_previously_updated_at = jamis.updated_at
+ Developer.where(name: "David").touch_all
+
+ assert_not_equal david_previously_updated_at, david.reload.updated_at
+ assert_equal jamis_previously_updated_at, jamis.reload.updated_at
+ end
+
+ def test_touch_all_with_custom_timestamp
+ developer = developers(:david)
+ previously_created_at = developer.created_at
+ previously_updated_at = developer.updated_at
+ Developer.where(name: "David").touch_all(:created_at)
+ developer.reload
+
+ assert_not_equal previously_created_at, developer.created_at
+ assert_not_equal previously_updated_at, developer.updated_at
+ end
+
+ def test_touch_all_with_given_time
+ developer = developers(:david)
+ previously_created_at = developer.created_at
+ previously_updated_at = developer.updated_at
+ new_time = Time.utc(2015, 2, 16, 4, 54, 0)
+ Developer.where(name: "David").touch_all(:created_at, time: new_time)
+ developer.reload
+
+ assert_not_equal previously_created_at, developer.created_at
+ assert_not_equal previously_updated_at, developer.updated_at
+ assert_equal new_time, developer.created_at
+ assert_equal new_time, developer.updated_at
+ end
+
+ def test_touch_all_updates_locking_column
+ person = people(:david)
+
+ assert_difference -> { person.reload.lock_version }, +1 do
+ Person.where(first_name: "David").touch_all
+ end
+ end
+
+ def test_update_on_relation
+ topic1 = TopicWithCallbacks.create! title: "arel", author_name: nil
+ topic2 = TopicWithCallbacks.create! title: "activerecord", author_name: nil
+ topics = TopicWithCallbacks.where(id: [topic1.id, topic2.id])
+ topics.update(title: "adequaterecord")
+
+ assert_equal TopicWithCallbacks.count, TopicWithCallbacks.topic_count
+
+ assert_equal "adequaterecord", topic1.reload.title
+ assert_equal "adequaterecord", topic2.reload.title
+ # Testing that the before_update callbacks have run
+ assert_equal "David", topic1.reload.author_name
+ assert_equal "David", topic2.reload.author_name
+ end
+
+ def test_update_with_ids_on_relation
+ topic1 = TopicWithCallbacks.create!(title: "arel", author_name: nil)
+ topic2 = TopicWithCallbacks.create!(title: "activerecord", author_name: nil)
+ topics = TopicWithCallbacks.none
+ topics.update(
+ [topic1.id, topic2.id],
+ [{ title: "adequaterecord" }, { title: "adequaterecord" }]
+ )
+
+ assert_equal TopicWithCallbacks.count, TopicWithCallbacks.topic_count
+
+ assert_equal "adequaterecord", topic1.reload.title
+ assert_equal "adequaterecord", topic2.reload.title
+ # Testing that the before_update callbacks have run
+ assert_equal "David", topic1.reload.author_name
+ assert_equal "David", topic2.reload.author_name
+ end
+
+ def test_update_on_relation_passing_active_record_object_is_not_permitted
+ topic = Topic.create!(title: "Foo", author_name: nil)
+ assert_raises(ArgumentError) do
+ Topic.where(id: topic.id).update(topic, title: "Bar")
+ end
+ end
+
+ # Oracle UPDATE does not support ORDER BY
+ unless current_adapter?(:OracleAdapter)
+ def test_update_all_ignores_order_without_limit_from_association
+ author = authors(:david)
+ assert_nothing_raised do
+ assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ])
+ end
+ end
+
+ def test_update_all_doesnt_ignore_order
+ assert_equal authors(:david).id + 1, authors(:mary).id # make sure there is going to be a duplicate PK error
+ test_update_with_order_succeeds = lambda do |order|
+ begin
+ Author.order(order).update_all("id = id + 1")
+ rescue ActiveRecord::ActiveRecordError
+ false
+ end
+ end
+
+ if test_update_with_order_succeeds.call("id DESC")
+ # test that this wasn't a fluke and using an incorrect order results in an exception
+ assert_not test_update_with_order_succeeds.call("id ASC")
+ else
+ # test that we're failing because the current Arel's engine doesn't support UPDATE ORDER BY queries is using subselects instead
+ assert_sql(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC\)\z/i) do
+ test_update_with_order_succeeds.call("id DESC")
+ end
+ end
+ end
+
+ def test_update_all_with_order_and_limit_updates_subset_only
+ author = authors(:david)
+ limited_posts = author.posts_sorted_by_id_limited
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
+ assert_equal "bulk update!", posts(:welcome).body
+ assert_not_equal "bulk update!", posts(:thinking).body
+ end
+
+ def test_update_all_with_order_and_limit_and_offset_updates_subset_only
+ author = authors(:david)
+ limited_posts = author.posts_sorted_by_id_limited.offset(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
+ assert_equal "bulk update!", posts(:thinking).body
+ assert_not_equal "bulk update!", posts(:welcome).body
+ end
+ end
+end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 5412ab5def..9914a61033 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -9,6 +9,7 @@ require "models/comment"
require "models/author"
require "models/entrant"
require "models/developer"
+require "models/project"
require "models/person"
require "models/computer"
require "models/reply"
@@ -28,13 +29,6 @@ require "models/subscriber"
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :author_addresses, :topics, :entrants, :developers, :people, :companies, :developers_projects, :accounts, :categories, :categorizations, :categories_posts, :posts, :comments, :tags, :taggings, :cars, :minivans
- class TopicWithCallbacks < ActiveRecord::Base
- self.table_name = :topics
- cattr_accessor :topic_count
- before_update { |topic| topic.author_name = "David" if topic.author_name.blank? }
- after_update { |topic| topic.class.topic_count = topic.class.count }
- end
-
def test_do_not_double_quote_string_id
van = Minivan.last
assert van
@@ -862,45 +856,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal authors(:bob), authors.last
end
- def test_destroy_all
- davids = Author.where(name: "David")
-
- # Force load
- assert_equal [authors(:david)], davids.to_a
- assert_predicate davids, :loaded?
-
- assert_difference("Author.count", -1) { davids.destroy_all }
-
- assert_equal [], davids.to_a
- assert_predicate davids, :loaded?
- end
-
- def test_delete_all
- davids = Author.where(name: "David")
-
- assert_difference("Author.count", -1) { davids.delete_all }
- assert_not_predicate davids, :loaded?
- end
-
- def test_delete_all_loaded
- davids = Author.where(name: "David")
-
- # Force load
- assert_equal [authors(:david)], davids.to_a
- assert_predicate davids, :loaded?
-
- assert_difference("Author.count", -1) { davids.delete_all }
-
- assert_equal [], davids.to_a
- assert_predicate davids, :loaded?
- end
-
- def test_delete_all_with_unpermitted_relation_raises_error
- assert_raises(ActiveRecord::ActiveRecordError) { Author.distinct.delete_all }
- assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all }
- assert_raises(ActiveRecord::ActiveRecordError) { Author.having("SUM(id) < 3").delete_all }
- end
-
def test_select_with_aggregates
posts = Post.select(:title, :body)
@@ -985,14 +940,6 @@ class RelationTest < ActiveRecord::TestCase
assert_queries(1) { assert_equal 11, posts.load.size }
end
- def test_update_all_with_scope
- tag = Tag.first
- Post.tagged_with(tag.id).update_all title: "rofl"
- list = Post.tagged_with(tag.id).all.to_a
- assert_operator list.length, :>, 0
- list.each { |post| assert_equal "rofl", post.title }
- end
-
def test_count_explicit_columns
Post.update_all(comments_count: nil)
posts = Post.all
@@ -1405,6 +1352,16 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "cock", hens.new.name
end
+ def test_create_with_nested_attributes
+ assert_difference("Project.count", 1) do
+ developers = Developer.where(name: "Aaron")
+ developers = developers.create_with(
+ projects_attributes: [{ name: "p1" }]
+ )
+ developers.create!
+ end
+ end
+
def test_except
relation = Post.where(author_id: 1).order("id ASC").limit(1)
assert_equal [posts(:welcome)], relation.to_a
@@ -1486,114 +1443,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal authors(:david), Author.order("id DESC , name DESC").last
end
- def test_update_all_with_blank_argument
- assert_raises(ArgumentError) { Comment.update_all({}) }
- end
-
- def test_update_all_with_joins
- comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id)
- count = comments.count
-
- assert_equal count, comments.update_all(post_id: posts(:thinking).id)
- assert_equal posts(:thinking), comments(:greetings).post
- end
-
- def test_update_all_with_joins_and_limit
- comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).limit(1)
- assert_equal 1, comments.update_all(post_id: posts(:thinking).id)
- end
-
- def test_update_all_with_joins_and_limit_and_order
- comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("comments.id").limit(1)
- assert_equal 1, comments.update_all(post_id: posts(:thinking).id)
- assert_equal posts(:thinking), comments(:greetings).post
- assert_equal posts(:welcome), comments(:more_greetings).post
- end
-
- def test_update_all_with_joins_and_offset
- all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id)
- count = all_comments.count
- comments = all_comments.offset(1)
-
- assert_equal count - 1, comments.update_all(post_id: posts(:thinking).id)
- end
-
- def test_update_all_with_joins_and_offset_and_order
- all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("posts.id", "comments.id")
- count = all_comments.count
- comments = all_comments.offset(1)
-
- assert_equal count - 1, comments.update_all(post_id: posts(:thinking).id)
- assert_equal posts(:thinking), comments(:more_greetings).post
- assert_equal posts(:welcome), comments(:greetings).post
- end
-
- def test_touch_all_updates_records_timestamps
- david = developers(:david)
- david_previously_updated_at = david.updated_at
- jamis = developers(:jamis)
- jamis_previously_updated_at = jamis.updated_at
- Developer.where(name: "David").touch_all
-
- assert_not_equal david_previously_updated_at, david.reload.updated_at
- assert_equal jamis_previously_updated_at, jamis.reload.updated_at
- end
-
- def test_touch_all_with_custom_timestamp
- developer = developers(:david)
- previously_created_at = developer.created_at
- previously_updated_at = developer.updated_at
- Developer.where(name: "David").touch_all(:created_at)
- developer = developer.reload
-
- assert_not_equal previously_created_at, developer.created_at
- assert_not_equal previously_updated_at, developer.updated_at
- end
-
- def test_touch_all_with_given_time
- developer = developers(:david)
- previously_created_at = developer.created_at
- previously_updated_at = developer.updated_at
- new_time = Time.utc(2015, 2, 16, 4, 54, 0)
- Developer.where(name: "David").touch_all(:created_at, time: new_time)
- developer = developer.reload
-
- assert_not_equal previously_created_at, developer.created_at
- assert_not_equal previously_updated_at, developer.updated_at
- assert_equal new_time, developer.created_at
- assert_equal new_time, developer.updated_at
- end
-
- def test_touch_all_updates_locking_column
- person = people(:david)
-
- assert_difference -> { person.reload.lock_version }, +1 do
- Person.where(first_name: "David").touch_all
- end
- end
-
- def test_update_on_relation
- topic1 = TopicWithCallbacks.create! title: "arel", author_name: nil
- topic2 = TopicWithCallbacks.create! title: "activerecord", author_name: nil
- topics = TopicWithCallbacks.where(id: [topic1.id, topic2.id])
- topics.update(title: "adequaterecord")
-
- assert_equal TopicWithCallbacks.count, TopicWithCallbacks.topic_count
-
- assert_equal "adequaterecord", topic1.reload.title
- assert_equal "adequaterecord", topic2.reload.title
- # Testing that the before_update callbacks have run
- assert_equal "David", topic1.reload.author_name
- assert_equal "David", topic2.reload.author_name
- end
-
- def test_update_on_relation_passing_active_record_object_is_not_permitted
- topic = Topic.create!(title: "Foo", author_name: nil)
- assert_raises(ArgumentError) do
- Topic.where(id: topic.id).update(topic, title: "Bar")
- end
- end
-
def test_distinct
tag1 = Tag.create(name: "Foo")
tag2 = Tag.create(name: "Foo")
diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb
index 68fcafb682..825aee2423 100644
--- a/activerecord/test/cases/result_test.rb
+++ b/activerecord/test/cases/result_test.rb
@@ -21,12 +21,22 @@ module ActiveRecord
assert_equal 3, result.length
end
- test "to_hash returns row_hashes" do
+ test "to_a returns row_hashes" do
assert_equal [
{ "col_1" => "row 1 col 1", "col_2" => "row 1 col 2" },
{ "col_1" => "row 2 col 1", "col_2" => "row 2 col 2" },
{ "col_1" => "row 3 col 1", "col_2" => "row 3 col 2" },
- ], result.to_hash
+ ], result.to_a
+ end
+
+ test "to_hash (deprecated) returns row_hashes" do
+ assert_deprecated do
+ assert_equal [
+ { "col_1" => "row 1 col 1", "col_2" => "row 1 col 2" },
+ { "col_1" => "row 2 col 1", "col_2" => "row 2 col 2" },
+ { "col_1" => "row 3 col 1", "col_2" => "row 3 col 2" },
+ ], result.to_hash
+ end
end
test "first returns first row as a hash" do
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 75b68b521c..db13f20a39 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -226,6 +226,20 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.float\s+"temperature"$}, output
end
+ if ActiveRecord::Base.connection.supports_expression_index?
+ def test_schema_dump_expression_indices
+ index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
+
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_match %r{CASE.+lower\(\(name\)::text\)}i, index_definition
+ elsif current_adapter?(:SQLite3Adapter)
+ assert_match %r{CASE.+lower\(name\)}i, index_definition
+ else
+ assert false
+ end
+ end
+ end
+
if current_adapter?(:Mysql2Adapter)
def test_schema_dump_includes_length_for_mysql_binary_fields
output = standard_dump
@@ -278,11 +292,6 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.decimal\s+"decimal_array_default",\s+default: \["1.23", "3.45"\],\s+array: true}, output
end
- def test_schema_dump_expression_indices
- index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
- assert_match %r{CASE.+lower\(\(name\)::text\)}i, index_definition
- end
-
def test_schema_dump_interval_type
output = dump_table_schema "postgresql_times"
assert_match %r{t\.interval\s+"time_interval"$}, output
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index e3a34aa50d..6281712df6 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -4,6 +4,7 @@ require "cases/helper"
require "models/post"
require "models/comment"
require "models/developer"
+require "models/project"
require "models/computer"
require "models/vehicle"
require "models/cat"
@@ -366,6 +367,21 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal "Jamis", jamis.name
end
+ def test_create_with_takes_precedence_over_where
+ developer = Developer.where(name: nil).create_with(name: "Aaron").new
+ assert_equal "Aaron", developer.name
+ end
+
+ def test_create_with_nested_attributes
+ assert_difference("Project.count", 1) do
+ Developer.create_with(
+ projects_attributes: [{ name: "p1" }]
+ ).scoping do
+ Developer.create!(name: "Aaron")
+ end
+ end
+ end
+
# FIXME: I don't know if this is *desired* behavior, but it is *today's*
# behavior.
def test_create_with_empty_hash_will_not_reset
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 4214f347fb..f707951a16 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -489,7 +489,7 @@ class NamedScopingTest < ActiveRecord::TestCase
[:public_method, :protected_method, :private_method].each do |reserved_method|
assert Topic.respond_to?(reserved_method, true)
assert_called(ActiveRecord::Base.logger, :warn) do
- silence_warnings { Topic.scope(reserved_method, -> {}) }
+ silence_warnings { Topic.scope(reserved_method, -> { }) }
end
end
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 544adc9b39..b4f4379e5e 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -236,8 +236,8 @@ class RelationScopingTest < ActiveRecord::TestCase
SpecialComment.unscoped.created
end
- assert_nil Comment.current_scope
- assert_nil SpecialComment.current_scope
+ assert_nil Comment.send(:current_scope)
+ assert_nil SpecialComment.send(:current_scope)
end
def test_scoping_respects_current_class
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 4cd4515c3b..1192b30b14 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -322,7 +322,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic = Topic.create!(content: {})
topic2 = Topic.create!(content: nil)
- assert_equal [topic, topic2], Topic.where(content: nil)
+ assert_equal [topic, topic2], Topic.where(content: nil).sort_by(&:id)
end
def test_nil_is_always_persisted_as_null
diff --git a/activerecord/test/cases/suppressor_test.rb b/activerecord/test/cases/suppressor_test.rb
index b68f0033d9..9be5356901 100644
--- a/activerecord/test/cases/suppressor_test.rb
+++ b/activerecord/test/cases/suppressor_test.rb
@@ -66,7 +66,7 @@ class SuppressorTest < ActiveRecord::TestCase
def test_suppresses_when_nested_multiple_times
assert_no_difference -> { Notification.count } do
Notification.suppress do
- Notification.suppress {}
+ Notification.suppress { }
Notification.create
Notification.create!
Notification.new.save
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index a6efe3fa5e..d674bd562f 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -46,35 +46,46 @@ module ActiveRecord
class DatabaseTasksUtilsTask < ActiveRecord::TestCase
def test_raises_an_error_when_called_with_protected_environment
- ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
-
protected_environments = ActiveRecord::Base.protected_environments
current_env = ActiveRecord::Base.connection.migration_context.current_environment
- assert_not_includes protected_environments, current_env
- # Assert no error
- ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
-
- ActiveRecord::Base.protected_environments = [current_env]
- assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ assert_called_on_instance_of(
+ ActiveRecord::MigrationContext,
+ :current_version,
+ times: 6,
+ returns: 1
+ ) do
+ assert_not_includes protected_environments, current_env
+ # Assert no error
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+
+ ActiveRecord::Base.protected_environments = [current_env]
+
+ assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
end
ensure
ActiveRecord::Base.protected_environments = protected_environments
end
def test_raises_an_error_when_called_with_protected_environment_which_name_is_a_symbol
- ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
-
protected_environments = ActiveRecord::Base.protected_environments
current_env = ActiveRecord::Base.connection.migration_context.current_environment
- assert_not_includes protected_environments, current_env
- # Assert no error
- ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
-
- ActiveRecord::Base.protected_environments = [current_env.to_sym]
- assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ assert_called_on_instance_of(
+ ActiveRecord::MigrationContext,
+ :current_version,
+ times: 6,
+ returns: 1
+ ) do
+ assert_not_includes protected_environments, current_env
+ # Assert no error
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+
+ ActiveRecord::Base.protected_environments = [current_env.to_sym]
+ assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
end
ensure
ActiveRecord::Base.protected_environments = protected_environments
@@ -82,10 +93,14 @@ module ActiveRecord
def test_raises_an_error_if_no_migrations_have_been_made
ActiveRecord::InternalMetadata.stub(:table_exists?, false) do
- ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
-
- assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do
- ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ assert_called_on_instance_of(
+ ActiveRecord::MigrationContext,
+ :current_version,
+ returns: 1
+ ) do
+ assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
end
end
end
@@ -213,18 +228,17 @@ module ActiveRecord
end
private
-
def with_stubbed_configurations_establish_connection
- ActiveRecord::Base.stub(:configurations, @configurations) do
- # To refrain from connecting to a newly created empty DB in
- # sqlite3_mem tests
- ActiveRecord::Base.connection_handler.stub(
- :establish_connection,
- nil
- ) do
- yield
- end
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ # To refrain from connecting to a newly created empty DB in
+ # sqlite3_mem tests
+ ActiveRecord::Base.connection_handler.stub(:establish_connection, nil) do
+ yield
end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
end
end
@@ -233,7 +247,7 @@ module ActiveRecord
@configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
- "production" => { "url" => "prod-db-url" }
+ "production" => { "url" => "abstract://prod-db-host/prod-db" }
}
end
@@ -256,7 +270,7 @@ module ActiveRecord
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
- ["url" => "prod-db-url"],
+ ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"],
) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new("production")
@@ -315,13 +329,15 @@ module ActiveRecord
end
private
-
def with_stubbed_configurations_establish_connection
- ActiveRecord::Base.stub(:configurations, @configurations) do
- ActiveRecord::Base.stub(:establish_connection, nil) do
- yield
- end
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ ActiveRecord::Base.connection_handler.stub(:establish_connection, nil) do
+ yield
end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
end
end
@@ -330,7 +346,7 @@ module ActiveRecord
@configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
- "production" => { "primary" => { "url" => "prod-db-url" }, "secondary" => { "url" => "secondary-prod-db-url" } }
+ "production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } }
}
end
@@ -357,8 +373,8 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks,
:create,
[
- ["url" => "prod-db-url"],
- ["url" => "secondary-prod-db-url"]
+ ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"],
+ ["adapter" => "abstract", "database" => "secondary-prod-db", "host" => "secondary-prod-db-host"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.create_current(
@@ -426,13 +442,15 @@ module ActiveRecord
end
private
-
def with_stubbed_configurations_establish_connection
- ActiveRecord::Base.stub(:configurations, @configurations) do
- ActiveRecord::Base.stub(:establish_connection, nil) do
- yield
- end
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ ActiveRecord::Base.connection_handler.stub(:establish_connection, nil) do
+ yield
end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
end
end
@@ -465,7 +483,7 @@ module ActiveRecord
def test_ignores_configurations_without_databases
@configurations[:development]["database"] = nil
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
@@ -475,7 +493,7 @@ module ActiveRecord
def test_ignores_remote_databases
@configurations[:development]["host"] = "my.server.tld"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
@@ -485,7 +503,7 @@ module ActiveRecord
def test_warning_for_remote_databases
@configurations[:development]["host"] = "my.server.tld"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
ActiveRecord::Tasks::DatabaseTasks.drop_all
assert_match "This task only modifies local databases. my-db is on a remote host.",
@@ -496,7 +514,7 @@ module ActiveRecord
def test_drops_configurations_with_local_ip
@configurations[:development]["host"] = "127.0.0.1"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
@@ -506,7 +524,7 @@ module ActiveRecord
def test_drops_configurations_with_local_host
@configurations[:development]["host"] = "localhost"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
@@ -516,12 +534,22 @@ module ActiveRecord
def test_drops_configurations_with_blank_hosts
@configurations[:development]["host"] = nil
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
end
end
+
+ private
+ def with_stubbed_configurations
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ yield
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
+ end
end
class DatabaseTasksDropCurrentTest < ActiveRecord::TestCase
@@ -529,12 +557,12 @@ module ActiveRecord
@configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
- "production" => { "url" => "prod-db-url" }
+ "production" => { "url" => "abstract://prod-db-host/prod-db" }
}
end
def test_drops_current_environment_database
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
["database" => "test-db"]) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
@@ -545,9 +573,9 @@ module ActiveRecord
end
def test_drops_current_environment_database_with_url
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
- ["url" => "prod-db-url"]) do
+ ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"]) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new("production")
)
@@ -556,7 +584,7 @@ module ActiveRecord
end
def test_drops_test_and_development_databases_when_env_was_not_specified
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -576,7 +604,7 @@ module ActiveRecord
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -593,6 +621,16 @@ module ActiveRecord
ensure
ENV["RAILS_ENV"] = old_env
end
+
+ private
+ def with_stubbed_configurations
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ yield
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
+ end
end
class DatabaseTasksDropCurrentThreeTierTest < ActiveRecord::TestCase
@@ -600,12 +638,12 @@ module ActiveRecord
@configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
- "production" => { "primary" => { "url" => "prod-db-url" }, "secondary" => { "url" => "secondary-prod-db-url" } }
+ "production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } }
}
end
def test_drops_current_environment_database
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -622,13 +660,13 @@ module ActiveRecord
end
def test_drops_current_environment_database_with_url
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
[
- ["url" => "prod-db-url"],
- ["url" => "secondary-prod-db-url"]
+ ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"],
+ ["adapter" => "abstract", "database" => "secondary-prod-db", "host" => "secondary-prod-db-host"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
@@ -639,7 +677,7 @@ module ActiveRecord
end
def test_drops_test_and_development_databases_when_env_was_not_specified
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -661,7 +699,7 @@ module ActiveRecord
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -680,6 +718,16 @@ module ActiveRecord
ensure
ENV["RAILS_ENV"] = old_env
end
+
+ private
+ def with_stubbed_configurations
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ yield
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
+ end
end
if current_adapter?(:SQLite3Adapter) && !in_memory_db?
@@ -833,37 +881,44 @@ module ActiveRecord
class DatabaseTasksPurgeCurrentTest < ActiveRecord::TestCase
def test_purges_current_environment_database
+ old_configurations = ActiveRecord::Base.configurations
configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
"production" => { "database" => "prod-db" }
}
- ActiveRecord::Base.stub(:configurations, configurations) do
- assert_called_with(
- ActiveRecord::Tasks::DatabaseTasks,
- :purge,
- ["database" => "prod-db"]
- ) do
- assert_called_with(ActiveRecord::Base, :establish_connection, [:production]) do
- ActiveRecord::Tasks::DatabaseTasks.purge_current("production")
- end
+
+ ActiveRecord::Base.configurations = configurations
+
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :purge,
+ ["database" => "prod-db"]
+ ) do
+ assert_called_with(ActiveRecord::Base, :establish_connection, [:production]) do
+ ActiveRecord::Tasks::DatabaseTasks.purge_current("production")
end
end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
end
end
class DatabaseTasksPurgeAllTest < ActiveRecord::TestCase
def test_purge_all_local_configurations
+ old_configurations = ActiveRecord::Base.configurations
configurations = { development: { "database" => "my-db" } }
- ActiveRecord::Base.stub(:configurations, configurations) do
- assert_called_with(
- ActiveRecord::Tasks::DatabaseTasks,
- :purge,
- ["database" => "my-db"]
- ) do
- ActiveRecord::Tasks::DatabaseTasks.purge_all
- end
+ ActiveRecord::Base.configurations = configurations
+
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :purge,
+ ["database" => "my-db"]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge_all
end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
end
end
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index eeb4222d97..4d6dff68f9 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -152,10 +152,14 @@ if current_adapter?(:Mysql2Adapter)
end
def test_establishes_connection_to_mysql_database
- with_stubbed_connection_establish_connection do
- ActiveRecord::Base.expects(:establish_connection).with @configuration
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [@configuration]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
end
end
@@ -196,10 +200,14 @@ if current_adapter?(:Mysql2Adapter)
end
def test_establishes_connection_to_the_appropriate_database
- with_stubbed_connection_establish_connection do
- ActiveRecord::Base.expects(:establish_connection).with(@configuration)
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [@configuration]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
end
end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index 00005e7a0d..065ba7734c 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -112,7 +112,7 @@ if current_adapter?(:PostgreSQLAdapter)
ActiveRecord::Base.stub(:connection, @connection) do
ActiveRecord::Base.stub(:establish_connection, -> * { raise Exception }) do
assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration }
- assert_match "Couldn't create database for #{@configuration.inspect}", $stderr.string
+ assert_match "Couldn't create '#{@configuration['database']}' database. Please check your configuration.", $stderr.string
end
end
end
@@ -166,12 +166,17 @@ if current_adapter?(:PostgreSQLAdapter)
def test_establishes_connection_to_postgresql_database
ActiveRecord::Base.stub(:connection, @connection) do
- ActiveRecord::Base.expects(:establish_connection).with(
- "adapter" => "postgresql",
- "database" => "postgres",
- "schema_search_path" => "public"
- )
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ "adapter" => "postgresql",
+ "database" => "postgres",
+ "schema_search_path" => "public"
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
end
end
@@ -491,7 +496,7 @@ if current_adapter?(:PostgreSQLAdapter)
assert_called_with(
Kernel,
:system,
- ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, @configuration["database"]],
+ ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-X", "-f", filename, @configuration["database"]],
returns: true
) do
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
@@ -500,7 +505,7 @@ if current_adapter?(:PostgreSQLAdapter)
def test_structure_load_with_extra_flags
filename = "awesome-file.sql"
- expected_command = ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, "--noop", @configuration["database"]]
+ expected_command = ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-X", "-f", filename, "--noop", @configuration["database"]]
assert_called_with(Kernel, :system, expected_command, returns: true) do
with_structure_load_flags(["--noop"]) do
@@ -514,7 +519,7 @@ if current_adapter?(:PostgreSQLAdapter)
assert_called_with(
Kernel,
:system,
- ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, @configuration["database"]],
+ ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-X", "-f", filename, @configuration["database"]],
returns: true
) do
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index 7eb062b456..c1092b97c1 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -47,9 +47,9 @@ if current_adapter?(:SQLite3Adapter)
def test_db_create_with_file_does_nothing
File.stub(:exist?, true) do
- ActiveRecord::Base.expects(:establish_connection).never
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ assert_not_called(ActiveRecord::Base, :establish_connection) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ end
end
end
@@ -62,7 +62,7 @@ if current_adapter?(:SQLite3Adapter)
def test_db_create_with_error_prints_message
ActiveRecord::Base.stub(:establish_connection, proc { raise Exception }) do
assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" }
- assert_match "Couldn't create database for #{@configuration.inspect}", $stderr.string
+ assert_match "Couldn't create '#{@configuration['database']}' database. Please check your configuration.", $stderr.string
end
end
end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 409b07e56c..40947767f3 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -31,6 +31,7 @@ module ActiveRecord
end
def capture_sql
+ ActiveRecord::Base.connection.materialize_transactions
SQLCounter.clear_log
yield
SQLCounter.log_all.dup
@@ -48,6 +49,7 @@ module ActiveRecord
def assert_queries(num = 1, options = {})
ignore_none = options.fetch(:ignore_none) { num == :any }
+ ActiveRecord::Base.connection.materialize_transactions
SQLCounter.clear_log
x = yield
the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log
diff --git a/activerecord/test/cases/transaction_isolation_test.rb b/activerecord/test/cases/transaction_isolation_test.rb
index eaafd13360..2932969412 100644
--- a/activerecord/test/cases/transaction_isolation_test.rb
+++ b/activerecord/test/cases/transaction_isolation_test.rb
@@ -11,7 +11,7 @@ unless ActiveRecord::Base.connection.supports_transaction_isolation?
test "setting the isolation level raises an error" do
assert_raises(ActiveRecord::TransactionIsolationError) do
- Tag.transaction(isolation: :serializable) {}
+ Tag.transaction(isolation: :serializable) { Tag.connection.materialize_transactions }
end
end
end
@@ -90,7 +90,7 @@ else
test "setting isolation when joining a transaction raises an error" do
Tag.transaction do
assert_raises(ActiveRecord::TransactionIsolationError) do
- Tag.transaction(isolation: :serializable) {}
+ Tag.transaction(isolation: :serializable) { }
end
end
end
@@ -98,7 +98,7 @@ else
test "setting isolation when starting a nested transaction raises error" do
Tag.transaction do
assert_raises(ActiveRecord::TransactionIsolationError) do
- Tag.transaction(requires_new: true, isolation: :serializable) {}
+ Tag.transaction(requires_new: true, isolation: :serializable) { }
end
end
end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 46463ac414..50740054f7 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -575,7 +575,7 @@ class TransactionTest < ActiveRecord::TestCase
assert_called(Topic.connection, :rollback_db_transaction) do
e = assert_raise RuntimeError do
Topic.transaction do
- # do nothing
+ Topic.connection.materialize_transactions
end
end
assert_equal "OH NOES", e.message
@@ -930,7 +930,7 @@ class TransactionTest < ActiveRecord::TestCase
klass = Class.new(ActiveRecord::Base) do
self.table_name = "transaction_without_primary_keys"
- after_commit {} # necessary to trigger the has_transactional_callbacks branch
+ after_commit { } # necessary to trigger the has_transactional_callbacks branch
end
assert_no_difference(-> { klass.count }) do
@@ -943,6 +943,76 @@ class TransactionTest < ActiveRecord::TestCase
connection.drop_table "transaction_without_primary_keys", if_exists: true
end
+ def test_empty_transaction_is_not_materialized
+ assert_no_queries do
+ Topic.transaction { }
+ end
+ end
+
+ def test_unprepared_statement_materializes_transaction
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction { Topic.where("1=1").first }
+ end
+ end
+
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_prepared_statement_materializes_transaction
+ Topic.first
+
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction { Topic.first }
+ end
+ end
+ end
+
+ def test_savepoint_does_not_materialize_transaction
+ assert_no_queries do
+ Topic.transaction do
+ Topic.transaction(requires_new: true) { }
+ end
+ end
+ end
+
+ def test_raising_does_not_materialize_transaction
+ assert_raise(RuntimeError) do
+ assert_no_queries do
+ Topic.transaction { raise }
+ end
+ end
+ end
+
+ def test_accessing_raw_connection_materializes_transaction
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction { Topic.connection.raw_connection }
+ end
+ end
+
+ def test_accessing_raw_connection_disables_lazy_transactions
+ Topic.connection.raw_connection
+
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction { }
+ end
+ end
+
+ def test_checking_in_connection_reenables_lazy_transactions
+ connection = Topic.connection_pool.checkout
+ connection.raw_connection
+ Topic.connection_pool.checkin connection
+
+ assert_no_queries do
+ connection.transaction { }
+ end
+ end
+
+ def test_transactions_can_be_manually_materialized
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction do
+ Topic.connection.materialize_transactions
+ end
+ end
+ end
+
private
%w(validation save destroy).each do |filter|
diff --git a/activerecord/test/cases/type/type_map_test.rb b/activerecord/test/cases/type/type_map_test.rb
index f3699c11a2..1ce515a90c 100644
--- a/activerecord/test/cases/type/type_map_test.rb
+++ b/activerecord/test/cases/type/type_map_test.rb
@@ -32,7 +32,7 @@ module ActiveRecord
end
def test_fuzzy_lookup
- string = String.new
+ string = +""
mapping = TypeMap.new
mapping.register_type(/varchar/i, string)
@@ -41,7 +41,7 @@ module ActiveRecord
end
def test_aliasing_types
- string = String.new
+ string = +""
mapping = TypeMap.new
mapping.register_type(/string/i, string)
@@ -73,7 +73,7 @@ module ActiveRecord
end
def test_register_proc
- string = String.new
+ string = +""
binary = Binary.new
mapping = TypeMap.new
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index a33877f43a..66763c727f 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -3,11 +3,11 @@
require "cases/helper"
require "models/topic"
require "models/reply"
-require "models/person"
require "models/developer"
require "models/computer"
require "models/parrot"
require "models/company"
+require "models/price_estimate"
class ValidationsTest < ActiveRecord::TestCase
fixtures :topics, :developers
@@ -183,6 +183,22 @@ class ValidationsTest < ActiveRecord::TestCase
assert_not_predicate klass.new(wibble: BigDecimal("97.179")), :valid?
end
+ def test_numericality_validator_wont_be_affected_by_custom_getter
+ price_estimate = PriceEstimate.new(price: 50)
+
+ assert_equal "$50.00", price_estimate.price
+ assert_equal 50, price_estimate.price_before_type_cast
+ assert_equal 50, price_estimate.read_attribute(:price)
+
+ assert_predicate price_estimate, :price_came_from_user?
+ assert_predicate price_estimate, :valid?
+
+ price_estimate.save!
+
+ assert_not_predicate price_estimate, :price_came_from_user?
+ assert_predicate price_estimate, :valid?
+ end
+
def test_acceptance_validator_doesnt_require_db_connection
klass = Class.new(ActiveRecord::Base) do
self.table_name = "posts"
diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml
index 4bcb2aeea6..be337ddcd8 100644
--- a/activerecord/test/config.example.yml
+++ b/activerecord/test/config.example.yml
@@ -54,11 +54,11 @@ connections:
mysql2:
arunit:
username: rails
- encoding: utf8
- collation: utf8_unicode_ci
+ encoding: utf8mb4
+ collation: utf8mb4_unicode_ci
arunit2:
username: rails
- encoding: utf8
+ encoding: utf8mb4
oracle:
arunit:
diff --git a/activerecord/test/fixtures/citations.yml b/activerecord/test/fixtures/citations.yml
new file mode 100644
index 0000000000..d31cb8efa1
--- /dev/null
+++ b/activerecord/test/fixtures/citations.yml
@@ -0,0 +1,4 @@
+<% 65536.times do |i| %>
+fixture_no_<%= i %>:
+ id: <%= i %>
+<% end %>
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 75932c7eb6..8b5a2fa0c8 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -81,7 +81,7 @@ class Author < ActiveRecord::Base
after_add: [:log_after_adding, Proc.new { |o, r| o.post_log << "after_adding_proc#{r.id || '<new>'}" }]
has_many :unchangeable_posts, class_name: "Post", before_add: :raise_exception, after_add: :log_after_adding
- has_many :categorizations, -> {}
+ has_many :categorizations, -> { }
has_many :categories, through: :categorizations
has_many :named_categories, through: :categorizations
diff --git a/activerecord/test/models/citation.rb b/activerecord/test/models/citation.rb
index 3d786f27eb..cee3d18173 100644
--- a/activerecord/test/models/citation.rb
+++ b/activerecord/test/models/citation.rb
@@ -2,4 +2,5 @@
class Citation < ActiveRecord::Base
belongs_to :reference_of, class_name: "Book", foreign_key: :book2_id
+ has_many :citations
end
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index d4d5275b78..838f515aad 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -122,6 +122,12 @@ class RestrictedWithErrorFirm < Company
has_many :companies, -> { order("id") }, foreign_key: "client_of", dependent: :restrict_with_error
end
+class Agency < Firm
+ has_many :projects, foreign_key: :firm_id
+
+ accepts_nested_attributes_for :projects
+end
+
class Client < Company
belongs_to :firm, foreign_key: "client_of"
belongs_to :firm_with_basic_id, class_name: "Firm", foreign_key: "firm_id"
@@ -204,4 +210,12 @@ end
class VerySpecialClient < SpecialClient
end
+class NewlyContractedCompany < Company
+ has_many :new_contracts, foreign_key: "company_id"
+
+ before_save do
+ self.new_contracts << NewContract.new
+ end
+end
+
require "models/account"
diff --git a/activerecord/test/models/contract.rb b/activerecord/test/models/contract.rb
index f273badd85..3f663375c4 100644
--- a/activerecord/test/models/contract.rb
+++ b/activerecord/test/models/contract.rb
@@ -20,3 +20,7 @@ class Contract < ActiveRecord::Base
@bye_count += 1
end
end
+
+class NewContract < Contract
+ validates :company_id, presence: true
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 640cdb33b4..528585fb75 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -324,7 +324,7 @@ class FakeKlass
table[name]
end
- def enforce_raw_sql_whitelist(*args)
+ def disallow_raw_sql!(*args)
# noop
end
diff --git a/activerecord/test/models/price_estimate.rb b/activerecord/test/models/price_estimate.rb
index f1f88d8d8d..669d0991f7 100644
--- a/activerecord/test/models/price_estimate.rb
+++ b/activerecord/test/models/price_estimate.rb
@@ -1,6 +1,14 @@
# frozen_string_literal: true
class PriceEstimate < ActiveRecord::Base
+ include ActiveSupport::NumberHelper
+
belongs_to :estimate_of, polymorphic: true
belongs_to :thing, polymorphic: true
+
+ validates_numericality_of :price
+
+ def price
+ number_to_currency super
+ end
end
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index 0ea110f4f8..0807bcf875 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -9,6 +9,10 @@ class Reply < Topic
has_many :silly_unique_replies, dependent: :destroy, foreign_key: "parent_id"
end
+class SillyReply < Topic
+ belongs_to :reply, foreign_key: "parent_id", counter_cache: :replies_count
+end
+
class UniqueReply < Reply
belongs_to :topic, foreign_key: "parent_id", counter_cache: true
validates_uniqueness_of :content, scope: "parent_id"
@@ -54,10 +58,6 @@ class WrongReply < Reply
end
end
-class SillyReply < Reply
- belongs_to :reply, foreign_key: "parent_id", counter_cache: :replies_count
-end
-
module Web
class Reply < Web::Topic
belongs_to :topic, foreign_key: "parent_id", counter_cache: true, class_name: "Web::Topic"
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 72699046f9..4aad6a4498 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -24,7 +24,7 @@ class Topic < ActiveRecord::Base
end
scope "approved_as_string", -> { where(approved: true) }
- scope :anonymous_extension, -> {} do
+ scope :anonymous_extension, -> { } do
def one
1
end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index 8371ba9528..499280cb0c 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -14,6 +14,13 @@ ActiveRecord::Schema.define do
end
end
+ create_table :defaults, force: true do |t|
+ t.date :fixed_date, default: "2004-01-01"
+ t.datetime :fixed_time, default: "2004-01-01 00:00:00"
+ t.column :char1, "char(1)", default: "Y"
+ t.string :char2, limit: 50, default: "a varchar field"
+ end
+
create_table :binary_fields, force: true do |t|
t.binary :var_binary, limit: 255
t.binary :var_binary_large, limit: 4095
@@ -29,7 +36,7 @@ ActiveRecord::Schema.define do
t.index :var_binary
end
- create_table :key_tests, force: true, options: "ENGINE=MyISAM" do |t|
+ create_table :key_tests, force: true do |t|
t.string :awesome
t.string :pizza
t.string :snacks
@@ -39,8 +46,8 @@ ActiveRecord::Schema.define do
end
create_table :collation_tests, id: false, force: true do |t|
- t.string :string_cs_column, limit: 1, collation: "utf8_bin"
- t.string :string_ci_column, limit: 1, collation: "utf8_general_ci"
+ t.string :string_cs_column, limit: 1, collation: "utf8mb4_bin"
+ t.string :string_ci_column, limit: 1, collation: "utf8mb4_general_ci"
t.binary :binary_column, limit: 1
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 266e55f682..2aaf393009 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -160,6 +160,7 @@ ActiveRecord::Schema.define do
create_table :citations, force: true do |t|
t.column :book1_id, :integer
t.column :book2_id, :integer
+ t.references :citation
end
create_table :clubs, force: true do |t|
@@ -946,7 +947,7 @@ ActiveRecord::Schema.define do
end
[:circles, :squares, :triangles, :non_poly_ones, :non_poly_twos].each do |t|
- create_table(t, force: true) {}
+ create_table(t, force: true) { }
end
create_table :men, force: true do |t|
diff --git a/activerecord/test/schema/sqlite_specific_schema.rb b/activerecord/test/schema/sqlite_specific_schema.rb
new file mode 100644
index 0000000000..18192292e4
--- /dev/null
+++ b/activerecord/test/schema/sqlite_specific_schema.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+ActiveRecord::Schema.define do
+ create_table :defaults, force: true do |t|
+ t.date :fixed_date, default: "2004-01-01"
+ t.datetime :fixed_time, default: "2004-01-01 00:00:00"
+ t.column :char1, "char(1)", default: "Y"
+ t.string :char2, limit: 50, default: "a varchar field"
+ t.text :char3, limit: 50, default: "a text field"
+ end
+end