aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md122
-rw-r--r--activerecord/lib/active_record.rb3
-rw-r--r--activerecord/lib/active_record/associations.rb16
-rw-r--r--activerecord/lib/active_record/associations/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb11
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb14
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb37
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb21
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb12
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb2
-rw-r--r--activerecord/lib/active_record/attribute_mutation_tracker.rb9
-rw-r--r--activerecord/lib/active_record/autosave_association.rb4
-rw-r--r--activerecord/lib/active_record/callbacks.rb49
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb64
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb70
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb45
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/column.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/quoting.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb37
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb60
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb66
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb59
-rw-r--r--activerecord/lib/active_record/core.rb12
-rw-r--r--activerecord/lib/active_record/counter_cache.rb3
-rw-r--r--activerecord/lib/active_record/fixtures.rb5
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb11
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb16
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb51
-rw-r--r--activerecord/lib/active_record/model_schema.rb1
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb6
-rw-r--r--activerecord/lib/active_record/persistence.rb30
-rw-r--r--activerecord/lib/active_record/railtie.rb4
-rw-r--r--activerecord/lib/active_record/railties/databases.rake24
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb105
-rw-r--r--activerecord/lib/active_record/relation.rb3
-rw-r--r--activerecord/lib/active_record/relation/batches.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb2
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb3
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb4
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb3
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb40
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb46
-rw-r--r--activerecord/lib/active_record/sanitization.rb2
-rw-r--r--activerecord/lib/active_record/schema.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/secure_token.rb2
-rw-r--r--activerecord/lib/active_record/serialization.rb2
-rw-r--r--activerecord/lib/active_record/store.rb6
-rw-r--r--activerecord/lib/active_record/table_metadata.rb2
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb6
-rw-r--r--activerecord/lib/active_record/transactions.rb1
-rw-r--r--activerecord/lib/active_record/type/serialized.rb2
-rw-r--r--activerecord/lib/active_record/validations/presence.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb32
-rw-r--r--activerecord/lib/rails/generators/active_record/migration.rb8
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb4
-rw-r--r--activerecord/test/cases/adapter_test.rb101
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb5
-rw-r--r--activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb23
-rw-r--r--activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb60
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/sql_types_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/virtual_column_test.rb59
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/infinity_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb54
-rw-r--r--activerecord/test/cases/adapters/postgresql/numbers_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb13
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb22
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb56
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb7
-rw-r--r--activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb59
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb1
-rw-r--r--activerecord/test/cases/associations/eager_test.rb29
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb10
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb5
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb23
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb21
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb7
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations_test.rb5
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb2
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb7
-rw-r--r--activerecord/test/cases/autosave_association_test.rb2
-rw-r--r--activerecord/test/cases/base_test.rb7
-rw-r--r--activerecord/test/cases/calculations_test.rb6
-rw-r--r--activerecord/test/cases/callbacks_test.rb161
-rw-r--r--activerecord/test/cases/coders/yaml_column_test.rb24
-rw-r--r--activerecord/test/cases/column_definition_test.rb58
-rw-r--r--activerecord/test/cases/comment_test.rb2
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb95
-rw-r--r--activerecord/test/cases/connection_adapters/type_lookup_test.rb20
-rw-r--r--activerecord/test/cases/connection_pool_test.rb12
-rw-r--r--activerecord/test/cases/counter_cache_test.rb9
-rw-r--r--activerecord/test/cases/date_time_precision_test.rb2
-rw-r--r--activerecord/test/cases/dirty_test.rb9
-rw-r--r--activerecord/test/cases/finder_test.rb17
-rw-r--r--activerecord/test/cases/fixtures_test.rb10
-rw-r--r--activerecord/test/cases/helper.rb1
-rw-r--r--activerecord/test/cases/json_serialization_test.rb11
-rw-r--r--activerecord/test/cases/locking_test.rb7
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb7
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb113
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb65
-rw-r--r--activerecord/test/cases/migration/index_test.rb16
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb41
-rw-r--r--activerecord/test/cases/migration_test.rb20
-rw-r--r--activerecord/test/cases/migrator_test.rb24
-rw-r--r--activerecord/test/cases/nested_attributes_with_callbacks_test.rb4
-rw-r--r--activerecord/test/cases/persistence_test.rb16
-rw-r--r--activerecord/test/cases/primary_keys_test.rb175
-rw-r--r--activerecord/test/cases/query_cache_test.rb221
-rw-r--r--activerecord/test/cases/reflection_test.rb35
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb3
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb4
-rw-r--r--activerecord/test/cases/relation_test.rb2
-rw-r--r--activerecord/test/cases/relations_test.rb16
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb47
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb2
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb14
-rw-r--r--activerecord/test/cases/statement_cache_test.rb26
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb2
-rw-r--r--activerecord/test/cases/time_precision_test.rb2
-rw-r--r--activerecord/test/cases/timestamp_test.rb2
-rw-r--r--activerecord/test/cases/transactions_test.rb16
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb4
-rw-r--r--activerecord/test/cases/view_test.rb4
-rw-r--r--activerecord/test/fixtures/subscribers.yml2
-rw-r--r--activerecord/test/models/company.rb4
-rw-r--r--activerecord/test/models/customer.rb2
-rw-r--r--activerecord/test/models/developer.rb6
-rw-r--r--activerecord/test/models/family.rb4
-rw-r--r--activerecord/test/models/family_tree.rb4
-rw-r--r--activerecord/test/models/non_primary_key.rb2
-rw-r--r--activerecord/test/models/project.rb2
-rw-r--r--activerecord/test/models/user.rb6
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb33
-rw-r--r--activerecord/test/schema/schema.rb39
-rw-r--r--activerecord/test/schema/sqlite_specific_schema.rb18
179 files changed, 2179 insertions, 1459 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 743a38b087..b27c03d935 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,12 +1,122 @@
+* All integer-like PKs are autoincrement unless they have an explicit default.
+
+ *Matthew Draper*
+
+* Omit redundant `using: :btree` for schema dumping.
+
+ *Ryuta Kamizono*
+
+* Deprecate passing `default` to `index_name_exists?`.
+
+ *Ryuta Kamizono*
+
+* PostgreSQL: schema dumping support for interval and OID columns.
+
+ *Ryuta Kamizono*
+
+* Deprecate `supports_primary_key?` on connection adapters since it's
+ been long unused and unsupported.
+
+ *Ryuta Kamizono*
+
+* Make `table_name=` reset current statement cache,
+ so queries are not run against the previous table name.
+
+ *namusyaka*
+
+* Allow `ActiveRecord::Base#as_json` to be passed a frozen Hash.
+
+ *Isaac Betesh*
+
+* Fix inspection behavior when the :id column is not primary key.
+
+ *namusyaka*
+
+* Deprecate locking records with unpersisted changes.
+
+ *Marc Schütz*
+
+* Remove deprecated behavior that halts callbacks when the return is false.
+
+ *Rafael Mendonça França*
+
+* Deprecate `ColumnDumper#migration_keys`.
+
+ *Ryuta Kamizono*
+
+* Fix `association_primary_key_type` for reflections with symbol primary key.
+
+ Fixes #27864.
+
+ *Daniel Colson*
+
+* Virtual/generated column support for MySQL 5.7.5+ and MariaDB 5.2.0+.
+
+ MySQL generated columns: https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html
+ MariaDB virtual columns: https://mariadb.com/kb/en/mariadb/virtual-computed-columns/
+
+ Declare virtual columns with `t.virtual name, type: …, as: "expression"`.
+ Pass `stored: true` to persist the generated value (false by default).
+
+ Example:
+
+ create_table :generated_columns do |t|
+ t.string :name
+ t.virtual :upper_name, type: :string, as: "UPPER(name)"
+ t.virtual :name_length, type: :integer, as: "LENGTH(name)", stored: true
+ t.index :name_length # May be indexed, too!
+ end
+
+ *Ryuta Kamizono*
+
+* Deprecate `initialize_schema_migrations_table` and `initialize_internal_metadata_table`.
+
+ *Ryuta Kamizono*
+
+* Support foreign key creation for SQLite3.
+
+ *Ryuta Kamizono*
+
+* Place generated migrations into the path set by `config.paths["db/migrate"]`.
+
+ *Kevin Glowacz*
+
+* Raise `ActiveRecord::InvalidForeignKey` when a foreign key constraint fails on SQLite3.
+
+ *Ryuta Kamizono*
+
+* Add the touch option to `#increment!` and `#decrement!`.
+
+ *Hiroaki Izu*
+
+* Deprecate passing a class to the `class_name` because it eagerloads more classes than
+ necessary and potentially creates circular dependencies.
+
+ *Kir Shatrov*
+
+* Raise error when has_many through is defined before through association.
+
+ Fixes #26834.
+
+ *Chris Holmes*
+
+* Deprecate passing `name` to `indexes`.
+
+ *Ryuta Kamizono*
+
+* Remove deprecated tasks: `db:test:clone`, `db:test:clone_schema`, `db:test:clone_structure`.
+
+ *Rafel Mendonça França*
+
* Compare deserialized values for `PostgreSQL::OID::Hstore` types when
- calling `ActiveRecord::Dirty#changed_in_place?`
+ calling `ActiveRecord::Dirty#changed_in_place?`.
Fixes #27502.
*Jon Moss*
-* Raise `ArgumentError` when passing an `ActiveRecord::Base` instance to `.find`
- and `.exists?`.
+* Raise `ArgumentError` when passing an `ActiveRecord::Base` instance to `.find`,
+ `.exists?` and `.update`.
*Rafael Mendonça França*
@@ -107,7 +217,7 @@
*Rafael Mendonça França*
* Set `:time` as a timezone aware type and remove deprecation when
- `config.active_record.time_zone_aware_types` is not explictly set.
+ `config.active_record.time_zone_aware_types` is not explicitly set.
*Rafael Mendonça França*
@@ -191,7 +301,7 @@
*Sean Griffin*
-* Fix that unsigned with zerofill is treated as signed.
+* Don't treat unsigned integers with zerofill as signed.
Fixes #27125.
@@ -397,7 +507,7 @@
*Ryuta Kamizono*
-* Sqlite3 migrations to add a column to an existing table can now be
+* SQLite3 migrations to add a column to an existing table can now be
successfully rolled back when the column was given and invalid column
type.
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 250f48fad9..96b8545dfc 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -44,7 +44,6 @@ module ActiveRecord
autoload :Explain
autoload :Inheritance
autoload :Integration
- autoload :LegacyYamlAdapter
autoload :Migration
autoload :Migrator, "active_record/migration"
autoload :ModelSchema
@@ -85,6 +84,8 @@ module ActiveRecord
autoload :AttributeMethods
autoload :AutosaveAssociation
+ autoload :LegacyYamlAdapter
+
autoload :Relation
autoload :AssociationRelation
autoload :NullRelation
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 1db5fc0dd1..4606c91ffd 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -97,6 +97,16 @@ module ActiveRecord
end
end
+ class HasManyThroughOrderError < ActiveRecordError #:nodoc:
+ def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil)
+ if owner_class_name && reflection && through_reflection
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through '#{owner_class_name}##{through_reflection.name}' before the through association is defined.")
+ else
+ super("Cannot have a has_many :through association before the through association is defined.")
+ end
+ end
+ end
+
class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
def initialize(owner = nil, reflection = nil)
if owner && reflection
@@ -1817,7 +1827,7 @@ module ActiveRecord
builder = Builder::HasAndBelongsToMany.new name, self, options
- join_model = builder.through_model
+ join_model = ActiveSupport::Deprecation.silence { builder.through_model }
const_set join_model.name, join_model
private_constant join_model.name
@@ -1846,8 +1856,8 @@ module ActiveRecord
hm_options[k] = options[k] if options.key? k
end
- has_many name, scope, hm_options, &extension
- self._reflections[name.to_s].parent_reflection = habtm_reflection
+ ActiveSupport::Deprecation.silence { has_many name, scope, hm_options, &extension }
+ _reflections[name.to_s].parent_reflection = habtm_reflection
end
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 84d0493a60..1cb2b2d7c6 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -83,7 +83,7 @@ module ActiveRecord
end
def scope
- target_scope.merge(association_scope)
+ target_scope.merge!(association_scope)
end
# The scope for this association.
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index c6d204d3c2..badde9973f 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -128,9 +128,9 @@ module ActiveRecord
reflection = chain_head
while reflection
table = reflection.alias_name
+ next_reflection = reflection.next
unless reflection == chain_tail
- next_reflection = reflection.next
foreign_table = next_reflection.alias_name
scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
end
@@ -138,7 +138,7 @@ module ActiveRecord
# Exclude the scope of the association itself, because that
# was already merged in the #scope method.
reflection.constraints.each do |scope_chain_item|
- item = eval_scope(reflection.klass, scope_chain_item, owner)
+ item = eval_scope(reflection.klass, table, scope_chain_item, owner)
if scope_chain_item == refl.scope
scope.merge! item.except(:where, :includes)
@@ -153,14 +153,15 @@ module ActiveRecord
scope.order_values |= item.order_values
end
- reflection = reflection.next
+ reflection = next_reflection
end
scope
end
- def eval_scope(klass, scope, owner)
- klass.unscoped.instance_exec(owner, &scope)
+ def eval_scope(klass, table, scope, owner)
+ predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table))
+ ActiveRecord::Relation.create(klass, table, predicate_builder).instance_exec(owner, &scope)
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 0437a79b84..77282e6463 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -30,13 +30,7 @@ module ActiveRecord
reload
end
- if null_scope?
- # Cache the proxy separately before the owner has an id
- # or else a post-save proxy will still lack the id
- @null_proxy ||= CollectionProxy.create(klass, self)
- else
- @proxy ||= CollectionProxy.create(klass, self)
- end
+ CollectionProxy.create(klass, self)
end
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -315,9 +309,9 @@ module ActiveRecord
record
end
- def scope(opts = {})
- scope = super()
- scope.none! if opts.fetch(:nullify, true) && null_scope?
+ def scope
+ scope = super
+ scope.none! if null_scope?
scope
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 0d84805b4d..55bf2e0ff0 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -28,12 +28,9 @@ module ActiveRecord
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
class CollectionProxy < Relation
- delegate :exists?, :update_all, :arel, to: :scope
-
def initialize(klass, association) #:nodoc:
@association = association
super klass, klass.arel_table, klass.predicate_builder
- merge! association.scope(nullify: false)
end
def target
@@ -956,19 +953,10 @@ module ActiveRecord
@association
end
- # We don't want this object to be put on the scoping stack, because
- # that could create an infinite loop where we call an @association
- # method, which gets the current scope, which is this object, which
- # delegates to @association, and so on.
- def scoping
- @association.scope.scoping { yield }
- end
-
# Returns a <tt>Relation</tt> object for the records in this association
def scope
- @association.scope
+ @scope ||= @association.scope
end
- alias spawn scope
# Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
# contain the same number of elements and if each element is equal
@@ -1100,6 +1088,7 @@ module ActiveRecord
# person.pets(true) # fetches pets from the database
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
def reload
+ @scope = nil
proxy_association.reload
self
end
@@ -1121,11 +1110,21 @@ module ActiveRecord
# person.pets # fetches pets from the database
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
def reset
+ @scope = nil
proxy_association.reset
proxy_association.reset_scope
self
end
+ delegate_methods = [
+ QueryMethods,
+ SpawnMethods,
+ ].flat_map { |klass|
+ klass.public_instance_methods(false)
+ } - self.public_instance_methods(false) + [:scoping]
+
+ delegate(*delegate_methods, to: :scope)
+
private
def find_nth_with_limit(index, limit)
@@ -1149,6 +1148,18 @@ module ActiveRecord
def exec_queries
load_target
end
+
+ def respond_to_missing?(method, _)
+ scope.respond_to?(method) || super
+ end
+
+ def method_missing(method, *args, &block)
+ if scope.respond_to?(method)
+ scope.public_send(method, *args, &block)
+ else
+ super
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 25613d2fa7..b413eb3f9c 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -100,7 +100,7 @@ module ActiveRecord
end
def delete_or_nullify_all_records(method)
- count = delete_count(method, self.scope)
+ count = delete_count(method, scope)
update_counter(-count)
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index b624154def..8458253ff8 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -85,7 +85,7 @@ module ActiveRecord
if target.persisted? && owner.persisted? && !target.save
set_owner_attributes(target)
- raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
+ raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " \
"The record failed to save after its foreign key was set to nil."
end
end
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index 604904abcc..1183bdf6f4 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -22,6 +22,10 @@ module ActiveRecord
elsif record
attributes = construct_join_attributes(record)
+ if through_record && through_record.destroyed?
+ through_record = through_proxy.tap(&:reload).target
+ end
+
if through_record
through_record.update(attributes)
elsif owner.new_record?
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 4cd1e64c3d..87e0847ec1 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -32,7 +32,7 @@ module ActiveRecord
@alias_cache[node][column]
end
- class Table < Struct.new(:node, :columns) # :nodoc:
+ Table = Struct.new(:node, :columns) do # :nodoc:
def table
Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
end
@@ -171,7 +171,7 @@ module ActiveRecord
chain = child.reflection.chain
foreign_table = parent.table
foreign_klass = parent.base_klass
- child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain)
+ child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, chain)
end
def make_outer_joins(parent, child)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index a5705951f3..f5fcba1236 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -23,14 +23,11 @@ module ActiveRecord
JoinInformation = Struct.new :joins, :binds
- def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
+ def join_constraints(foreign_table, foreign_klass, node, join_type, tables, chain)
joins = []
binds = []
tables = tables.reverse
- scope_chain_index = 0
- scope_chain = scope_chain.reverse
-
# The chain starts with the target table, but we want to end with it here (makes
# more sense in this context), so we reverse
chain.reverse_each do |reflection|
@@ -44,7 +41,7 @@ module ActiveRecord
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table))
- scope_chain_items = scope_chain[scope_chain_index].map do |item|
+ scope_chain_items = reflection.scopes.map do |item|
if item.is_a?(Relation)
item
else
@@ -52,7 +49,6 @@ module ActiveRecord
.instance_exec(node, &item)
end
end
- scope_chain_index += 1
klass_scope =
if klass.current_scope
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 1ed1deec55..ebe06566cc 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -394,28 +394,21 @@ module ActiveRecord
protected
- def clone_attribute_value(reader_method, attribute_name) # :nodoc:
- value = send(reader_method, attribute_name)
- value.duplicable? ? value.clone : value
- rescue TypeError, NoMethodError
- value
+ def attribute_method?(attr_name) # :nodoc:
+ # We check defined? because Syck calls respond_to? before actually calling initialize.
+ defined?(@attributes) && @attributes.key?(attr_name)
end
- def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
+ private
+
+ def arel_attributes_with_values_for_create(attribute_names)
arel_attributes_with_values(attributes_for_create(attribute_names))
end
- def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
+ def arel_attributes_with_values_for_update(attribute_names)
arel_attributes_with_values(attributes_for_update(attribute_names))
end
- def attribute_method?(attr_name) # :nodoc:
- # We check defined? because Syck calls respond_to? before actually calling initialize.
- defined?(@attributes) && @attributes.key?(attr_name)
- end
-
- private
-
# Returns a Hash of the Arel::Attributes and attribute values that have been
# typecasted for use in an Arel insert/update method.
def arel_attributes_with_values(attribute_names)
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index b0e1391cb9..31c1e687dc 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -275,7 +275,17 @@ module ActiveRecord
def attribute_will_change!(attr_name)
super
- mutations_from_database.force_change(attr_name)
+ if self.class.has_attribute?(attr_name)
+ mutations_from_database.force_change(attr_name)
+ else
+ ActiveSupport::Deprecation.warn(<<-EOW.squish)
+ #{attr_name} is not an attribute known to Active Record.
+ This behavior is deprecated and will be removed in the next
+ version of Rails. If you'd like #{attr_name} to be managed
+ by Active Record, add `attribute :#{attr_name} to your class.
+ EOW
+ mutations_from_database.deprecated_force_change(attr_name)
+ end
end
def _update_record(*)
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 8fcac82a0d..2f32caa257 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -9,7 +9,7 @@ module ActiveRecord
# available.
def to_key
sync_with_transaction_state
- key = self.id
+ key = id
[key] if key
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 369a6e35aa..fdc4bf6621 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -54,7 +54,7 @@ module ActiveRecord
attr_name.to_s
end
- name = self.class.primary_key if name == "id".freeze
+ name = self.class.primary_key if name == "id".freeze && self.class.primary_key
_read_attribute(name, &block)
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 945192fe04..4d9aff76cc 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -54,7 +54,7 @@ module ActiveRecord
elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
class_name_or_coder
else
- Coders::YAMLColumn.new(class_name_or_coder)
+ Coders::YAMLColumn.new(attr_name, class_name_or_coder)
end
decorate_attribute_type(attr_name, :serialize) do |type|
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 df1231ad47..321d039ed4 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -1,5 +1,3 @@
-require "active_support/core_ext/string/strip"
-
module ActiveRecord
module AttributeMethods
module TimeZoneConversion
diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb
index 3417090830..4de993e169 100644
--- a/activerecord/lib/active_record/attribute_mutation_tracker.rb
+++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb
@@ -5,6 +5,7 @@ module ActiveRecord
def initialize(attributes)
@attributes = attributes
@forced_changes = Set.new
+ @deprecated_forced_changes = Set.new
end
def changed_values
@@ -31,7 +32,7 @@ module ActiveRecord
end
def any_changes?
- attr_names.any? { |attr| changed?(attr) }
+ attr_names.any? { |attr| changed?(attr) } || deprecated_forced_changes.any?
end
def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
@@ -60,11 +61,15 @@ module ActiveRecord
forced_changes << attr_name.to_s
end
+ def deprecated_force_change(attr_name)
+ deprecated_forced_changes << attr_name.to_s
+ end
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
# Workaround for Ruby 2.2 "private attribute?" warning.
protected
- attr_reader :attributes, :forced_changes
+ attr_reader :attributes, :forced_changes, :deprecated_forced_changes
private
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 9d0b501862..6bccbc06cd 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -328,9 +328,9 @@ module ActiveRecord
def association_valid?(reflection, record, index = nil)
return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
- validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
+ context = validation_context unless [:create, :update].include?(validation_context)
- unless valid = record.valid?(validation_context)
+ unless valid = record.valid?(context)
if reflection.options[:autosave]
indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index be6720ddf3..eb44887e18 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -225,6 +225,55 @@ module ActiveRecord
#
# This way, the +before_destroy+ gets executed before the <tt>dependent: :destroy</tt> is called, and the data is still available.
#
+ # Also, there are cases when you want several callbacks of the same type to
+ # be executed in order.
+ #
+ # For example:
+ #
+ # class Topic
+ # has_many :children
+ #
+ # after_save :log_children
+ # after_save :do_something_else
+ #
+ # private
+ #
+ # def log_chidren
+ # # Child processing
+ # end
+ #
+ # def do_something_else
+ # # Something else
+ # end
+ # end
+ #
+ # In this case the +log_children+ gets executed before +do_something_else+.
+ # The same applies to all non-transactional callbacks.
+ #
+ # In case there are multiple transactional callbacks as seen below, the order
+ # is reversed.
+ #
+ # For example:
+ #
+ # class Topic
+ # has_many :children
+ #
+ # after_commit :log_children
+ # after_commit :do_something_else
+ #
+ # private
+ #
+ # def log_chidren
+ # # Child processing
+ # end
+ #
+ # def do_something_else
+ # # Something else
+ # end
+ # end
+ #
+ # In this case the +do_something_else+ gets executed before +log_children+.
+ #
# == \Transactions
#
# The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!],
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index 3a04a10fc9..9c52a31b95 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -5,7 +5,8 @@ module ActiveRecord
class YAMLColumn # :nodoc:
attr_accessor :object_class
- def initialize(object_class = Object)
+ def initialize(attr_name, object_class = Object)
+ @attr_name = attr_name
@object_class = object_class
check_arity_of_constructor
end
@@ -13,7 +14,7 @@ module ActiveRecord
def dump(obj)
return if obj.nil?
- assert_valid_value(obj)
+ assert_valid_value(obj, action: "dump")
YAML.dump obj
end
@@ -22,27 +23,25 @@ module ActiveRecord
return yaml unless yaml.is_a?(String) && /^---/.match?(yaml)
obj = YAML.load(yaml)
- assert_valid_value(obj)
+ assert_valid_value(obj, action: "load")
obj ||= object_class.new if object_class != Object
obj
end
- def assert_valid_value(obj)
+ def assert_valid_value(obj, action:)
unless obj.nil? || obj.is_a?(object_class)
raise SerializationTypeMismatch,
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
+ "can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
end
end
private
def check_arity_of_constructor
- begin
- load(nil)
- rescue ArgumentError
- raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
- end
+ load(nil)
+ rescue ArgumentError
+ raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
end
end
end
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 5ec2fc073e..3f2e86a98d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -353,6 +353,16 @@ module ActiveRecord
@threads_blocking_new_connections = 0
@available = ConnectionLeasingQueue.new self
+
+ @lock_thread = false
+ end
+
+ def lock_thread=(lock_thread)
+ if lock_thread
+ @lock_thread = Thread.current
+ else
+ @lock_thread = nil
+ end
end
# Retrieve the connection associated with the current thread, or call
@@ -361,7 +371,7 @@ module ActiveRecord
# #connection can be called any number of times; the connection is
# held in a cache keyed by a thread.
def connection
- @thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
+ @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
end
# Returns true if there is an open connection being used for the current thread.
@@ -967,7 +977,7 @@ module ActiveRecord
end
def pool_from_any_process_for(spec_name)
- owner_to_pool = @owner_to_pool.values.find { |v| v[spec_name] }
+ owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[spec_name] }
owner_to_pool && owner_to_pool[spec_name]
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 2c352819fb..769f488469 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -51,8 +51,7 @@ module ActiveRecord
# Returns a single value from a record
def select_value(arel, name = nil, binds = [])
- arel, binds = binds_from_relation arel, binds
- if result = select_rows(to_sql(arel, binds), name, binds).first
+ if result = select_rows(arel, name, binds).first
result.first
end
end
@@ -60,14 +59,13 @@ module ActiveRecord
# Returns an array of the values of the first column in a select:
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
def select_values(arel, name = nil, binds = [])
- arel, binds = binds_from_relation arel, binds
- select_rows(to_sql(arel, binds), name, binds).map(&:first)
+ select_rows(arel, name, binds).map(&:first)
end
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
- def select_rows(sql, name = nil, binds = [])
- exec_query(sql, name, binds).rows
+ def select_rows(arel, name = nil, binds = [])
+ select_all(arel, name, binds).rows
end
# Executes the SQL statement in the context of this connection and returns
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 7eab7de5d3..e53ba4e666 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -83,7 +83,9 @@ module ActiveRecord
# the same SQL query and repeatedly return the same result each time, silently
# undermining the randomness you were expecting.
def clear_query_cache
- @query_cache.clear
+ @lock.synchronize do
+ @query_cache.clear
+ end
end
def select_all(arel, name = nil, binds = [], preparable: nil)
@@ -99,21 +101,23 @@ module ActiveRecord
private
def cache_sql(sql, name, binds)
- result =
- if @query_cache[sql].key?(binds)
- ActiveSupport::Notifications.instrument(
- "sql.active_record",
- sql: sql,
- binds: binds,
- name: name,
- connection_id: object_id,
- cached: true,
- )
- @query_cache[sql][binds]
- else
- @query_cache[sql][binds] = yield
- end
- result.dup
+ @lock.synchronize do
+ result =
+ if @query_cache[sql].key?(binds)
+ ActiveSupport::Notifications.instrument(
+ "sql.active_record",
+ sql: sql,
+ binds: binds,
+ name: name,
+ connection_id: object_id,
+ cached: true,
+ )
+ @query_cache[sql][binds]
+ else
+ @query_cache[sql][binds] = yield
+ end
+ result.dup
+ end
end
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 0c6bc16e6f..7f4132accf 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -1,4 +1,5 @@
require "active_support/core_ext/big_decimal/conversions"
+require "active_support/multibyte/chars"
module ActiveRecord
module ConnectionAdapters # :nodoc:
@@ -140,6 +141,10 @@ module ActiveRecord
quoted_date(value).sub(/\A2000-01-01 /, "")
end
+ def quoted_binary(value) # :nodoc:
+ "'#{quote_string(value.to_s)}'"
+ end
+
private
def type_casted_binds(binds)
@@ -152,7 +157,7 @@ module ActiveRecord
def _quote(value)
case value
- when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data
+ when String, ActiveSupport::Multibyte::Chars
"'#{quote_string(value.to_s)}'"
when true then quoted_true
when false then quoted_false
@@ -160,6 +165,7 @@ module ActiveRecord
# BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s("F")
when Numeric, ActiveSupport::Duration then value.to_s
+ when Type::Binary::Data then quoted_binary(value)
when Type::Time::Value then "'#{quoted_time(value)}'"
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
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 322684672f..c48a4acff8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -15,9 +15,9 @@ module ActiveRecord
end
delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
- :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?, :foreign_key_options, to: :@conn
+ :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?, :foreign_key_options, to: :@conn
private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
- :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?, :foreign_key_options
+ :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?, :foreign_key_options
private
@@ -29,7 +29,7 @@ module ActiveRecord
end
def visit_ColumnDefinition(o)
- o.sql_type ||= type_to_sql(o.type, o.limit, o.precision, o.scale)
+ o.sql_type = type_to_sql(o.type, o.options)
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
@@ -49,7 +49,7 @@ module ActiveRecord
statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
end
- if supports_foreign_keys?
+ if supports_foreign_keys_in_create?
statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
end
@@ -96,17 +96,7 @@ module ActiveRecord
end
def column_options(o)
- column_options = {}
- column_options[:null] = o.null unless o.null.nil?
- column_options[:default] = o.default unless o.default.nil?
- column_options[:column] = o
- column_options[:first] = o.first
- column_options[:after] = o.after
- column_options[:auto_increment] = o.auto_increment
- column_options[:primary_key] = o.primary_key
- column_options[:collation] = o.collation
- column_options[:comment] = o.comment
- column_options
+ o.options.merge(column: o)
end
def add_column_options!(sql, options)
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 9b324c090b..5eb7787226 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -3,29 +3,37 @@ module ActiveRecord
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
- end
+ IndexDefinition = Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment) #:nodoc:
+ ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc:
def primary_key?
- primary_key || type.to_sym == :primary_key
+ options[:primary_key]
end
- end
- class AddColumnDefinition < Struct.new(:column) # :nodoc:
- end
+ [:limit, :precision, :scale, :default, :null, :collation, :comment].each do |option_name|
+ module_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{option_name}
+ options[:#{option_name}]
+ end
- class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc:
+ def #{option_name}=(value)
+ options[:#{option_name}] = value
+ end
+ CODE
+ end
end
- class PrimaryKeyDefinition < Struct.new(:name) # :nodoc:
- end
+ AddColumnDefinition = Struct.new(:column) # :nodoc:
+
+ ChangeColumnDefinition = Struct.new(:column, :name) #:nodoc:
- class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc:
+ PrimaryKeyDefinition = Struct.new(:name) # :nodoc:
+
+ ForeignKeyDefinition = Struct.new(:from_table, :to_table, :options) do #:nodoc:
def name
options[:name]
end
@@ -177,6 +185,7 @@ module ActiveRecord
:text,
:time,
:timestamp,
+ :virtual,
].each do |column_type|
module_eval <<-CODE, __FILE__, __LINE__ + 1
def #{column_type}(*args, **options)
@@ -357,33 +366,22 @@ module ActiveRecord
#
# See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
def references(*args, **options)
- args.each do |col|
- ReferenceDefinition.new(col, **options).add_to(self)
+ args.each do |ref_name|
+ ReferenceDefinition.new(ref_name, options).add_to(self)
end
end
alias :belongs_to :references
- def new_column_definition(name, type, options) # :nodoc:
+ def new_column_definition(name, type, **options) # :nodoc:
type = aliased_types(type.to_s, type)
- column = create_column_definition name, type
-
- column.limit = options[:limit]
- column.precision = options[:precision]
- column.scale = options[:scale]
- column.default = options[:default]
- column.null = options[:null]
- column.first = options[:first]
- column.after = options[:after]
- column.auto_increment = options[:auto_increment]
- column.primary_key = type == :primary_key || options[:primary_key]
- column.collation = options[:collation]
- column.comment = options[:comment]
- column
+ options[:primary_key] ||= type == :primary_key
+ options[:null] = false if options[:primary_key]
+ create_column_definition(name, type, options)
end
private
- def create_column_definition(name, type)
- ColumnDefinition.new name, type
+ def create_column_definition(name, type, options)
+ ColumnDefinition.new(name, type, options)
end
def aliased_types(name, fallback)
@@ -591,8 +589,7 @@ module ActiveRecord
# t.belongs_to(:supplier, foreign_key: true)
#
# See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
- def references(*args)
- options = args.extract_options!
+ def references(*args, **options)
args.each do |ref_name|
@base.add_reference(name, ref_name, options)
end
@@ -605,8 +602,7 @@ module ActiveRecord
# t.remove_belongs_to(:supplier, polymorphic: true)
#
# See {connection.remove_reference}[rdoc-ref:SchemaStatements#remove_reference]
- def remove_references(*args)
- options = args.extract_options!
+ def remove_references(*args, **options)
args.each do |ref_name|
@base.remove_reference(name, ref_name, options)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index b912d24626..34036d8a1d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -7,13 +7,15 @@ module ActiveRecord
# Adapter level by over-writing this code inside the database specific adapters
module ColumnDumper
def column_spec(column)
- [schema_type(column), prepare_column_options(column)]
+ [schema_type_with_virtual(column), prepare_column_options(column)]
end
def column_spec_for_primary_key(column)
return {} if default_primary_key?(column)
spec = { id: schema_type(column).inspect }
spec.merge!(prepare_column_options(column).except!(:null))
+ spec[:default] ||= "nil" if explicit_primary_key_default?(column)
+ spec
end
# This can be overridden on an Adapter level basis to support other
@@ -49,9 +51,10 @@ module ActiveRecord
end
# Lists the valid migration options
- def migration_keys
- [:limit, :precision, :scale, :default, :null, :collation, :comment]
+ def migration_keys # :nodoc:
+ column_options_keys
end
+ deprecate :migration_keys
private
@@ -59,6 +62,18 @@ module ActiveRecord
schema_type(column) == :bigint
end
+ def explicit_primary_key_default?(column)
+ false
+ end
+
+ def schema_type_with_virtual(column)
+ if supports_virtual_columns? && column.virtual?
+ :virtual
+ else
+ schema_type(column)
+ end
+ end
+
def schema_type(column)
if column.bigint?
:bigint
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 e5c0e1690c..3686ad8b54 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -69,7 +69,9 @@ module ActiveRecord
end
# Returns an array of indexes for the given table.
- # def indexes(table_name, name = nil) end
+ def indexes(table_name, name = nil)
+ raise NotImplementedError, "#indexes is not implemented"
+ end
# Checks to see if an index exists on a table for a given index definition.
#
@@ -120,7 +122,7 @@ module ActiveRecord
checks = []
checks << lambda { |c| c.name == column_name }
checks << lambda { |c| c.type == type } if type
- migration_keys.each do |attr|
+ column_options_keys.each do |attr|
checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
end
@@ -271,8 +273,8 @@ module ActiveRecord
yield td if block_given?
- if options[:force] && data_source_exists?(table_name)
- drop_table(table_name, options)
+ if options[:force]
+ drop_table(table_name, **options, if_exists: true)
end
result = execute schema_creation.accept td
@@ -766,16 +768,17 @@ module ActiveRecord
raise ArgumentError, "You must specify the index name"
end
else
- index_name(table_name, column: options)
+ index_name(table_name, index_name_options(options))
end
end
# Verifies the existence of an index with a given name.
- #
- # The default argument is returned if the underlying implementation does not define the indexes method,
- # as there's no way to determine the correct answer in that case.
- def index_name_exists?(table_name, index_name, default)
- return default unless respond_to?(:indexes)
+ def index_name_exists?(table_name, index_name, default = nil)
+ unless default.nil?
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing default to #index_name_exists? is deprecated without replacement.
+ MSG
+ end
index_name = index_name.to_s
indexes(table_name).detect { |i| i.name == index_name }
end
@@ -826,8 +829,8 @@ module ActiveRecord
#
# add_reference(:products, :supplier, foreign_key: {to_table: :firms})
#
- def add_reference(table_name, *args)
- ReferenceDefinition.new(*args).add_to(update_table_definition(table_name, self))
+ def add_reference(table_name, ref_name, **options)
+ ReferenceDefinition.new(ref_name, options).add_to(update_table_definition(table_name, self))
end
alias :add_belongs_to :add_reference
@@ -1006,15 +1009,15 @@ module ActiveRecord
end
end
- # Should not be called normally, but this operation is non-destructive.
- # The migrations module handles this automatically.
- def initialize_schema_migrations_table
+ def initialize_schema_migrations_table # :nodoc:
ActiveRecord::SchemaMigration.create_table
end
+ deprecate :initialize_schema_migrations_table
- def initialize_internal_metadata_table
+ def initialize_internal_metadata_table # :nodoc:
ActiveRecord::InternalMetadata.create_table
end
+ deprecate :initialize_internal_metadata_table
def internal_string_options_for_primary_key # :nodoc:
{ primary_key: true }
@@ -1050,7 +1053,7 @@ module ActiveRecord
end
end
- def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc:
type = type.to_sym if type
if native = native_database_types[type]
column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
@@ -1068,7 +1071,7 @@ module ActiveRecord
raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
end
- elsif [:datetime, :time].include?(type) && precision ||= native[:precision]
+ elsif [:datetime, :time, :interval].include?(type) && precision ||= native[:precision]
if (0..6) === precision
column_type_sql << "(#{precision})"
else
@@ -1120,18 +1123,14 @@ module ActiveRecord
end
def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
- if column_name.is_a?(String) && /\W/.match?(column_name)
- column_names = column_name
- else
- column_names = Array(column_name)
- end
+ column_names = index_column_names(column_name)
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
index_type = options[:type].to_s if options.key?(:type)
index_type ||= options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
- index_name ||= index_name(table_name, index_name_options(column_names))
+ index_name ||= index_name(table_name, column_names)
if options.key?(:algorithm)
algorithm = index_algorithms.fetch(options[:algorithm]) {
@@ -1147,7 +1146,7 @@ module ActiveRecord
validate_index_length!(table_name, index_name, options.fetch(:internal, false))
- if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false)
+ if data_source_exists?(table_name) && index_name_exists?(table_name, index_name)
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
@@ -1170,6 +1169,9 @@ module ActiveRecord
end
private
+ def column_options_keys
+ [:limit, :precision, :scale, :default, :null, :collation, :comment]
+ end
def add_index_sort_order(quoted_columns, **options)
if order = options[:order]
@@ -1208,13 +1210,13 @@ module ActiveRecord
if options.is_a?(Hash)
checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
- column_names = Array(options[:column]).map(&:to_s)
+ column_names = index_column_names(options[:column])
else
- column_names = Array(options).map(&:to_s)
+ column_names = index_column_names(options)
end
- if column_names.any?
- checks << lambda { |i| i.columns.join("_and_") == column_names.join("_and_") }
+ if column_names.present?
+ checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
end
raise ArgumentError, "No name or columns specified" if checks.none?
@@ -1261,8 +1263,16 @@ module ActiveRecord
AlterTable.new create_table_definition(name)
end
+ def index_column_names(column_names)
+ if column_names.is_a?(String) && /\W/.match?(column_names)
+ column_names
+ else
+ Array(column_names)
+ end
+ end
+
def index_name_options(column_names)
- if column_names.is_a?(String)
+ if column_names.is_a?(String) && /\W/.match?(column_names)
column_names = column_names.scan(/\w+/).join("_")
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 4046b3829d..b31ce0a181 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -107,6 +107,7 @@ module ActiveRecord
@schema_cache = SchemaCache.new self
@quoted_column_names, @quoted_table_names = {}, {}
@visitor = arel_visitor
+ @lock = Monitor.new
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
@@ -176,7 +177,7 @@ module ActiveRecord
if @owner == Thread.current
msg << "it is already leased by the current thread."
else
- msg << "it is already in use by a different thread: #{@owner}. " <<
+ msg << "it is already in use by a different thread: #{@owner}. " \
"Current thread: #{Thread.current}."
end
raise ActiveRecordError, msg
@@ -194,8 +195,8 @@ module ActiveRecord
def expire
if in_use?
if @owner != Thread.current
- raise ActiveRecordError, "Cannot expire connection, " <<
- "it is owned by a different thread: #{@owner}. " <<
+ raise ActiveRecordError, "Cannot expire connection, " \
+ "it is owned by a different thread: #{@owner}. " \
"Current thread: #{Thread.current}."
end
@@ -236,11 +237,10 @@ module ActiveRecord
false
end
- # Can this adapter determine the primary key for tables not attached
- # to an Active Record class, such as join tables?
- def supports_primary_key?
- false
+ def supports_primary_key? # :nodoc:
+ true
end
+ deprecate :supports_primary_key?
# Does this adapter support DDL rollbacks in transactions? That is, would
# CREATE TABLE or ALTER TABLE get rolled back by a transaction?
@@ -310,6 +310,12 @@ module ActiveRecord
false
end
+ # Does this adapter support creating foreign key constraints
+ # in the same statement as creating the table?
+ def supports_foreign_keys_in_create?
+ supports_foreign_keys?
+ end
+
# Does this adapter support views?
def supports_views?
false
@@ -340,6 +346,11 @@ module ActiveRecord
true
end
+ # Does this adapter support virtual columns?
+ def supports_virtual_columns?
+ false
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
@@ -441,15 +452,15 @@ module ActiveRecord
@connection
end
- def case_sensitive_comparison(table, attribute, column, value)
- table[attribute].eq(Arel::Nodes::BindParam.new)
+ def case_sensitive_comparison(table, attribute, column, value) # :nodoc:
+ table[attribute].eq(value)
end
- def case_insensitive_comparison(table, attribute, column, value)
+ def case_insensitive_comparison(table, attribute, column, value) # :nodoc:
if can_perform_case_insensitive_comparison_for?(column)
- table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new))
+ table[attribute].lower.eq(table.lower(value))
else
- table[attribute].eq(Arel::Nodes::BindParam.new)
+ table[attribute].eq(value)
end
end
@@ -499,6 +510,10 @@ module ActiveRecord
result
end
+ def default_index_type?(index) # :nodoc:
+ index.using.nil?
+ end
+
private
def initialize_type_map(m)
@@ -591,7 +606,11 @@ module ActiveRecord
binds: binds,
type_casted_binds: type_casted_binds,
statement_name: statement_name,
- connection_id: object_id) { yield }
+ connection_id: object_id) do
+ @lock.synchronize do
+ yield
+ end
+ end
rescue => e
raise translate_exception_class(e, sql)
end
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 01cd1e5446..14269b4570 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -93,10 +93,6 @@ module ActiveRecord
true
end
- def supports_primary_key?
- true
- end
-
def supports_bulk_alter? #:nodoc:
true
end
@@ -141,6 +137,14 @@ module ActiveRecord
end
end
+ def supports_virtual_columns?
+ if mariadb?
+ version >= "5.2.0"
+ else
+ version >= "5.7.5"
+ end
+ end
+
def supports_advisory_locks?
true
end
@@ -367,6 +371,12 @@ module ActiveRecord
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil) #:nodoc:
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing name to #indexes is deprecated without replacement.
+ MSG
+ end
+
indexes = []
current_index = nil
execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
@@ -521,6 +531,7 @@ module ActiveRecord
WHERE fk.referenced_column_name IS NOT NULL
AND fk.table_schema = #{quote(schema)}
AND fk.table_name = #{quote(name)}
+ AND rc.table_name = #{quote(name)}
SQL
fk_info.map do |row|
@@ -559,7 +570,7 @@ module ActiveRecord
end
# Maps logical Rails types to MySQL-specific data types.
- def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, unsigned: nil, **) # :nodoc:
sql = \
case type.to_s
when "integer"
@@ -575,7 +586,7 @@ module ActiveRecord
binary_to_sql(limit)
end
else
- super(type, limit, precision, scale)
+ super
end
sql << " unsigned" if unsigned && type != :primary_key
@@ -604,9 +615,9 @@ module ActiveRecord
SQL
end
- def case_sensitive_comparison(table, attribute, column, value)
+ def case_sensitive_comparison(table, attribute, column, value) # :nodoc:
if column.collation && !column.case_sensitive?
- table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
+ table[attribute].eq(Arel::Nodes::Bin.new(value))
else
super
end
@@ -640,6 +651,10 @@ module ActiveRecord
!native_database_types[type].nil?
end
+ def default_index_type?(index) # :nodoc:
+ index.using == :btree || super
+ end
+
private
def initialize_type_map(m)
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index dcf56997db..3e4ea28f63 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -149,9 +149,18 @@ module ActiveRecord
# 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.reject! { |k, v| v.is_a?(Hash) && !(v.key?("adapter") || v.key?("url")) }
+ config.merge! env_config if env_config
+
config.each do |key, value|
config[key] = resolve(value) if value
end
+
config
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/column.rb b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
index 1499c1681f..c9ad47c035 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
@@ -15,6 +15,10 @@ module ActiveRecord
def auto_increment?
extra == "auto_increment"
end
+
+ def virtual?
+ /\b(?:VIRTUAL|STORED|PERSISTENT)\b/.match?(extra)
+ end
end
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 78e7181266..8c67a7a80b 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module MySQL
module DatabaseStatements
# Returns an ActiveRecord::Result instance.
- def select_all(arel, name = nil, binds = [], preparable: nil)
+ def select_all(arel, name = nil, binds = [], preparable: nil) # :nodoc:
result = if ExplainRegistry.collect? && prepared_statements
unprepared_statement { super }
else
@@ -15,8 +15,8 @@ module ActiveRecord
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
- def select_rows(sql, name = nil, binds = [])
- select_result(sql, name, binds) do |result|
+ def select_rows(arel, name = nil, binds = []) # :nodoc:
+ select_result(arel, name, binds) do |result|
@connection.next_result while @connection.more_results?
result.to_a
end
@@ -58,7 +58,9 @@ module ActiveRecord
@connection.last_id
end
- def select_result(sql, name = nil, binds = [])
+ def select_result(arel, name, binds)
+ arel, binds = binds_from_relation(arel, binds)
+ sql = to_sql(arel, binds)
if without_prepared_statement?(binds)
execute_and_free(sql, name) { |result| yield result }
else
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
index 9d11ad28d4..d4f5906b33 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
@@ -36,15 +36,9 @@ module ActiveRecord
end
end
- private
-
- def _quote(value)
- if value.is_a?(Type::Binary::Data)
- "x'#{value.hex}'"
- else
- super
- end
- end
+ def quoted_binary(value)
+ "x'#{value.hex}'"
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
index d808b50332..e8358271ab 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
@@ -1,9 +1,9 @@
module ActiveRecord
module ConnectionAdapters
module MySQL
- class SchemaCreation < AbstractAdapter::SchemaCreation
- delegate :add_sql_comment!, to: :@conn
- private :add_sql_comment!
+ class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
+ delegate :add_sql_comment!, :mariadb?, to: :@conn
+ private :add_sql_comment!, :mariadb?
private
@@ -11,11 +11,6 @@ module ActiveRecord
"DROP FOREIGN KEY #{name}"
end
- def visit_ColumnDefinition(o)
- o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.unsigned)
- super
- end
-
def visit_AddColumnDefinition(o)
add_column_position!(super, column_options(o.column))
end
@@ -29,12 +24,6 @@ module ActiveRecord
add_sql_comment!(super, options[:comment])
end
- def column_options(o)
- column_options = super
- column_options[:charset] = o.charset
- column_options
- end
-
def add_column_options!(sql, options)
if charset = options[:charset]
sql << " CHARACTER SET #{charset}"
@@ -44,6 +33,13 @@ module ActiveRecord
sql << " COLLATE #{collation}"
end
+ if as = options[:as]
+ sql << " AS (#{as})"
+ if options[:stored]
+ sql << (mariadb? ? " PERSISTENT" : " STORED")
+ end
+ end
+
add_sql_comment!(super, options[:comment])
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
index 0cf40de70f..773bbcef4e 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
@@ -3,10 +3,7 @@ module ActiveRecord
module MySQL
module ColumnMethods
def primary_key(name, type = :primary_key, **options)
- if type == :primary_key && !options.key?(:default)
- options[:auto_increment] = true
- options[:limit] = 8
- end
+ options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default)
super
end
@@ -59,33 +56,25 @@ module ActiveRecord
end
end
- class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
- attr_accessor :charset, :unsigned
- end
-
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
- def new_column_definition(name, type, options) # :nodoc:
- column = super
- case column.type
+ def new_column_definition(name, type, **options) # :nodoc:
+ case type
+ when :virtual
+ type = options[:type]
when :primary_key
- column.type = :integer
- column.auto_increment = true
+ type = :integer
+ options[:limit] ||= 8
+ options[:auto_increment] = true
+ options[:primary_key] = true
when /\Aunsigned_(?<type>.+)\z/
- column.type = $~[:type].to_sym
- column.unsigned = true
+ type = $~[:type].to_sym
+ options[:unsigned] = true
end
- column.unsigned ||= options[:unsigned]
- column.charset = options[:charset]
- column
- end
-
- private
- def create_column_definition(name, type)
- MySQL::ColumnDefinition.new(name, type)
- end
+ super
+ end
end
class Table < ActiveRecord::ConnectionAdapters::Table
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
index 2065816501..ad4a069d73 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -1,19 +1,17 @@
module ActiveRecord
module ConnectionAdapters
module MySQL
- module ColumnDumper
- def column_spec_for_primary_key(column)
- spec = super
- if column.type == :integer && !column.auto_increment?
- spec[:default] = schema_default(column) || "nil"
- end
- spec[:unsigned] = "true" if column.unsigned?
- spec
- end
-
+ module ColumnDumper # :nodoc:
def prepare_column_options(column)
spec = super
spec[:unsigned] = "true" if column.unsigned?
+
+ if supports_virtual_columns? && column.virtual?
+ spec[:as] = extract_expression_for_virtual_column(column)
+ spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra)
+ spec = { type: schema_type(column).inspect }.merge!(spec)
+ end
+
spec
end
@@ -24,7 +22,11 @@ module ActiveRecord
private
def default_primary_key?(column)
- super && column.auto_increment?
+ super && column.auto_increment? && !column.unsigned?
+ end
+
+ def explicit_primary_key_default?(column)
+ column.type == :integer && !column.auto_increment?
end
def schema_type(column)
@@ -46,6 +48,21 @@ module ActiveRecord
column.collation.inspect if column.collation != @table_collation_cache[table_name]
end
end
+
+ def extract_expression_for_virtual_column(column)
+ if mariadb?
+ create_table_info = create_table_info(column.table_name)
+ if %r/#{quote_column_name(column.name)} #{Regexp.quote(column.sql_type)} AS \((?<expression>.+?)\) #{column.extra}/m =~ create_table_info
+ $~[:expression].inspect
+ end
+ else
+ sql = "SELECT generation_expression FROM information_schema.columns" \
+ " WHERE table_schema = #{quote(@config[:database])}" \
+ " AND table_name = #{quote(column.table_name)}" \
+ " AND column_name = #{quote(column.name)}"
+ select_value(sql, "SCHEMA").inspect
+ end
+ end
end
end
end
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 9a2017b443..705e6063dc 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -7,18 +7,14 @@ module ActiveRecord
PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds))
end
- def select_value(arel, name = nil, binds = [])
- arel, binds = binds_from_relation arel, binds
- sql = to_sql(arel, binds)
- execute_and_clear(sql, name, binds) do |result|
+ def select_value(arel, name = nil, binds = []) # :nodoc:
+ select_result(arel, name, binds) do |result|
result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0
end
end
- def select_values(arel, name = nil, binds = [])
- arel, binds = binds_from_relation arel, binds
- sql = to_sql(arel, binds)
- execute_and_clear(sql, name, binds) do |result|
+ def select_values(arel, name = nil, binds = []) # :nodoc:
+ select_result(arel, name, binds) do |result|
if result.nfields > 0
result.column_values(0)
else
@@ -29,8 +25,8 @@ module ActiveRecord
# Executes a SELECT query and returns an array of rows. Each row is an
# array of field values.
- def select_rows(sql, name = nil, binds = [])
- execute_and_clear(sql, name, binds) do |result|
+ def select_rows(arel, name = nil, binds = []) # :nodoc:
+ select_result(arel, name, binds) do |result|
result.values
end
end
@@ -179,6 +175,14 @@ module ActiveRecord
def suppress_composite_primary_key(pk)
pk unless pk.is_a?(Array)
end
+
+ def select_result(arel, name, binds)
+ arel, binds = binds_from_relation(arel, binds)
+ sql = to_sql(arel, binds)
+ execute_and_clear(sql, name, binds) do |result|
+ yield result
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 0e526f6201..4098250f3e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -11,6 +11,7 @@ require "active_record/connection_adapters/postgresql/oid/inet"
require "active_record/connection_adapters/postgresql/oid/json"
require "active_record/connection_adapters/postgresql/oid/jsonb"
require "active_record/connection_adapters/postgresql/oid/money"
+require "active_record/connection_adapters/postgresql/oid/oid"
require "active_record/connection_adapters/postgresql/oid/point"
require "active_record/connection_adapters/postgresql/oid/legacy_point"
require "active_record/connection_adapters/postgresql/oid/range"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb
new file mode 100644
index 0000000000..9c2ac08b30
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb
@@ -0,0 +1,13 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Oid < Type::Integer # :nodoc:
+ def type
+ :oid
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
index 2c714f4018..54d5d0902e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -1,5 +1,3 @@
-require "active_support/core_ext/string/filters"
-
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
index 2d2fede4e8..564e82a4ac 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
@@ -5,8 +5,9 @@ module ActiveRecord
class SpecializedString < Type::String # :nodoc:
attr_reader :type
- def initialize(type)
+ def initialize(type, **options)
@type = type
+ super(options)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 3783925954..6663448a99 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -55,6 +55,10 @@ module ActiveRecord
end
end
+ def quoted_binary(value) # :nodoc:
+ "'#{escape_bytea(value.to_s)}'"
+ end
+
def quote_default_expression(value, column) # :nodoc:
if value.is_a?(Proc)
value.call
@@ -76,8 +80,6 @@ module ActiveRecord
def _quote(value)
case value
- when Type::Binary::Data
- "'#{escape_bytea(value.to_s)}'"
when OID::Xml::Data
"xml '#{quote_string(value.to_s)}'"
when OID::Bit::Data
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
new file mode 100644
index 0000000000..e1d5089115
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
@@ -0,0 +1,15 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
+ private
+ def add_column_options!(sql, options)
+ if options[:collation]
+ sql << " COLLATE \"#{options[:collation]}\""
+ end
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index 4afb4733eb..11ea1e5144 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -42,6 +42,7 @@ module ActiveRecord
# a record (as primary keys cannot be +nil+). This might be done via the
# +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
def primary_key(name, type = :primary_key, **options)
+ options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default)
if type == :uuid
options[:default] = options.fetch(:default, "gen_random_uuid()")
elsif options.delete(:auto_increment) == true && %i(integer bigint).include?(type)
@@ -87,6 +88,10 @@ module ActiveRecord
args.each { |name| column(name, :inet, options) }
end
+ def interval(*args, **options)
+ args.each { |name| column(name, :interval, options) }
+ end
+
def int4range(*args, **options)
args.each { |name| column(name, :int4range, options) }
end
@@ -119,6 +124,10 @@ module ActiveRecord
args.each { |name| column(name, :numrange, options) }
end
+ def oid(*args, **options)
+ args.each { |name| column(name, :oid, options) }
+ end
+
def point(*args, **options)
args.each { |name| column(name, :point, options) }
end
@@ -172,24 +181,8 @@ module ActiveRecord
end
end
- class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
- attr_accessor :array
- end
-
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
-
- def new_column_definition(name, type, options) # :nodoc:
- column = super
- column.array = options[:array]
- column
- end
-
- private
-
- def create_column_definition(name, type)
- PostgreSQL::ColumnDefinition.new name, type
- end
end
class Table < ActiveRecord::ConnectionAdapters::Table
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
index 7808d37deb..5555a46b6b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
@@ -1,15 +1,7 @@
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
- module ColumnDumper
- def column_spec_for_primary_key(column)
- spec = super
- if schema_type(column) == :uuid
- spec[:default] ||= "nil"
- end
- spec
- end
-
+ module ColumnDumper # :nodoc:
# Adds +:array+ option to the default set
def prepare_column_options(column)
spec = super
@@ -28,6 +20,10 @@ module ActiveRecord
schema_type(column) == :bigserial
end
+ def explicit_primary_key_default?(column)
+ column.type == :uuid || (column.type == :integer && !column.serial?)
+ end
+
def schema_type(column)
return super unless column.serial?
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 85836fc575..a61d920a73 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -3,22 +3,6 @@ require "active_support/core_ext/string/strip"
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
- class SchemaCreation < AbstractAdapter::SchemaCreation
- private
-
- def visit_ColumnDefinition(o)
- o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array)
- super
- end
-
- def add_column_options!(sql, options)
- if options[:collation]
- sql << " COLLATE \"#{options[:collation]}\""
- end
- super
- end
- end
-
module SchemaStatements
# Drops the database specified on the +name+ attribute
# and creates it again using the provided +options+.
@@ -148,7 +132,12 @@ module ActiveRecord
end
# Verifies existence of an index with a given name.
- def index_name_exists?(table_name, index_name, default)
+ def index_name_exists?(table_name, index_name, default = nil)
+ unless default.nil?
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing default to #index_name_exists? is deprecated without replacement.
+ MSG
+ end
table = Utils.extract_schema_qualified_name(table_name.to_s)
index = Utils.extract_schema_qualified_name(index_name.to_s)
@@ -166,7 +155,13 @@ module ActiveRecord
end
# Returns an array of indexes for the given table.
- def indexes(table_name, name = nil)
+ def indexes(table_name, name = nil) # :nodoc:
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing name to #indexes is deprecated without replacement.
+ MSG
+ end
+
table = Utils.extract_schema_qualified_name(table_name.to_s)
result = query(<<-SQL, "SCHEMA")
@@ -435,17 +430,18 @@ module ActiveRecord
end
def primary_keys(table_name) # :nodoc:
+ name = Utils.extract_schema_qualified_name(table_name.to_s)
select_values(<<-SQL.strip_heredoc, "SCHEMA")
- WITH pk_constraint AS (
- SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint
- WHERE contype = 'p'
- AND conrelid = #{quote(quote_table_name(table_name))}::regclass
- ), cons AS (
- SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint
- )
- SELECT attr.attname FROM pg_attribute attr
- INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum
- ORDER BY cons.rownum
+ SELECT column_name
+ FROM information_schema.key_column_usage kcu
+ JOIN information_schema.table_constraints tc
+ ON kcu.table_name = tc.table_name
+ AND kcu.table_schema = tc.table_schema
+ AND kcu.constraint_name = tc.constraint_name
+ WHERE constraint_type = 'PRIMARY KEY'
+ AND kcu.table_name = #{quote(name.identifier)}
+ AND kcu.table_schema = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
+ ORDER BY kcu.ordinal_position
SQL
end
@@ -480,7 +476,7 @@ module ActiveRecord
clear_cache!
quoted_table_name = quote_table_name(table_name)
quoted_column_name = quote_column_name(column_name)
- sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale], options[:array])
+ sql_type = type_to_sql(type, options)
sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
@@ -488,7 +484,7 @@ module ActiveRecord
if options[:using]
sql << " USING #{options[:using]}"
elsif options[:cast_as]
- cast_as_type = type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale], options[:array])
+ cast_as_type = type_to_sql(options[:cast_as], options)
sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
end
execute sql
@@ -624,7 +620,7 @@ module ActiveRecord
end
# Maps logical Rails types to PostgreSQL-specific data types.
- def type_to_sql(type, limit = nil, precision = nil, scale = nil, array = nil)
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
sql = \
case type.to_s
when "binary"
@@ -649,7 +645,7 @@ module ActiveRecord
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.")
end
else
- super(type, limit, precision, scale)
+ super
end
sql << "[]" if array && type != :primary_key
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 0ebd907cc0..c89e29ba44 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -9,6 +9,7 @@ require "active_record/connection_adapters/postgresql/explain_pretty_printer"
require "active_record/connection_adapters/postgresql/oid"
require "active_record/connection_adapters/postgresql/quoting"
require "active_record/connection_adapters/postgresql/referential_integrity"
+require "active_record/connection_adapters/postgresql/schema_creation"
require "active_record/connection_adapters/postgresql/schema_definitions"
require "active_record/connection_adapters/postgresql/schema_dumper"
require "active_record/connection_adapters/postgresql/schema_statements"
@@ -108,6 +109,8 @@ module ActiveRecord
bit: { name: "bit" },
bit_varying: { name: "bit varying" },
money: { name: "money" },
+ interval: { name: "interval" },
+ oid: { name: "oid" },
}
OID = PostgreSQL::OID #:nodoc:
@@ -233,7 +236,9 @@ module ActiveRecord
# Clears the prepared statements cache.
def clear_cache!
- @statements.clear
+ @lock.synchronize do
+ @statements.clear
+ end
end
def truncate(table_name, name = nil)
@@ -281,11 +286,6 @@ module ActiveRecord
true
end
- # Does PostgreSQL support finding primary key on non-Active Record tables?
- def supports_primary_key? #:nodoc:
- true
- end
-
def set_standard_conforming_strings
execute("SET standard_conforming_strings = on", "SCHEMA")
end
@@ -404,6 +404,10 @@ module ActiveRecord
@connection.server_version
end
+ def default_index_type?(index) # :nodoc:
+ index.using == :btree || super
+ end
+
private
# See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
@@ -438,7 +442,7 @@ module ActiveRecord
end
end
- def get_oid_type(oid, fmod, column_name, sql_type = "")
+ def get_oid_type(oid, fmod, column_name, sql_type = "".freeze)
if !type_map.key?(oid)
load_additional_types(type_map, [oid])
end
@@ -455,7 +459,7 @@ module ActiveRecord
register_class_with_limit m, "int2", Type::Integer
register_class_with_limit m, "int4", Type::Integer
register_class_with_limit m, "int8", Type::Integer
- m.alias_type "oid", "int2"
+ m.register_type "oid", OID::Oid.new
m.register_type "float4", Type::Float.new
m.alias_type "float8", "float4"
m.register_type "text", Type::Text.new
@@ -490,8 +494,10 @@ module ActiveRecord
m.register_type "polygon", OID::SpecializedString.new(:polygon)
m.register_type "circle", OID::SpecializedString.new(:circle)
- # FIXME: why are we keeping these types as strings?
- m.alias_type "interval", "varchar"
+ m.register_type "interval" do |_, _, sql_type|
+ precision = extract_precision(sql_type)
+ OID::SpecializedString.new(:interval, precision: precision)
+ end
register_class_with_precision m, "time", Type::Time
register_class_with_precision m, "timestamp", OID::DateTime
@@ -633,8 +639,10 @@ module ActiveRecord
if in_transaction?
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
else
- # outside of transactions we can simply flush this query and retry
- @statements.delete sql_key(sql)
+ @lock.synchronize do
+ # outside of transactions we can simply flush this query and retry
+ @statements.delete sql_key(sql)
+ end
retry
end
end
@@ -670,19 +678,21 @@ module ActiveRecord
# Prepare the statement if it hasn't been prepared, return
# the statement key.
def prepare_statement(sql)
- sql_key = sql_key(sql)
- unless @statements.key? sql_key
- nextkey = @statements.next_key
- begin
- @connection.prepare nextkey, sql
- rescue => e
- raise translate_exception_class(e, sql)
+ @lock.synchronize do
+ sql_key = sql_key(sql)
+ unless @statements.key? sql_key
+ nextkey = @statements.next_key
+ begin
+ @connection.prepare nextkey, sql
+ rescue => e
+ raise translate_exception_class(e, sql)
+ end
+ # Clear the queue
+ @connection.get_last_result
+ @statements[sql_key] = nextkey
end
- # Clear the queue
- @connection.get_last_result
- @statements[sql_key] = nextkey
+ @statements[sql_key]
end
- @statements[sql_key]
end
# Connects to a PostgreSQL server and sets up the adapter depending on the
@@ -759,11 +769,11 @@ module ActiveRecord
query(<<-end_sql, "SCHEMA")
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
- (SELECT c.collname FROM pg_collation c, pg_type t
- WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation),
- col_description(a.attrelid, a.attnum) AS comment
- FROM pg_attribute a LEFT JOIN pg_attrdef d
- ON a.attrelid = d.adrelid AND a.attnum = d.adnum
+ c.collname, col_description(a.attrelid, a.attnum) AS comment
+ FROM pg_attribute a
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
index f01ed67b0f..7276a65098 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -18,15 +18,11 @@ module ActiveRecord
quoted_date(value)
end
- private
+ def quoted_binary(value)
+ "x'#{value.hex}'"
+ end
- def _quote(value)
- if value.is_a?(Type::Binary::Data)
- "x'#{value.hex}'"
- else
- super
- end
- end
+ private
def _type_cast(value)
case value
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
index 70c0d28830..bc798d1dbb 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
@@ -1,15 +1,8 @@
module ActiveRecord
module ConnectionAdapters
module SQLite3
- class SchemaCreation < AbstractAdapter::SchemaCreation
+ class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
private
-
- def column_options(o)
- options = super
- options[:null] = false if o.primary_key
- options
- end
-
def add_column_options!(sql, options)
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb
index d0b38dff4c..e157e4b218 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module SQLite3
module ColumnMethods
def primary_key(name, type = :primary_key, **options)
- if options.delete(:auto_increment) == true && %i(integer bigint).include?(type)
+ if %i(integer bigint).include?(type) && (options.delete(:auto_increment) == true || !options.key?(:default))
type = :primary_key
end
@@ -13,6 +13,11 @@ module ActiveRecord
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
+
+ def references(*args, **options)
+ super(*args, type: :integer, **options)
+ end
+ alias :belongs_to :references
end
class Table < ActiveRecord::ConnectionAdapters::Table
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb
index c027fef83c..eec018eda3 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb
@@ -1,12 +1,16 @@
module ActiveRecord
module ConnectionAdapters
module SQLite3
- module ColumnDumper
+ module ColumnDumper # :nodoc:
private
def default_primary_key?(column)
schema_type(column) == :integer
end
+
+ def explicit_primary_key_default?(column)
+ column.bigint?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 297e2997a9..16ef195bfc 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -95,6 +95,8 @@ module ActiveRecord
@active = nil
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
+
+ configure_connection
end
def supports_ddl_transactions?
@@ -120,12 +122,12 @@ module ActiveRecord
true
end
- def supports_primary_key? #:nodoc:
+ def requires_reloading?
true
end
- def requires_reloading?
- true
+ def supports_foreign_keys_in_create?
+ sqlite_version >= "3.6.19"
end
def supports_views?
@@ -185,6 +187,19 @@ module ActiveRecord
true
end
+ # REFERENTIAL INTEGRITY ====================================
+
+ def disable_referential_integrity # :nodoc:
+ old = select_value("PRAGMA foreign_keys")
+
+ begin
+ execute("PRAGMA foreign_keys = OFF")
+ yield
+ ensure
+ execute("PRAGMA foreign_keys = #{old}")
+ end
+ end
+
#--
# DATABASE STATEMENTS ======================================
#++
@@ -316,6 +331,12 @@ module ActiveRecord
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil) #:nodoc:
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing name to #indexes is deprecated without replacement.
+ MSG
+ end
+
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
sql = <<-SQL
SELECT sql
@@ -418,6 +439,24 @@ module ActiveRecord
rename_column_indexes(table_name, column.name, new_column_name)
end
+ def add_reference(table_name, ref_name, **options) # :nodoc:
+ super(table_name, ref_name, type: :integer, **options)
+ end
+ alias :add_belongs_to :add_reference
+
+ def foreign_keys(table_name)
+ fk_info = select_all("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
+ fk_info.map do |row|
+ options = {
+ column: row["from"],
+ primary_key: row["to"],
+ on_delete: extract_foreign_key_action(row["on_delete"]),
+ on_update: extract_foreign_key_action(row["on_update"])
+ }
+ ForeignKeyDefinition.new(table_name, row["table"], options)
+ end
+ end
+
private
def table_structure(table_name)
@@ -519,6 +558,8 @@ module ActiveRecord
RecordNotUnique.new(message)
when /.* may not be NULL/, /NOT NULL constraint failed: .*/
NotNullViolation.new(message)
+ when /FOREIGN KEY constraint failed/i
+ InvalidForeignKey.new(message)
else
super
end
@@ -568,6 +609,18 @@ module ActiveRecord
def create_table_definition(*args)
SQLite3::TableDefinition.new(*args)
end
+
+ def extract_foreign_key_action(specifier)
+ case specifier
+ when "CASCADE"; :cascade
+ when "SET NULL"; :nullify
+ when "RESTRICT"; :restrict
+ end
+ end
+
+ def configure_connection
+ execute("PRAGMA foreign_keys = ON", "SCHEMA")
+ end
end
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 5d0f82130d..0028dc0edb 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -196,12 +196,12 @@ module ActiveRecord
end
def find_by(*args) # :nodoc:
- return super if scope_attributes? || !(Hash === args.first) || reflect_on_all_aggregations.any?
+ return super if scope_attributes? || reflect_on_all_aggregations.any?
hash = args.first
- return super if hash.values.any? { |v|
- v.nil? || Array === v || Hash === v || Relation === v
+ return super if !(Hash === hash) || hash.values.any? { |v|
+ v.nil? || Array === v || Hash === v || Relation === v || Base === v
}
# We can't cache Post.find_by(author: david) ...yet
@@ -308,7 +308,7 @@ module ActiveRecord
relation = Relation.create(self, arel_table, predicate_builder)
if finder_needs_type_condition? && !ignore_default_scope?
- relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
+ relation.where(type_condition).create_with(inheritance_column.to_s => sti_name)
else
relation
end
@@ -450,7 +450,7 @@ module ActiveRecord
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
def hash
if id
- self.class.hash ^ self.id.hash
+ self.class.hash ^ id.hash
else
super
end
@@ -472,7 +472,7 @@ module ActiveRecord
# Allows sort on objects
def <=>(other_object)
if other_object.is_a?(self.class)
- self.to_key <=> other_object.to_key
+ to_key <=> other_object.to_key
else
super
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 93b9371206..cbd71a3779 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -105,7 +105,8 @@ module ActiveRecord
end
if touch
- updates << sanitize_sql_for_assignment(touch_updates(touch))
+ touch_updates = touch_updates(touch)
+ updates << sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty?
end
unscoped.where(primary_key => id).update_all updates.join(", ")
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index de1b0d63bc..e79167d568 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -885,7 +885,7 @@ module ActiveRecord
#
# The keys must be the fixture names, that coincide with the short paths to the fixture files.
def set_fixture_class(class_names = {})
- self.fixture_class_names = self.fixture_class_names.merge(class_names.stringify_keys)
+ self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
end
def fixtures(*fixture_set_names)
@@ -970,6 +970,7 @@ module ActiveRecord
@fixture_connections = enlist_fixture_connections
@fixture_connections.each do |connection|
connection.begin_transaction joinable: false
+ connection.pool.lock_thread = true
end
# When connections are established in the future, begin a transaction too
@@ -985,6 +986,7 @@ module ActiveRecord
if connection && !@fixture_connections.include?(connection)
connection.begin_transaction joinable: false
+ connection.pool.lock_thread = true
@fixture_connections << connection
end
end
@@ -1007,6 +1009,7 @@ module ActiveRecord
ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
@fixture_connections.each do |connection|
connection.rollback_transaction if connection.transaction_open?
+ connection.pool.lock_thread = false
end
@fixture_connections.clear
else
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index e73cb4fc12..263e2a5f7f 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -59,7 +59,16 @@ module ActiveRecord
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
# the locked record.
def lock!(lock = true)
- reload(lock: lock) if persisted?
+ if persisted?
+ if changed?
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Locking a record with unpersisted changes is deprecated and will raise an
+ exception in Rails 5.2. Use `save` to persist the changes, or `reload` to
+ discard them explicitly.
+ MSG
+ end
+ reload(lock: lock)
+ end
self
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 4b8d8d9105..ea101946f4 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -81,7 +81,7 @@ module ActiveRecord
RED
when /transaction\s*\Z/i
CYAN
- else
+ else
MAGENTA
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index ed0c81b639..40f6226315 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -525,7 +525,7 @@ module ActiveRecord
raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \
"Please specify the Rails release the migration was written for:\n" \
"\n" \
- " class #{self.class.name} < ActiveRecord::Migration[4.2]"
+ " class #{subclass} < ActiveRecord::Migration[4.2]"
end
end
@@ -692,7 +692,7 @@ module ActiveRecord
connection.respond_to?(:reverting) && connection.reverting
end
- class ReversibleBlockHelper < Struct.new(:reverting) # :nodoc:
+ ReversibleBlockHelper = Struct.new(:reverting) do # :nodoc:
def up
yield unless reverting
end
@@ -938,7 +938,7 @@ module ActiveRecord
# MigrationProxy is used to defer loading of the actual migration classes
# until they are needed
- class MigrationProxy < Struct.new(:name, :version, :filename, :scope)
+ MigrationProxy = Struct.new(:name, :version, :filename, :scope) do
def initialize(name, version, filename, scope)
super
@migration = nil
@@ -1107,8 +1107,8 @@ module ActiveRecord
validate(@migrations)
- Base.connection.initialize_schema_migrations_table
- Base.connection.initialize_internal_metadata_table
+ ActiveRecord::SchemaMigration.create_table
+ ActiveRecord::InternalMetadata.create_table
end
def current_version
@@ -1170,9 +1170,10 @@ module ActiveRecord
def run_without_lock
migration = migrations.detect { |m| m.version == @target_version }
raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
- execute_migration_in_transaction(migration, @direction)
+ result = execute_migration_in_transaction(migration, @direction)
record_environment
+ result
end
# Used for running multiple migrations up to or down to a certain value.
@@ -1181,11 +1182,12 @@ module ActiveRecord
raise UnknownMigrationVersionError.new(@target_version)
end
- runnable.each do |migration|
+ result = runnable.each do |migration|
execute_migration_in_transaction(migration, @direction)
end
record_environment
+ result
end
# Stores the current environment in the database.
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index 2904634eb7..85032ce470 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -14,23 +14,62 @@ module ActiveRecord
V5_1 = Current
class V5_0 < V5_1
+ module TableDefinition
+ def references(*args, **options)
+ super(*args, type: :integer, **options)
+ end
+ alias :belongs_to :references
+ end
+
def create_table(table_name, options = {})
if adapter_name == "PostgreSQL"
- if options[:id] == :uuid && !options[:default]
+ if options[:id] == :uuid && !options.key?(:default)
options[:default] = "uuid_generate_v4()"
end
end
+ unless adapter_name == "Mysql2" && options[:id] == :bigint
+ if [:integer, :bigint].include?(options[:id]) && !options.key?(:default)
+ options[:default] = nil
+ end
+ end
+
# Since 5.1 Postgres adapter uses bigserial type for primary
# keys by default and MySQL uses bigint. This compat layer makes old migrations utilize
# serial/int type instead -- the way it used to work before 5.1.
- if options[:id].blank?
+ unless options.key?(:id)
options[:id] = :integer
- options[:auto_increment] = true
end
- super
+ if block_given?
+ super(table_name, options) do |t|
+ class << t
+ prepend TableDefinition
+ end
+ yield t
+ end
+ else
+ super
+ end
end
+
+ def change_table(table_name, options = {})
+ if block_given?
+ super(table_name, options) do |t|
+ class << t
+ prepend TableDefinition
+ end
+ yield t
+ end
+ else
+ super
+ end
+ end
+
+ def add_reference(table_name, ref_name, **options)
+ super(table_name, ref_name, type: :integer, **options)
+ end
+ alias :add_belongs_to :add_reference
end
class V4_2 < V5_0
@@ -106,13 +145,13 @@ module ActiveRecord
def index_name_for_remove(table_name, options = {})
index_name = index_name(table_name, options)
- unless index_name_exists?(table_name, index_name, true)
+ unless index_name_exists?(table_name, index_name)
if options.is_a?(Hash) && options.has_key?(:name)
options_without_column = options.dup
options_without_column.delete :column
index_name_without_column = index_name(table_name, options_without_column)
- return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
+ return index_name_without_column if index_name_exists?(table_name, index_name_without_column)
end
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 2a28c6bf6d..54216caaaf 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -432,6 +432,7 @@ module ActiveRecord
connection.schema_cache.clear_data_source_cache!(table_name)
reload_schema_from_cache
+ initialize_find_by_cache
end
private
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index e983026961..01ecd79b8f 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -393,7 +393,7 @@ module ActiveRecord
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
# then the existing record will be marked for destruction.
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
- options = self.nested_attributes_options[association_name]
+ options = nested_attributes_options[association_name]
if attributes.respond_to?(:permitted?)
attributes = attributes.to_h
end
@@ -452,7 +452,7 @@ module ActiveRecord
# { id: '2', _destroy: true }
# ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
- options = self.nested_attributes_options[association_name]
+ options = nested_attributes_options[association_name]
if attributes_collection.respond_to?(:permitted?)
attributes_collection = attributes_collection.to_h
end
@@ -562,7 +562,7 @@ module ActiveRecord
def call_reject_if(association_name, attributes)
return false if will_be_destroyed?(association_name, attributes)
- case callback = self.nested_attributes_options[association_name][:reject_if]
+ case callback = nested_attributes_options[association_name][:reject_if]
when Symbol
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
when Proc
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 63d8a201f0..7ceb7d1a55 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -148,6 +148,8 @@ module ActiveRecord
#
# Attributes marked as readonly are silently ignored if the record is
# being updated.
+ #
+ # Unless an error is raised, returns true.
def save!(*args)
create_or_update(*args) || raise(RecordNotSaved.new("Failed to save the record", self))
end
@@ -338,14 +340,16 @@ module ActiveRecord
self
end
- # Wrapper around #increment that saves the record. This method differs from
- # its non-bang version in that it passes through the attribute setter.
- # Saving is not subjected to validation checks. Returns +true+ if the
- # record could be saved.
- def increment!(attribute, by = 1)
+ # Wrapper around #increment that writes the update to the database.
+ # Only +attribute+ is updated; the record itself is not saved.
+ # This means that any other modified attributes will still be dirty.
+ # Validations and callbacks are skipped. Supports the `touch` option from
+ # +update_counters+, see that for more.
+ # Returns +self+.
+ def increment!(attribute, by = 1, touch: nil)
increment(attribute, by)
change = public_send(attribute) - (attribute_in_database(attribute.to_s) || 0)
- self.class.update_counters(id, attribute => change)
+ self.class.update_counters(id, attribute => change, touch: touch)
clear_attribute_change(attribute) # eww
self
end
@@ -357,12 +361,14 @@ module ActiveRecord
increment(attribute, -by)
end
- # Wrapper around #decrement that saves the record. This method differs from
- # its non-bang version in the sense that it passes through the attribute setter.
- # Saving is not subjected to validation checks. Returns +true+ if the
- # record could be saved.
- def decrement!(attribute, by = 1)
- increment!(attribute, -by)
+ # Wrapper around #decrement that writes the update to the database.
+ # Only +attribute+ is updated; the record itself is not saved.
+ # This means that any other modified attributes will still be dirty.
+ # Validations and callbacks are skipped. Supports the `touch` option from
+ # +update_counters+, see that for more.
+ # Returns +self+.
+ def decrement!(attribute, by = 1, touch: nil)
+ increment!(attribute, -by, touch: touch)
end
# Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 2701c5bca9..0276d41494 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -87,8 +87,8 @@ module ActiveRecord
if File.file?(filename)
cache = YAML.load(File.read(filename))
if cache.version == ActiveRecord::Migrator.current_version
- self.connection.schema_cache = cache
- self.connection_pool.schema_cache = cache.dup
+ connection.schema_cache = cache
+ connection_pool.schema_cache = cache.dup
else
warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}."
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index cf1c0fb423..246d330b76 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -309,14 +309,6 @@ db_namespace = namespace :db do
end
namespace :test do
-
- task :deprecated do
- Rake.application.top_level_tasks.grep(/^db:test:/).each do |task|
- $stderr.puts "WARNING: #{task} is deprecated. The Rails test helper now maintains " \
- "your test schema automatically, see the release notes for details."
- end
- end
-
# desc "Recreate the test database from the current schema"
task load: %w(db:test:purge) do
case ActiveRecord::Base.schema_format
@@ -345,22 +337,6 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :sql, ENV["SCHEMA"]
end
- # desc "Recreate the test database from a fresh schema"
- task clone: %w(db:test:deprecated environment) do
- case ActiveRecord::Base.schema_format
- when :ruby
- db_namespace["test:clone_schema"].invoke
- when :sql
- db_namespace["test:clone_structure"].invoke
- end
- end
-
- # desc "Recreate the test database from a fresh schema.rb file"
- task clone_schema: %w(db:test:deprecated db:schema:dump db:test:load_schema)
-
- # desc "Recreate the test database from a fresh structure.sql file"
- task clone_structure: %w(db:test:deprecated db:structure:dump db:test:load_structure)
-
# desc "Empty the test database"
task purge: %w(environment load_config check_protected_environments) do
ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations["test"]
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index 8ff265bdfa..6274996ab8 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -11,7 +11,7 @@ module ActiveRecord
# Attributes listed as readonly will be used to create a new record but update operations will
# ignore these fields.
def attr_readonly(*attributes)
- self._attr_readonly = Set.new(attributes.map(&:to_s)) + (self._attr_readonly || [])
+ self._attr_readonly = Set.new(attributes.map(&:to_s)) + (_attr_readonly || [])
end
# Returns an array of all the attributes that have been specified as readonly.
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index f3e81ee1e2..61a2279292 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,5 +1,6 @@
require "thread"
require "active_support/core_ext/string/filters"
+require "active_support/deprecation"
module ActiveRecord
# = Active Record Reflection
@@ -175,8 +176,19 @@ module ActiveRecord
JoinKeys.new(foreign_key, active_record_primary_key)
end
+ # Returns a list of scopes that should be applied for this Reflection
+ # object when querying the database.
+ def scopes
+ scope ? [scope] : []
+ end
+
+ def scope_chain
+ chain.map(&:scopes)
+ end
+ deprecate :scope_chain
+
def constraints
- scope_chain.flatten
+ chain.map(&:scopes).flatten
end
def counter_cache_column
@@ -321,7 +333,7 @@ module ActiveRecord
end
end
- # Holds all the meta-data about an aggregation as it was specified in the
+ # Holds all the metadata about an aggregation as it was specified in the
# Active Record class.
class AggregateReflection < MacroReflection #:nodoc:
def mapping
@@ -330,7 +342,7 @@ module ActiveRecord
end
end
- # Holds all the meta-data about an association as it was specified in the
+ # Holds all the metadata about an association as it was specified in the
# Active Record class.
class AssociationReflection < MacroReflection #:nodoc:
# Returns the target association's class.
@@ -364,6 +376,17 @@ module ActiveRecord
@constructable = calculate_constructable(macro, options)
@association_scope_cache = {}
@scope_lock = Mutex.new
+
+ if options[:class_name] && options[:class_name].class == Class
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing a class to the `class_name` is deprecated and will raise
+ an ArgumentError in Rails 5.2. It eagerloads more classes than
+ necessary and potentially creates circular dependencies.
+
+ Please pass the class name as a string:
+ `#{macro} :#{name}, class_name: '#{options[:class_name]}'`
+ MSG
+ end
end
def association_scope_cache(conn, owner)
@@ -398,7 +421,7 @@ module ActiveRecord
end
def association_primary_key_type
- klass.type_for_attribute(association_primary_key)
+ klass.type_for_attribute(association_primary_key.to_s)
end
def active_record_primary_key
@@ -450,12 +473,6 @@ module ActiveRecord
false
end
- # An array of arrays of scopes. Each item in the outside array corresponds to a reflection
- # in the #chain.
- def scope_chain
- scope ? [[scope]] : [[]]
- end
-
def has_scope?
scope
end
@@ -698,7 +715,7 @@ module ActiveRecord
end
end
- # Holds all the meta-data about a :through association as it was specified
+ # Holds all the metadata about a :through association as it was specified
# in the Active Record class.
class ThroughReflection < AbstractReflection #:nodoc:
attr_reader :delegate_reflection
@@ -785,45 +802,12 @@ module ActiveRecord
through_reflection.clear_association_scope_cache
end
- # Consider the following example:
- #
- # class Person
- # has_many :articles
- # has_many :comment_tags, through: :articles
- # end
- #
- # class Article
- # has_many :comments
- # has_many :comment_tags, through: :comments, source: :tags
- # end
- #
- # class Comment
- # has_many :tags
- # end
- #
- # There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
- # but only Comment.tags will be represented in the #chain. So this method creates an array
- # of scopes corresponding to the chain.
- def scope_chain
- @scope_chain ||= begin
- scope_chain = source_reflection.scope_chain.map(&:dup)
-
- # Add to it the scope from this reflection (if any)
- scope_chain.first << scope if scope
-
- through_scope_chain = through_reflection.scope_chain.map(&:dup)
-
- if options[:source_type]
- type = foreign_type
- source_type = options[:source_type]
- through_scope_chain.first << lambda { |object|
- where(type => source_type)
- }
- end
+ def scopes
+ source_reflection.scopes + super
+ end
- # Recursively fill out the rest of the array from the through reflection
- scope_chain + through_scope_chain
- end
+ def source_type_scope
+ through_reflection.klass.where(foreign_type => options[:source_type])
end
def has_scope?
@@ -851,7 +835,7 @@ module ActiveRecord
end
def association_primary_key_type
- klass.type_for_attribute(association_primary_key)
+ klass.type_for_attribute(association_primary_key.to_s)
end
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
@@ -931,6 +915,14 @@ module ActiveRecord
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
end
+ if parent_reflection.nil?
+ reflections = active_record.reflections.keys.map(&:to_sym)
+
+ if reflections.index(through_reflection.name) > reflections.index(name)
+ raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
+ end
+ end
+
check_validity_of_inverse!
end
@@ -961,8 +953,7 @@ module ActiveRecord
end
end
- protected
-
+ private
def actual_source_reflection # FIXME: this is a horrible name
source_reflection.send(:actual_source_reflection)
end
@@ -973,7 +964,6 @@ module ActiveRecord
def inverse_name; delegate_reflection.send(:inverse_name); end
- private
def derive_class_name
# get the class_name of the belongs_to association of the through reflection
options[:source_type] || source_reflection.class_name
@@ -991,6 +981,15 @@ module ActiveRecord
@previous_reflection = previous_reflection
end
+ def scopes
+ scopes = @previous_reflection.scopes
+ if @previous_reflection.options[:source_type]
+ scopes + [@previous_reflection.source_type_scope]
+ else
+ scopes
+ end
+ end
+
def klass
@reflection.klass
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index ccd75ec5d2..61ee09bcc8 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -418,8 +418,7 @@ module ActiveRecord
records.each { |record| record.update(attributes) }
else
if ActiveRecord::Base === id
- id = id.id
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ raise ArgumentError, <<-MSG.squish
You are passing an instance of ActiveRecord::Base to `update`.
Please pass the id of the object by calling `.id`.
MSG
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 4b2987ac6d..76031515fd 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -260,7 +260,7 @@ module ActiveRecord
end
def act_on_ignored_order(error_on_ignore)
- raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order : error_on_ignore)
+ raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
if raise_error
raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 827688a663..35c670f1a1 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -193,7 +193,7 @@ module ActiveRecord
# If #count is used with #distinct (i.e. `relation.distinct.count`) it is
# considered distinct.
- distinct = self.distinct_value
+ distinct = distinct_value
if operation == "count"
column_name ||= select_for_count
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 3c1dea8c6c..d3ba724507 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -38,6 +38,7 @@ module ActiveRecord
delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
:[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
+ :to_sentence, :to_formatted_s,
:shuffle, :split, :index, to: :records
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
@@ -113,7 +114,7 @@ module ActiveRecord
arel.respond_to?(method, include_private)
end
- protected
+ private
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 6663bdb244..4548944fe6 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -17,8 +17,8 @@ module ActiveRecord
# Person.where("administrator = 1").order("created_on DESC").find(1)
#
# NOTE: The returned records may not be in the same order as the ids you
- # provide since database rows are unordered. You'd need to provide an explicit QueryMethods#order
- # option if you want the results are sorted.
+ # provide since database rows are unordered. You will need to provide an explicit QueryMethods#order
+ # option if you want the results to be sorted.
#
# ==== Find with lock
#
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 78c046b07f..4ee413c805 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -3,7 +3,6 @@ require "active_record/relation/query_attribute"
require "active_record/relation/where_clause"
require "active_record/relation/where_clause_factory"
require "active_model/forbidden_attributes_protection"
-require "active_support/core_ext/string/filters"
module ActiveRecord
module QueryMethods
@@ -657,7 +656,7 @@ module ActiveRecord
end
self.where_clause = self.where_clause.or(other.where_clause)
- self.having_clause = self.having_clause.or(other.having_clause)
+ self.having_clause = having_clause.or(other.having_clause)
self
end
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index ef0d059d1c..417b24c7bb 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -25,10 +25,7 @@ module ActiveRecord
end
def except(*columns)
- WhereClause.new(
- predicates_except(columns),
- binds_except(columns),
- )
+ WhereClause.new(*except_predicates_and_binds(columns))
end
def or(other)
@@ -134,20 +131,35 @@ module ActiveRecord
end
end
- def predicates_except(columns)
- predicates.reject do |node|
- case node
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
- subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
- columns.include?(subrelation.name.to_s)
+ def except_predicates_and_binds(columns)
+ except_binds = []
+ binds_index = 0
+
+ predicates = self.predicates.reject do |node|
+ except = \
+ case node
+ when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
+ binds_contains = node.grep(Arel::Nodes::BindParam).size
+ subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
+ columns.include?(subrelation.name.to_s)
+ end
+
+ if except && binds_contains > 0
+ (binds_index...(binds_index + binds_contains)).each do |i|
+ except_binds[i] = true
+ end
+
+ binds_index += binds_contains
end
+
+ except
end
- end
- def binds_except(columns)
- binds.reject do |attr|
- columns.include?(attr.name)
+ binds = self.binds.reject.with_index do |_, i|
+ except_binds[i]
end
+
+ [predicates, binds]
end
def predicates_with_wrapped_sql_literals
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
index 737bc278bd..04bee73e8f 100644
--- a/activerecord/lib/active_record/relation/where_clause_factory.rb
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -15,9 +15,12 @@ module ActiveRecord
attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
attributes.stringify_keys!
- attributes, binds = predicate_builder.create_binds(attributes)
-
- parts = predicate_builder.build_from_hash(attributes)
+ if perform_case_sensitive?(options = other.last)
+ parts, binds = build_for_case_sensitive(attributes, options)
+ else
+ attributes, binds = predicate_builder.create_binds(attributes)
+ parts = predicate_builder.build_from_hash(attributes)
+ end
when Arel::Nodes::Node
parts = [opts]
else
@@ -32,6 +35,43 @@ module ActiveRecord
protected
attr_reader :klass, :predicate_builder
+
+ private
+
+ def perform_case_sensitive?(options)
+ options && options.key?(:case_sensitive)
+ end
+
+ def build_for_case_sensitive(attributes, options)
+ parts, binds = [], []
+ table = klass.arel_table
+
+ attributes.each do |attribute, value|
+ if reflection = klass._reflect_on_association(attribute)
+ attribute = reflection.foreign_key.to_s
+ value = value[reflection.klass.primary_key] unless value.nil?
+ end
+
+ if value.nil?
+ parts << table[attribute].eq(value)
+ else
+ column = klass.column_for_attribute(attribute)
+
+ binds << predicate_builder.send(:build_bind_param, attribute, value)
+ value = Arel::Nodes::BindParam.new
+
+ predicate = if options[:case_sensitive]
+ klass.connection.case_sensitive_comparison(table, attribute, column, value)
+ else
+ klass.connection.case_insensitive_comparison(table, attribute, column, value)
+ end
+
+ parts << predicate
+ end
+ end
+
+ [parts, binds]
+ end
end
end
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 647834b12e..427c0019c6 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -46,7 +46,7 @@ module ActiveRecord
#
# sanitize_sql_for_assignment("name=NULL and group_id='4'")
# # => "name=NULL and group_id='4'"
- def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name) # :doc:
+ def sanitize_sql_for_assignment(assignments, default_table_name = table_name) # :doc:
case assignments
when Array; sanitize_sql_array(assignments)
when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 7a2bc9c8af..5104427339 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -48,7 +48,7 @@ module ActiveRecord
instance_eval(&block)
if info[:version].present?
- initialize_schema_migrations_table
+ ActiveRecord::SchemaMigration.create_table
connection.assume_migrated_upto_version(info[:version], migrations_paths)
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 12289511b7..15533f0151 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -188,7 +188,7 @@ HEADER
index_parts << "length: { #{format_options(index.lengths)} }" if index.lengths.present?
index_parts << "order: { #{format_options(index.orders)} }" if index.orders.present?
index_parts << "where: #{index.where.inspect}" if index.where
- index_parts << "using: #{index.using.inspect}" if index.using
+ index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index)
index_parts << "type: #{index.type.inspect}" if index.type
index_parts << "comment: #{index.comment.inspect}" if index.comment
index_parts
diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb
index 7606961e2e..115799cc20 100644
--- a/activerecord/lib/active_record/secure_token.rb
+++ b/activerecord/lib/active_record/secure_token.rb
@@ -27,7 +27,7 @@ module ActiveRecord
# Load securerandom only when has_secure_token is used.
require "active_support/core_ext/securerandom"
define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token }
- before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?") }
+ before_create { send("#{attribute}=", self.class.generate_unique_secure_token) unless send("#{attribute}?") }
end
def generate_unique_secure_token
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 5a408e7b8e..db2bd0b55e 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -9,7 +9,7 @@ module ActiveRecord #:nodoc:
end
def serializable_hash(options = nil)
- options = options.try(:clone) || {}
+ options = options.try(:dup) || {}
options[:except] = Array(options[:except]).map(&:to_s)
options[:except] |= Array(self.class.inheritance_column)
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index d4be20d999..006afe7495 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -78,7 +78,7 @@ module ActiveRecord
module ClassMethods
def store(store_attribute, options = {})
- serialize store_attribute, IndifferentCoder.new(options[:coder])
+ serialize store_attribute, IndifferentCoder.new(store_attribute, options[:coder])
store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
end
@@ -177,12 +177,12 @@ module ActiveRecord
end
class IndifferentCoder # :nodoc:
- def initialize(coder_or_class_name)
+ def initialize(attr_name, coder_or_class_name)
@coder =
if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
coder_or_class_name
else
- ActiveRecord::Coders::YAMLColumn.new(coder_or_class_name || Object)
+ ActiveRecord::Coders::YAMLColumn.new(attr_name, coder_or_class_name || Object)
end
end
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index b618e5cfcd..71efc1829a 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -44,7 +44,7 @@ module ActiveRecord
end
def associated_table(table_name)
- association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.singularize)
+ association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.to_s.singularize)
if !association && table_name == arel_table.name
return self
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 1423e6008f..82604a915f 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -1,5 +1,3 @@
-require "active_support/core_ext/string/filters"
-
module ActiveRecord
module Tasks # :nodoc:
class DatabaseAlreadyExists < StandardError; end # :nodoc:
@@ -269,8 +267,8 @@ module ActiveRecord
if seed_loader
seed_loader.load_seed
else
- raise "You tried to load seed data, but no seed loader is specified. Please specify seed " +
- "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" +
+ raise "You tried to load seed data, but no seed loader is specified. Please specify seed " \
+ "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" \
"Seed loader should respond to load_seed method"
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 56b75540e3..08417aaa0f 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -11,7 +11,6 @@ module ActiveRecord
:before_commit_without_transaction_enrollment,
:commit_without_transaction_enrollment,
:rollback_without_transaction_enrollment,
- terminator: deprecated_false_terminator,
scope: [:kind, :name]
end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index ac9134bfcb..6af05c1860 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -43,7 +43,7 @@ module ActiveRecord
def assert_valid_value(value)
if coder.respond_to?(:assert_valid_value)
- coder.assert_valid_value(value)
+ coder.assert_valid_value(value, action: "serialize")
end
end
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index ca5eda2f84..7cfd55f516 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -57,7 +57,7 @@ module ActiveRecord
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
# proc or string should return or evaluate to a +true+ or +false+ value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See ActiveModel::Validation#validates! for more information.
+ # See ActiveModel::Validations#validates! for more information.
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 9e8edfbfaf..154cf5f1a4 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -50,37 +50,7 @@ module ActiveRecord
end
def build_relation(klass, attribute, value)
- if reflection = klass._reflect_on_association(attribute)
- attribute = reflection.foreign_key
- value = value.attributes[reflection.klass.primary_key] unless value.nil?
- end
-
- if value.nil?
- return klass.unscoped.where!(attribute => value)
- end
-
- # the attribute may be an aliased attribute
- if klass.attribute_alias?(attribute)
- attribute = klass.attribute_alias(attribute)
- end
-
- attribute_name = attribute.to_s
-
- table = klass.arel_table
- column = klass.columns_hash[attribute_name]
- cast_type = klass.type_for_attribute(attribute_name)
-
- comparison = if !options[:case_sensitive]
- # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
- klass.connection.case_insensitive_comparison(table, attribute, column, value)
- else
- klass.connection.case_sensitive_comparison(table, attribute, column, value)
- end
- klass.unscoped.tap do |scope|
- parts = [comparison]
- binds = [Relation::QueryAttribute.new(attribute_name, value, cast_type)]
- scope.where_clause += Relation::WhereClause.new(parts, binds)
- end
+ klass.unscoped.where!({ attribute => value }, options)
end
def scope_relation(record, relation)
diff --git a/activerecord/lib/rails/generators/active_record/migration.rb b/activerecord/lib/rails/generators/active_record/migration.rb
index 4263c11ffc..43075077b9 100644
--- a/activerecord/lib/rails/generators/active_record/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration.rb
@@ -20,6 +20,14 @@ module ActiveRecord
key_type = options[:primary_key_type]
", id: :#{key_type}" if key_type
end
+
+ def db_migrate_path
+ if defined?(Rails) && Rails.application
+ Rails.application.config.paths["db/migrate"].to_ary.first
+ else
+ "db/migrate"
+ end
+ 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 8511531af7..1f1c47499b 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -10,7 +10,7 @@ module ActiveRecord
def create_migration_file
set_local_assigns!
validate_file_name!
- migration_template @migration_template, "db/migrate/#{file_name}.rb"
+ migration_template @migration_template, File.join(db_migrate_path, "#{file_name}.rb")
end
# TODO Change this to private once we've dropped Ruby 2.2 support.
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 61a8d3c100..5cec07d2e3 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -17,7 +17,7 @@ module ActiveRecord
def create_migration_file
return unless options[:migration] && options[:parent].nil?
attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false
- migration_template "../../migration/templates/create_table_migration.rb", "db/migrate/create_#{table_name}.rb"
+ migration_template "../../migration/templates/create_table_migration.rb", File.join(db_migrate_path, "create_#{table_name}.rb")
end
def create_model_file
@@ -41,7 +41,7 @@ module ActiveRecord
# FIXME: Change this file to a symlink once RubyGems 2.5.0 is required.
def generate_application_record
- if self.behavior == :invoke && !application_record_exist?
+ if behavior == :invoke && !application_record_exist?
template "application_record.rb", application_record_file_name
end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 9828e682ef..0c9d1dff9d 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -31,7 +31,6 @@ module ActiveRecord
end
def test_tables
- tables = nil
tables = @connection.tables
assert_includes tables, "accounts"
assert_includes tables, "authors"
@@ -185,34 +184,6 @@ module ActiveRecord
end
unless current_adapter?(:SQLite3Adapter)
- def test_foreign_key_violations_are_translated_to_specific_exception
- error = assert_raises(ActiveRecord::InvalidForeignKey) do
- # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
- if @connection.prefetch_primary_key?
- id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
- @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)"
- else
- @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
- end
- end
-
- assert_not_nil error.cause
- end
-
- def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false
- klass_has_fk = Class.new(ActiveRecord::Base) do
- self.table_name = "fk_test_has_fk"
- end
-
- error = assert_raises(ActiveRecord::InvalidForeignKey) do
- has_fk = klass_has_fk.new
- has_fk.fk_id = 1231231231
- has_fk.save(validate: false)
- end
-
- assert_not_nil error.cause
- end
-
def test_value_limit_violations_are_translated_to_specific_exception
error = assert_raises(ActiveRecord::ValueTooLong) do
Event.create(title: "abcdefgh")
@@ -223,30 +194,13 @@ module ActiveRecord
def test_numeric_value_out_of_ranges_are_translated_to_specific_exception
error = assert_raises(ActiveRecord::RangeError) do
- Book.connection.create("INSERT INTO books(author_id) VALUES (2147483648)")
+ Book.connection.create("INSERT INTO books(author_id) VALUES (9223372036854775808)")
end
assert_not_nil error.cause
end
end
- def test_disable_referential_integrity
- assert_nothing_raised do
- @connection.disable_referential_integrity do
- # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
- if @connection.prefetch_primary_key?
- id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
- @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)"
- else
- @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
- end
- # should delete created record as otherwise disable_referential_integrity will try to enable constraints after executed block
- # and will fail (at least on Oracle)
- @connection.execute "DELETE FROM fk_test_has_fk"
- end
- end
- end
-
def test_select_all_always_return_activerecord_result
result = @connection.select_all "SELECT * FROM posts"
assert result.is_a?(ActiveRecord::Result)
@@ -290,6 +244,59 @@ module ActiveRecord
end
end
+ class AdapterForeignKeyTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false
+ klass_has_fk = Class.new(ActiveRecord::Base) do
+ self.table_name = "fk_test_has_fk"
+ end
+
+ error = assert_raises(ActiveRecord::InvalidForeignKey) do
+ has_fk = klass_has_fk.new
+ has_fk.fk_id = 1231231231
+ has_fk.save(validate: false)
+ end
+
+ assert_not_nil error.cause
+ end
+
+ def test_foreign_key_violations_are_translated_to_specific_exception
+ error = assert_raises(ActiveRecord::InvalidForeignKey) do
+ insert_into_fk_test_has_fk
+ end
+
+ assert_not_nil error.cause
+ end
+
+ def test_disable_referential_integrity
+ assert_nothing_raised do
+ @connection.disable_referential_integrity do
+ insert_into_fk_test_has_fk
+ # should delete created record as otherwise disable_referential_integrity will try to enable constraints
+ # after executed block and will fail (at least on Oracle)
+ @connection.execute "DELETE FROM fk_test_has_fk"
+ end
+ end
+ end
+
+ private
+
+ def insert_into_fk_test_has_fk
+ # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
+ if @connection.prefetch_primary_key?
+ id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
+ @connection.execute "INSERT INTO fk_test_has_fk (id,fk_id) VALUES (#{id_value},0)"
+ else
+ @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
+ end
+ end
+ end
+
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
self.use_transactional_tests = false
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 2a528b2cb1..67e1efde27 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -92,8 +92,8 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
assert_equal expected, actual
end
- expected = "ALTER TABLE `peaple` ADD INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
- actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t|
+ expected = "ALTER TABLE `people` ADD INDEX `index_people_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
+ actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
t.index :last_name, length: 10, using: :btree, algorithm: :copy
end
assert_equal expected, actual
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index c1de2218e2..1f94472390 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -66,9 +66,10 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
def test_execute_after_disconnect
@connection.disconnect!
- assert_raise(ActiveRecord::StatementInvalid) do
+ error = assert_raise(ActiveRecord::StatementInvalid) do
@connection.execute("SELECT 1")
end
+ assert_kind_of Mysql2::Error, error.cause
end
def test_quote_after_disconnect
@@ -119,7 +120,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
end
- def test_passing_arbitary_flags_to_adapter
+ def test_passing_arbitrary_flags_to_adapter
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge(flags: Mysql2::Client::COMPRESS))
assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags]
diff --git a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
index 135789a57d..c131a5169c 100644
--- a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
@@ -6,23 +6,27 @@ class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase
end
test "microsecond precision for MySQL gte 5.6.4" do
- stub_version "5.6.4"
- assert_microsecond_precision
+ stub_version "5.6.4" do
+ assert_microsecond_precision
+ end
end
test "no microsecond precision for MySQL lt 5.6.4" do
- stub_version "5.6.3"
- assert_no_microsecond_precision
+ stub_version "5.6.3" do
+ assert_no_microsecond_precision
+ end
end
test "microsecond precision for MariaDB gte 5.3.0" do
- stub_version "5.5.5-10.1.8-MariaDB-log"
- assert_microsecond_precision
+ stub_version "5.5.5-10.1.8-MariaDB-log" do
+ assert_microsecond_precision
+ end
end
test "no microsecond precision for MariaDB lt 5.3.0" do
- stub_version "5.2.9-MariaDB"
- assert_no_microsecond_precision
+ stub_version "5.2.9-MariaDB" do
+ assert_no_microsecond_precision
+ end
end
private
@@ -41,5 +45,8 @@ class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase
def stub_version(full_version_string)
@connection.stubs(:full_version).returns(full_version_string)
@connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
+ yield
+ ensure
+ @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb b/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb
deleted file mode 100644
index 5d3125c2be..0000000000
--- a/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require "cases/helper"
-
-class MysqlLegacyMigrationTest < ActiveRecord::Mysql2TestCase
- self.use_transactional_tests = false
-
- class GenerateTableWithoutBigint < ActiveRecord::Migration[5.0]
- def change
- create_table :legacy_integer_pk do |table|
- table.string :foo
- end
-
- create_table :override_pk, id: :bigint do |table|
- table.string :bar
- end
- end
- end
-
- def setup
- super
- @connection = ActiveRecord::Base.connection
-
- @migration_verbose_old = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
-
- migrations = [GenerateTableWithoutBigint.new(nil, 1)]
-
- ActiveRecord::Migrator.new(:up, migrations).migrate
- end
-
- def teardown
- ActiveRecord::Migration.verbose = @migration_verbose_old
- @connection.drop_table("legacy_integer_pk")
- @connection.drop_table("override_pk")
- ActiveRecord::SchemaMigration.delete_all rescue nil
- super
- end
-
- def test_create_table_uses_integer_as_pkey_by_default
- col = column(:legacy_integer_pk, :id)
- assert_equal "int(11)", sql_type_for(col)
- assert col.auto_increment?
- end
-
- def test_create_tables_respects_pk_column_type_override
- col = column(:override_pk, :id)
- assert_equal "bigint(20)", sql_type_for(col)
- end
-
- private
-
- def column(table_name, column_name)
- ActiveRecord::Base.connection
- .columns(table_name.to_s)
- .detect { |c| c.name == column_name.to_s }
- end
-
- def sql_type_for(col)
- col && col.sql_type
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index fa54aac6b3..605baa9905 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -16,7 +16,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
table_name = ActiveRecord::SchemaMigration.table_name
connection.drop_table table_name, if_exists: true
- connection.initialize_schema_migrations_table
+ ActiveRecord::SchemaMigration.create_table
assert connection.column_exists?(table_name, :version, :string, collation: "utf8_general_ci")
end
@@ -27,7 +27,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
table_name = ActiveRecord::InternalMetadata.table_name
connection.drop_table table_name, if_exists: true
- connection.initialize_internal_metadata_table
+ ActiveRecord::InternalMetadata.create_table
assert connection.column_exists?(table_name, :key, :string, collation: "utf8_general_ci")
end
diff --git a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
index bee42d48f1..d6e7f29a5c 100644
--- a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
@@ -8,7 +8,7 @@ class Mysql2SqlTypesTest < ActiveRecord::Mysql2TestCase
assert_equal "blob", type_to_sql(:binary)
end
- def type_to_sql(*args)
- ActiveRecord::Base.connection.type_to_sql(*args)
+ def type_to_sql(type, limit = nil)
+ ActiveRecord::Base.connection.type_to_sql(type, limit: limit)
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb
new file mode 100644
index 0000000000..442a4fb7b5
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb
@@ -0,0 +1,59 @@
+require "cases/helper"
+require "support/schema_dumping_helper"
+
+if ActiveRecord::Base.connection.supports_virtual_columns?
+ class Mysql2VirtualColumnTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+
+ self.use_transactional_tests = false
+
+ class VirtualColumn < ActiveRecord::Base
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :virtual_columns, force: true do |t|
+ t.string :name
+ t.virtual :upper_name, type: :string, as: "UPPER(`name`)"
+ t.virtual :name_length, type: :integer, as: "LENGTH(`name`)", stored: true
+ end
+ VirtualColumn.create(name: "Rails")
+ end
+
+ def teardown
+ @connection.drop_table :virtual_columns, if_exists: true
+ VirtualColumn.reset_column_information
+ end
+
+ def test_virtual_column
+ column = VirtualColumn.columns_hash["upper_name"]
+ assert_predicate column, :virtual?
+ assert_match %r{\bVIRTUAL\b}, column.extra
+ assert_equal "RAILS", VirtualColumn.take.upper_name
+ end
+
+ def test_stored_column
+ column = VirtualColumn.columns_hash["name_length"]
+ assert_predicate column, :virtual?
+ assert_match %r{\b(?:STORED|PERSISTENT)\b}, column.extra
+ assert_equal 5, VirtualColumn.take.name_length
+ end
+
+ def test_change_table
+ @connection.change_table :virtual_columns do |t|
+ t.virtual :lower_name, type: :string, as: "LOWER(name)"
+ end
+ VirtualColumn.reset_column_information
+ column = VirtualColumn.columns_hash["lower_name"]
+ assert_predicate column, :virtual?
+ assert_match %r{\bVIRTUAL\b}, column.extra
+ assert_equal "rails", VirtualColumn.take.lower_name
+ end
+
+ def test_schema_dumping
+ output = dump_table_schema("virtual_columns")
+ assert_match(/t\.virtual\s+"upper_name",\s+type: :string,\s+as: "UPPER\(`name`\)"$/i, output)
+ assert_match(/t\.virtual\s+"name_length",\s+type: :integer,\s+as: "LENGTH\(`name`\)",\s+stored: true$/i, output)
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 5c207116c4..505c297cd4 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -32,9 +32,9 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
end
def test_binary_columns_are_limitless_the_upper_limit_is_one_GB
- assert_equal "bytea", @connection.type_to_sql(:binary, 100_000)
+ assert_equal "bytea", @connection.type_to_sql(:binary, limit: 100_000)
assert_raise ActiveRecord::ActiveRecordError do
- @connection.type_to_sql :binary, 4294967295
+ @connection.type_to_sql(:binary, limit: 4294967295)
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index e916d15f7f..3cbd4ca212 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -95,7 +95,7 @@ module ActiveRecord
end
def test_indexes_logs_name
- @connection.indexes("items", "hello")
+ assert_deprecated { @connection.indexes("items", "hello") }
assert_equal "SCHEMA", @subscriber.logged[0][1]
end
@@ -156,7 +156,7 @@ module ActiveRecord
secondary_connection.query("select pg_terminate_backend(#{original_connection_pid.first.first})")
ActiveRecord::Base.connection_pool.checkin(secondary_connection)
elsif ARTest.config["with_manual_interventions"]
- puts "Kill the connection now (e.g. by restarting the PostgreSQL " +
+ puts "Kill the connection now (e.g. by restarting the PostgreSQL " \
'server with the "-m fast" option) and then press enter.'
$stdin.gets
else
@@ -175,7 +175,7 @@ module ActiveRecord
new_connection_pid = @connection.query("select pg_backend_pid()")
assert_not_equal original_connection_pid, new_connection_pid,
- "umm -- looks like you didn't break the connection, because we're still " +
+ "umm -- looks like you didn't break the connection, because we're still " \
"successfully querying with the same connection pid."
# Repair all fixture connections so other tests won't break.
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 0ac8b7339b..0725fde5ae 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -28,12 +28,12 @@ class PostgresqlDataTypeTest < ActiveRecord::PostgreSQLTestCase
end
def test_data_type_of_time_types
- assert_equal :string, @first_time.column_for_attribute(:time_interval).type
- assert_equal :string, @first_time.column_for_attribute(:scaled_time_interval).type
+ assert_equal :interval, @first_time.column_for_attribute(:time_interval).type
+ assert_equal :interval, @first_time.column_for_attribute(:scaled_time_interval).type
end
def test_data_type_of_oid_types
- assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type
+ assert_equal :oid, @first_oid.column_for_attribute(:obj_id).type
end
def test_time_values
@@ -61,9 +61,9 @@ class PostgresqlDataTypeTest < ActiveRecord::PostgreSQLTestCase
end
def test_text_columns_are_limitless_the_upper_limit_is_one_GB
- assert_equal "text", @connection.type_to_sql(:text, 100_000)
+ assert_equal "text", @connection.type_to_sql(:text, limit: 100_000)
assert_raise ActiveRecord::ActiveRecordError do
- @connection.type_to_sql :text, 4294967295
+ @connection.type_to_sql(:text, limit: 4294967295)
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/infinity_test.rb b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
index 19b00258b6..b9e177e6ec 100644
--- a/activerecord/test/cases/adapters/postgresql/infinity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
@@ -30,7 +30,7 @@ class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase
record = PostgresqlInfinity.new(float: "-Infinity")
assert_equal(-Float::INFINITY, record.float)
record = PostgresqlInfinity.new(float: "NaN")
- assert_send [record.float, :nan?]
+ assert record.float.nan?, "Expected #{record.float} to be NaN"
end
test "update_all with infinity on a float column" do
diff --git a/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb b/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb
deleted file mode 100644
index 082fe95053..0000000000
--- a/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require "cases/helper"
-
-class PostgresqlLegacyMigrationTest < ActiveRecord::PostgreSQLTestCase
- class GenerateTableWithoutBigserial < ActiveRecord::Migration[5.0]
- def change
- create_table :legacy_integer_pk do |table|
- table.string :foo
- end
-
- create_table :override_pk, id: :bigint do |table|
- table.string :bar
- end
- end
- end
-
- def setup
- super
-
- @migration_verbose_old = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
-
- migrations = [GenerateTableWithoutBigserial.new(nil, 1)]
- ActiveRecord::Migrator.new(:up, migrations).migrate
- end
-
- def teardown
- ActiveRecord::Migration.verbose = @migration_verbose_old
-
- super
- end
-
- def test_create_table_uses_serial_as_pkey_by_default
- col = column(:legacy_integer_pk, :id)
- assert_equal "integer", sql_type_for(col)
- assert col.serial?
- end
-
- def test_create_tables_respects_pk_column_type_override
- col = column(:override_pk, :id)
- assert_equal "bigint", sql_type_for(col)
- end
-
- private
-
- def column(table_name, column_name)
- ActiveRecord::Base.connection.
- columns(table_name.to_s).
- detect { |c| c.name == column_name.to_s }
- end
-
- def sql_type_for(col)
- col && col.sql_type
- end
-end
diff --git a/activerecord/test/cases/adapters/postgresql/numbers_test.rb b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
index 834354dcc9..bfb2b7c27a 100644
--- a/activerecord/test/cases/adapters/postgresql/numbers_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
@@ -31,7 +31,7 @@ class PostgresqlNumberTest < ActiveRecord::PostgreSQLTestCase
assert_equal 123456.789, first.double
assert_equal(-::Float::INFINITY, second.single)
assert_equal ::Float::INFINITY, second.double
- assert_send [third.double, :nan?]
+ assert third.double.nan?, "Expected #{third.double} to be NaN"
end
def test_update
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index e6af93a53e..3054f0271f 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -54,12 +54,6 @@ module ActiveRecord
end
end
- def test_primary_key_raises_error_if_table_not_found
- assert_raises(ActiveRecord::StatementInvalid) do
- @connection.primary_key("unobtainium")
- end
- end
-
def test_exec_insert_with_returning_disabled
connection = connection_without_insert_returning
result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], "id", "postgresql_partitioned_table_parent_id_seq")
@@ -263,9 +257,12 @@ module ActiveRecord
def test_index_with_opclass
with_example_table do
- @connection.add_index "ex", "data varchar_pattern_ops", name: "with_opclass"
- index = @connection.indexes("ex").find { |idx| idx.name == "with_opclass" }
+ @connection.add_index "ex", "data varchar_pattern_ops"
+ index = @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" }
assert_equal "data varchar_pattern_ops", index.columns
+
+ @connection.remove_index "ex", "data varchar_pattern_ops"
+ assert_not @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" }
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 237e9ff6a5..7b065ff320 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -301,13 +301,13 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
def test_index_name_exists
with_schema_search_path(SCHEMA_NAME) do
- assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME, true)
- assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME, true)
- assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME, true)
- assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME, true)
- assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true)
- assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true)
- assert_not @connection.index_name_exists?(TABLE_NAME, "missing_index", true)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME)
+ assert_not @connection.index_name_exists?(TABLE_NAME, "missing_index")
end
end
@@ -366,14 +366,6 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
end
end
- def test_primary_key_raises_error_if_table_not_found_on_schema_search_path
- with_schema_search_path(SCHEMA2_NAME) do
- assert_raises(ActiveRecord::StatementInvalid) do
- @connection.primary_key(PK_TABLE_NAME)
- end
- end
- end
-
def test_pk_and_sequence_for_with_schema_specified
pg_name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
[
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index f34d50e25c..6aa6a79705 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -234,25 +234,23 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
end
end
- if ActiveRecord::Base.connection.supports_pgcrypto_uuid?
- def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration
- @verbose_was = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
-
- migration = Class.new(ActiveRecord::Migration[4.2]) do
- def version; 101 end
- def migrate(x)
- create_table("pg_uuids_4", id: :uuid)
- end
- end.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
-
- schema = dump_table_schema "pg_uuids_4"
- assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
- ensure
- drop_table "pg_uuids_4"
- ActiveRecord::Migration.verbose = @verbose_was
- end
+ def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+
+ migration = Class.new(ActiveRecord::Migration[5.0]) do
+ def version; 101 end
+ def migrate(x)
+ create_table("pg_uuids_4", id: :uuid)
+ end
+ end.new
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ schema = dump_table_schema "pg_uuids_4"
+ assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
+ ensure
+ drop_table "pg_uuids_4"
+ ActiveRecord::Migration.verbose = @verbose_was
end
end
end
@@ -285,6 +283,25 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase
schema = dump_table_schema "pg_uuids"
assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: nil/, schema)
end
+
+ def test_schema_dumper_for_uuid_primary_key_with_default_nil_in_legacy_migration
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+
+ migration = Class.new(ActiveRecord::Migration[5.0]) do
+ def version; 101 end
+ def migrate(x)
+ create_table("pg_uuids_4", id: :uuid, default: nil)
+ end
+ end.new
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ schema = dump_table_schema "pg_uuids_4"
+ assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: nil/, schema)
+ ensure
+ drop_table "pg_uuids_4"
+ ActiveRecord::Migration.verbose = @verbose_was
+ end
end
end
@@ -330,7 +347,6 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
assert_raise ActiveRecord::RecordNotFound do
UuidPost.find(123456)
end
-
end
def test_find_by_with_uuid
diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index 91967c1e33..e1cfd703e8 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -41,8 +41,8 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase
test_copy_table("comments", "comments_with_index") do
@connection.add_index("comments_with_index", ["post_id", "type"])
test_copy_table("comments_with_index", "comments_with_index2") do
- assert_equal table_indexes_without_name("comments_with_index"),
- table_indexes_without_name("comments_with_index2")
+ assert_nil table_indexes_without_name("comments_with_index")
+ assert_nil table_indexes_without_name("comments_with_index2")
end
end
end
@@ -59,7 +59,8 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase
copied_id = @connection.columns("goofy_string_id2").detect { |col| col.name == "id" }
assert_equal original_id.type, copied_id.type
assert_equal original_id.sql_type, copied_id.sql_type
- assert_equal original_id.limit, copied_id.limit
+ assert_nil original_id.limit
+ assert_nil copied_id.limit
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb b/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb
deleted file mode 100644
index fcca8d66b5..0000000000
--- a/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require "cases/helper"
-
-class SqliteLegacyMigrationTest < ActiveRecord::SQLite3TestCase
- self.use_transactional_tests = false
-
- class GenerateTableWithoutBigint < ActiveRecord::Migration[5.0]
- def change
- create_table :legacy_integer_pk do |table|
- table.string :foo
- end
-
- create_table :override_pk, id: :bigint do |table|
- table.string :bar
- end
- end
- end
-
- def setup
- super
- @connection = ActiveRecord::Base.connection
-
- @migration_verbose_old = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
-
- migrations = [GenerateTableWithoutBigint.new(nil, 1)]
-
- ActiveRecord::Migrator.new(:up, migrations).migrate
- end
-
- def teardown
- ActiveRecord::Migration.verbose = @migration_verbose_old
- @connection.drop_table("legacy_integer_pk")
- @connection.drop_table("override_pk")
- ActiveRecord::SchemaMigration.delete_all rescue nil
- super
- end
-
- def test_create_table_uses_integer_as_pkey_by_default
- col = column(:legacy_integer_pk, :id)
- assert_equal "INTEGER", sql_type_for(col)
- assert primary_key?(:legacy_integer_pk, "id"), "id is not primary key"
- end
-
- private
-
- def column(table_name, column_name)
- ActiveRecord::Base.connection
- .columns(table_name.to_s)
- .detect { |c| c.name == column_name.to_s }
- end
-
- def sql_type_for(col)
- col && col.sql_type
- end
-
- def primary_key?(table_name, column)
- ActiveRecord::Base.connection.execute("PRAGMA table_info(#{table_name})").find { |col| col["name"] == column }["pk"] == 1
- 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 a6109348cc..a6afb7816b 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -287,7 +287,7 @@ module ActiveRecord
def test_indexes_logs_name
with_example_table do
assert_logged [["PRAGMA index_list(\"ex\")", "SCHEMA", []]] do
- @conn.indexes("ex", "hello")
+ assert_deprecated { @conn.indexes("ex", "hello") }
end
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
index aebcce3691..37ff973397 100644
--- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
@@ -3,7 +3,6 @@ require "cases/helper"
class SQLite3StatementPoolTest < ActiveRecord::SQLite3TestCase
if Process.respond_to?(:fork)
def test_cache_is_per_pid
-
cache = ActiveRecord::ConnectionAdapters::SQLite3Adapter::StatementPool.new(10)
cache["foo"] = "bar"
assert_equal "bar", cache["foo"]
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index ff1bf8acd4..11f4aae5b3 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -739,18 +739,25 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_invalid_association_reference
- assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ e = assert_raise(ActiveRecord::AssociationNotFoundError) {
Post.all.merge!(includes: :monkeys).find(6)
}
- assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message)
+
+ e = assert_raise(ActiveRecord::AssociationNotFoundError) {
Post.all.merge!(includes: [ :monkeys ]).find(6)
}
- assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message)
+
+ e = assert_raise(ActiveRecord::AssociationNotFoundError) {
Post.all.merge!(includes: [ "monkeys" ]).find(6)
}
- assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") {
+ assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message)
+
+ e = assert_raise(ActiveRecord::AssociationNotFoundError) {
Post.all.merge!(includes: [ :monkeys, :elephants ]).find(6)
}
+ assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message)
end
def test_eager_has_many_through_with_order
@@ -933,7 +940,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
d2 = find_all_ordered(Firm, :account)
d1.each_index do |i|
assert_equal(d1[i], d2[i])
- assert_equal(d1[i].account, d2[i].account)
+ if d1[i].account.nil?
+ assert_nil(d2[i].account)
+ else
+ assert_equal(d1[i].account, d2[i].account)
+ end
end
end
@@ -943,7 +954,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
d2 = find_all_ordered(Client, firm_types)
d1.each_index do |i|
assert_equal(d1[i], d2[i])
- firm_types.each { |type| assert_equal(d1[i].send(type), d2[i].send(type)) }
+ firm_types.each do |type|
+ if (expected = d1[i].send(type)).nil?
+ assert_nil(d2[i].send(type))
+ else
+ assert_equal(expected, d2[i].send(type))
+ end
+ end
end
end
def test_eager_with_valid_association_as_string_not_symbol
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 54fb61d6a5..d6b595d7e7 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
@@ -86,8 +86,10 @@ class DeveloperWithSymbolClassName < Developer
has_and_belongs_to_many :projects, class_name: :ProjectWithSymbolsForKeys
end
-class DeveloperWithConstantClassName < Developer
- has_and_belongs_to_many :projects, class_name: ProjectWithSymbolsForKeys
+ActiveSupport::Deprecation.silence do
+ class DeveloperWithConstantClassName < Developer
+ has_and_belongs_to_many :projects, class_name: ProjectWithSymbolsForKeys
+ end
end
class DeveloperWithExtendOption < Developer
@@ -743,8 +745,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_scoped_grouped_having
- assert_equal 2, projects(:active_record).well_payed_salary_groups.to_a.size
- assert projects(:active_record).well_payed_salary_groups.all? { |g| g.salary > 10000 }
+ assert_equal 2, projects(:active_record).well_paid_salary_groups.to_a.size
+ assert projects(:active_record).well_paid_salary_groups.all? { |g| g.salary > 10000 }
end
def test_get_ids
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index cbecfa84ff..ede3a44090 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -611,21 +611,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_update_all_on_association_accessed_before_save
firm = Firm.new(name: "Firm")
- clients_proxy_id = firm.clients.object_id
firm.clients << Client.first
firm.save!
assert_equal firm.clients.count, firm.clients.update_all(description: "Great!")
- assert_not_equal clients_proxy_id, firm.clients.object_id
end
def test_update_all_on_association_accessed_before_save_with_explicit_foreign_key
- # We can use the same cached proxy object because the id is available for the scope
firm = Firm.new(name: "Firm", id: 100)
- clients_proxy_id = firm.clients.object_id
firm.clients << Client.first
firm.save!
assert_equal firm.clients.count, firm.clients.update_all(description: "Great!")
- assert_equal clients_proxy_id, firm.clients.object_id
end
def test_belongs_to_sanity
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 47c6480a8e..ea52fb5a67 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -28,6 +28,9 @@ require "models/member"
require "models/membership"
require "models/club"
require "models/organization"
+require "models/user"
+require "models/family"
+require "models/family_tree"
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags,
@@ -880,7 +883,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
book.subscriber_ids = []
assert_equal [], book.subscribers.reload
end
-
end
def test_collection_singular_ids_setter_with_changed_primary_key
@@ -1231,4 +1233,23 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
ensure
TenantMembership.current_member = nil
end
+
+ def test_has_many_through_with_scope_should_respect_table_alias
+ family = Family.create!
+ users = 3.times.map { User.create! }
+ FamilyTree.create!(member: users[0], family: family)
+ FamilyTree.create!(member: users[1], family: family)
+ FamilyTree.create!(member: users[2], family: family, token: "wat")
+
+ assert_equal 2, users[0].family_members.to_a.size
+ assert_equal 0, users[2].family_members.to_a.size
+ end
+
+ def test_incorrectly_ordered_through_associations
+ assert_raises(ActiveRecord::HasManyThroughOrderError) do
+ DeveloperWithIncorrectlyOrderedHasManyThrough.create(
+ companies: [Company.create]
+ )
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index ed22a9802f..7c11d2e7fc 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -476,7 +476,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal ships(:black_pearl), pirate.ship
assert_equal pirate.id, pirate.ship.pirate_id
- assert_equal "Failed to remove the existing associated ship. " +
+ assert_equal "Failed to remove the existing associated ship. " \
"The record failed to save after its foreign key was set to nil.", error.message
end
@@ -648,6 +648,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
class SpecialBook < ActiveRecord::Base
self.table_name = "books"
belongs_to :author, class_name: "SpecialAuthor"
+ has_one :subscription, class_name: "SpecialSupscription", foreign_key: "subscriber_id"
end
class SpecialAuthor < ActiveRecord::Base
@@ -655,11 +656,27 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
has_one :book, class_name: "SpecialBook", foreign_key: "author_id"
end
- def test_assocation_enum_works_properly
+ class SpecialSupscription < ActiveRecord::Base
+ self.table_name = "subscriptions"
+ belongs_to :book, class_name: "SpecialBook"
+ end
+
+ def test_association_enum_works_properly
author = SpecialAuthor.create!(name: "Test")
book = SpecialBook.create!(status: "published")
author.book = book
refute_equal 0, SpecialAuthor.joins(:book).where(books: { status: "published" }).count
end
+
+ def test_association_enum_works_properly_with_nested_join
+ author = SpecialAuthor.create!(name: "Test")
+ book = SpecialBook.create!(status: "published")
+ author.book = book
+
+ where_clause = { books: { subscriptions: { subscriber_id: nil } } }
+ assert_nothing_raised do
+ SpecialAuthor.joins(book: :subscription).where.not(where_clause)
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 432c3526a5..38a729d2d4 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -86,6 +86,13 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_nil @member.club
end
+ def test_set_record_after_delete_association
+ @member.club = nil
+ @member.club = clubs(:moustache_club)
+ @member.reload
+ assert_equal clubs(:moustache_club), @member.club
+ end
+
def test_has_one_through_polymorphic
assert_equal clubs(:moustache_club), @member.sponsor_club
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 6fe6ee6783..287b3e9ebc 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -443,7 +443,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert man.equal?(man.interests.first.man), "Two inverses should lead back to the same object that was originally held"
assert man.equal?(man.interests.find(interest.id).man), "Two inversions should lead back to the same object that was originally held"
- assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match before the name is changed"
+ assert_nil man.interests.find(interest.id).man.name, "The name of the man should match before the name is changed"
man.name = "Ben Bitdiddle"
assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the parent name is changed"
man.interests.find(interest.id).man.name = "Alyssa P. Hacker"
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index a223b4338f..26056f6f63 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -220,11 +220,6 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert_equal david.projects, david.projects.scope
end
- test "proxy object is cached" do
- david = developers(:david)
- assert david.projects.equal?(david.projects)
- end
-
test "inverses get set of subsets of the association" do
man = Man.create
man.interests.create
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index 978dd93fa4..1fc63a49d4 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -3,7 +3,7 @@ require "cases/helper"
module ActiveRecord
module AttributeMethods
class ReadTest < ActiveRecord::TestCase
- class FakeColumn < Struct.new(:name)
+ FakeColumn = Struct.new(:name) do
def type; :integer; end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 3dc0c0ce53..4d24a980dc 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -866,6 +866,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert subklass.method_defined?(:id), "subklass is missing id method"
end
+ test "define_attribute_method works with both symbol and string" do
+ klass = Class.new(ActiveRecord::Base)
+
+ assert_nothing_raised { klass.define_attribute_method(:foo) }
+ assert_nothing_raised { klass.define_attribute_method("bar") }
+ end
+
test "read_attribute with nil should not asplode" do
assert_nil Topic.new.read_attribute(nil)
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 6d31b7a091..2203aa1788 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -37,7 +37,7 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
private
def should_be_cool
- unless self.first_name == "cool"
+ unless first_name == "cool"
errors.add :first_name, "not cool"
end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index a611cc208c..979a59f566 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -98,6 +98,13 @@ class BasicsTest < ActiveRecord::TestCase
assert_nil Edge.primary_key
end
+ def test_primary_key_and_references_columns_should_be_identical_type
+ pk = Author.columns_hash["id"]
+ ref = Post.columns_hash["author_id"]
+
+ assert_equal pk.bigint?, ref.bigint?
+ end
+
def test_many_mutations
car = Car.new name: "<3<3<3"
car.engines_count = 0
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index ffd5c1395d..1813534b62 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -13,9 +13,9 @@ require "models/speedometer"
require "models/ship_part"
require "models/treasure"
require "models/developer"
+require "models/post"
require "models/comment"
require "models/rating"
-require "models/post"
class NumericData < ActiveRecord::Base
self.table_name = "numeric_data"
@@ -166,14 +166,14 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_limit_should_apply_before_count
- accounts = Account.limit(3).where("firm_id IS NOT NULL")
+ accounts = Account.limit(4)
assert_equal 3, accounts.count(:firm_id)
assert_equal 3, accounts.select(:firm_id).count
end
def test_limit_should_apply_before_count_arel_attribute
- accounts = Account.limit(3).where("firm_id IS NOT NULL")
+ accounts = Account.limit(4)
firm_id_attribute = Account.arel_table[:firm_id]
assert_equal 3, accounts.count(firm_id_attribute)
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 11ec6fb2c5..b3c86586d0 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -6,10 +6,6 @@ class CallbackDeveloper < ActiveRecord::Base
self.table_name = "developers"
class << self
- def callback_string(callback_method)
- "history << [#{callback_method.to_sym.inspect}, :string]"
- end
-
def callback_proc(callback_method)
Proc.new { |model| model.history << [callback_method, :proc] }
end
@@ -33,7 +29,6 @@ class CallbackDeveloper < ActiveRecord::Base
ActiveRecord::Callbacks::CALLBACKS.each do |callback_method|
next if callback_method.to_s.start_with?("around_")
define_callback_method(callback_method)
- ActiveSupport::Deprecation.silence { send(callback_method, callback_string(callback_method)) }
send(callback_method, callback_proc(callback_method))
send(callback_method, callback_object(callback_method))
send(callback_method) { |model| model.history << [callback_method, :block] }
@@ -44,11 +39,6 @@ class CallbackDeveloper < ActiveRecord::Base
end
end
-class CallbackDeveloperWithFalseValidation < CallbackDeveloper
- before_validation proc { |model| model.history << [:before_validation, :returning_false]; false }
- before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
-end
-
class CallbackDeveloperWithHaltedValidation < CallbackDeveloper
before_validation proc { |model| model.history << [:before_validation, :throwing_abort]; throw(:abort) }
before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
@@ -125,11 +115,11 @@ class ContextualCallbacksDeveloper < ActiveRecord::Base
after_validation :after_validation_on_create_and_update, on: [ :create, :update ]
def before_validation_on_create_and_update
- history << "before_validation_on_#{self.validation_context}".to_sym
+ history << "before_validation_on_#{validation_context}".to_sym
end
def after_validation_on_create_and_update
- history << "after_validation_on_#{self.validation_context}".to_sym
+ history << "after_validation_on_#{validation_context}".to_sym
end
def history
@@ -137,23 +127,6 @@ class ContextualCallbacksDeveloper < ActiveRecord::Base
end
end
-class CallbackCancellationDeveloper < ActiveRecord::Base
- self.table_name = "developers"
-
- attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called
- attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy
-
- before_save { defined?(@cancel_before_save) ? !@cancel_before_save : false }
- before_create { !@cancel_before_create }
- before_update { !@cancel_before_update }
- before_destroy { !@cancel_before_destroy }
-
- after_save { @after_save_called = true }
- after_update { @after_update_called = true }
- after_create { @after_create_called = true }
- after_destroy { @after_destroy_called = true }
-end
-
class CallbackHaltedDeveloper < ActiveRecord::Base
self.table_name = "developers"
@@ -178,7 +151,6 @@ class CallbacksTest < ActiveRecord::TestCase
david = CallbackDeveloper.new
assert_equal [
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
@@ -189,12 +161,10 @@ class CallbacksTest < ActiveRecord::TestCase
david = CallbackDeveloper.find(1)
assert_equal [
[ :after_find, :method ],
- [ :after_find, :string ],
[ :after_find, :proc ],
[ :after_find, :object ],
[ :after_find, :block ],
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
@@ -206,17 +176,14 @@ class CallbacksTest < ActiveRecord::TestCase
david.valid?
assert_equal [
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
[ :before_validation, :method ],
- [ :before_validation, :string ],
[ :before_validation, :proc ],
[ :before_validation, :object ],
[ :before_validation, :block ],
[ :after_validation, :method ],
- [ :after_validation, :string ],
[ :after_validation, :proc ],
[ :after_validation, :object ],
[ :after_validation, :block ],
@@ -228,22 +195,18 @@ class CallbacksTest < ActiveRecord::TestCase
david.valid?
assert_equal [
[ :after_find, :method ],
- [ :after_find, :string ],
[ :after_find, :proc ],
[ :after_find, :object ],
[ :after_find, :block ],
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
[ :before_validation, :method ],
- [ :before_validation, :string ],
[ :before_validation, :proc ],
[ :before_validation, :object ],
[ :before_validation, :block ],
[ :after_validation, :method ],
- [ :after_validation, :string ],
[ :after_validation, :proc ],
[ :after_validation, :object ],
[ :after_validation, :block ],
@@ -254,44 +217,36 @@ class CallbacksTest < ActiveRecord::TestCase
david = CallbackDeveloper.create("name" => "David", "salary" => 1000000)
assert_equal [
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
[ :before_validation, :method ],
- [ :before_validation, :string ],
[ :before_validation, :proc ],
[ :before_validation, :object ],
[ :before_validation, :block ],
[ :after_validation, :method ],
- [ :after_validation, :string ],
[ :after_validation, :proc ],
[ :after_validation, :object ],
[ :after_validation, :block ],
[ :before_save, :method ],
- [ :before_save, :string ],
[ :before_save, :proc ],
[ :before_save, :object ],
[ :before_save, :block ],
[ :before_create, :method ],
- [ :before_create, :string ],
[ :before_create, :proc ],
[ :before_create, :object ],
[ :before_create, :block ],
[ :after_create, :method ],
- [ :after_create, :string ],
[ :after_create, :proc ],
[ :after_create, :object ],
[ :after_create, :block ],
[ :after_save, :method ],
- [ :after_save, :string ],
[ :after_save, :proc ],
[ :after_save, :object ],
[ :after_save, :block ],
[ :after_commit, :block ],
[ :after_commit, :object ],
[ :after_commit, :proc ],
- [ :after_commit, :string ],
[ :after_commit, :method ]
], david.history
end
@@ -323,49 +278,40 @@ class CallbacksTest < ActiveRecord::TestCase
david.save
assert_equal [
[ :after_find, :method ],
- [ :after_find, :string ],
[ :after_find, :proc ],
[ :after_find, :object ],
[ :after_find, :block ],
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
[ :before_validation, :method ],
- [ :before_validation, :string ],
[ :before_validation, :proc ],
[ :before_validation, :object ],
[ :before_validation, :block ],
[ :after_validation, :method ],
- [ :after_validation, :string ],
[ :after_validation, :proc ],
[ :after_validation, :object ],
[ :after_validation, :block ],
[ :before_save, :method ],
- [ :before_save, :string ],
[ :before_save, :proc ],
[ :before_save, :object ],
[ :before_save, :block ],
[ :before_update, :method ],
- [ :before_update, :string ],
[ :before_update, :proc ],
[ :before_update, :object ],
[ :before_update, :block ],
[ :after_update, :method ],
- [ :after_update, :string ],
[ :after_update, :proc ],
[ :after_update, :object ],
[ :after_update, :block ],
[ :after_save, :method ],
- [ :after_save, :string ],
[ :after_save, :proc ],
[ :after_save, :object ],
[ :after_save, :block ],
[ :after_commit, :block ],
[ :after_commit, :object ],
[ :after_commit, :proc ],
- [ :after_commit, :string ],
[ :after_commit, :method ]
], david.history
end
@@ -399,29 +345,24 @@ class CallbacksTest < ActiveRecord::TestCase
david.destroy
assert_equal [
[ :after_find, :method ],
- [ :after_find, :string ],
[ :after_find, :proc ],
[ :after_find, :object ],
[ :after_find, :block ],
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
[ :before_destroy, :method ],
- [ :before_destroy, :string ],
[ :before_destroy, :proc ],
[ :before_destroy, :object ],
[ :before_destroy, :block ],
[ :after_destroy, :method ],
- [ :after_destroy, :string ],
[ :after_destroy, :proc ],
[ :after_destroy, :object ],
[ :after_destroy, :block ],
[ :after_commit, :block ],
[ :after_commit, :object ],
[ :after_commit, :proc ],
- [ :after_commit, :string ],
[ :after_commit, :method ]
], david.history
end
@@ -431,82 +372,16 @@ class CallbacksTest < ActiveRecord::TestCase
CallbackDeveloper.delete(david.id)
assert_equal [
[ :after_find, :method ],
- [ :after_find, :string ],
[ :after_find, :proc ],
[ :after_find, :object ],
[ :after_find, :block ],
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
], david.history
end
- def test_deprecated_before_save_returning_false
- david = ImmutableDeveloper.find(1)
- assert_deprecated do
- assert david.valid?
- assert !david.save
- exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
- assert_equal david, exc.record
- assert_equal "Failed to save the record", exc.message
- end
-
- david = ImmutableDeveloper.find(1)
- david.salary = 10_000_000
- assert !david.valid?
- assert !david.save
- assert_raise(ActiveRecord::RecordInvalid) { david.save! }
-
- someone = CallbackCancellationDeveloper.find(1)
- someone.cancel_before_save = true
- assert_deprecated do
- assert someone.valid?
- assert !someone.save
- end
- assert_save_callbacks_not_called(someone)
- end
-
- def test_deprecated_before_create_returning_false
- someone = CallbackCancellationDeveloper.new
- someone.cancel_before_create = true
- assert_deprecated do
- assert someone.valid?
- assert !someone.save
- end
- assert_save_callbacks_not_called(someone)
- end
-
- def test_deprecated_before_update_returning_false
- someone = CallbackCancellationDeveloper.find(1)
- someone.cancel_before_update = true
- assert_deprecated do
- assert someone.valid?
- assert !someone.save
- end
- assert_save_callbacks_not_called(someone)
- end
-
- def test_deprecated_before_destroy_returning_false
- david = ImmutableDeveloper.find(1)
- assert_deprecated do
- assert !david.destroy
- exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
- assert_equal david, exc.record
- assert_equal "Failed to destroy the record", exc.message
- end
- assert_not_nil ImmutableDeveloper.find_by_id(1)
-
- someone = CallbackCancellationDeveloper.find(1)
- someone.cancel_before_destroy = true
- assert_deprecated do
- assert !someone.destroy
- assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! }
- end
- assert !someone.after_destroy_called
- end
-
def assert_save_callbacks_not_called(someone)
assert !someone.after_save_called
assert !someone.after_create_called
@@ -564,50 +439,19 @@ class CallbacksTest < ActiveRecord::TestCase
assert !someone.after_destroy_called
end
- def test_callback_returning_false
- david = CallbackDeveloperWithFalseValidation.find(1)
- assert_deprecated { david.save }
- assert_equal [
- [ :after_find, :method ],
- [ :after_find, :string ],
- [ :after_find, :proc ],
- [ :after_find, :object ],
- [ :after_find, :block ],
- [ :after_initialize, :method ],
- [ :after_initialize, :string ],
- [ :after_initialize, :proc ],
- [ :after_initialize, :object ],
- [ :after_initialize, :block ],
- [ :before_validation, :method ],
- [ :before_validation, :string ],
- [ :before_validation, :proc ],
- [ :before_validation, :object ],
- [ :before_validation, :block ],
- [ :before_validation, :returning_false ],
- [ :after_rollback, :block ],
- [ :after_rollback, :object ],
- [ :after_rollback, :proc ],
- [ :after_rollback, :string ],
- [ :after_rollback, :method ],
- ], david.history
- end
-
def test_callback_throwing_abort
david = CallbackDeveloperWithHaltedValidation.find(1)
david.save
assert_equal [
[ :after_find, :method ],
- [ :after_find, :string ],
[ :after_find, :proc ],
[ :after_find, :object ],
[ :after_find, :block ],
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
[ :before_validation, :method ],
- [ :before_validation, :string ],
[ :before_validation, :proc ],
[ :before_validation, :object ],
[ :before_validation, :block ],
@@ -615,7 +459,6 @@ class CallbacksTest < ActiveRecord::TestCase
[ :after_rollback, :block ],
[ :after_rollback, :object ],
[ :after_rollback, :proc ],
- [ :after_rollback, :string ],
[ :after_rollback, :method ],
], david.history
end
diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb
index b9c6224425..59ef389326 100644
--- a/activerecord/test/cases/coders/yaml_column_test.rb
+++ b/activerecord/test/cases/coders/yaml_column_test.rb
@@ -5,46 +5,48 @@ module ActiveRecord
module Coders
class YAMLColumnTest < ActiveRecord::TestCase
def test_initialize_takes_class
- coder = YAMLColumn.new(Object)
+ coder = YAMLColumn.new("attr_name", Object)
assert_equal Object, coder.object_class
end
def test_type_mismatch_on_different_classes_on_dump
- coder = YAMLColumn.new(Array)
- assert_raises(SerializationTypeMismatch) do
+ coder = YAMLColumn.new("tags", Array)
+ error = assert_raises(SerializationTypeMismatch) do
coder.dump("a")
end
+ assert_equal %{can't dump `tags`: was supposed to be a Array, but was a String. -- "a"}, error.to_s
end
def test_type_mismatch_on_different_classes
- coder = YAMLColumn.new(Array)
- assert_raises(SerializationTypeMismatch) do
+ coder = YAMLColumn.new("tags", Array)
+ error = assert_raises(SerializationTypeMismatch) do
coder.load "--- foo"
end
+ assert_equal %{can't load `tags`: was supposed to be a Array, but was a String. -- "foo"}, error.to_s
end
def test_nil_is_ok
- coder = YAMLColumn.new
+ coder = YAMLColumn.new("attr_name")
assert_nil coder.load "--- "
end
def test_returns_new_with_different_class
- coder = YAMLColumn.new SerializationTypeMismatch
+ coder = YAMLColumn.new("attr_name", SerializationTypeMismatch)
assert_equal SerializationTypeMismatch, coder.load("--- ").class
end
def test_returns_string_unless_starts_with_dash
- coder = YAMLColumn.new
+ coder = YAMLColumn.new("attr_name")
assert_equal "foo", coder.load("foo")
end
def test_load_handles_other_classes
- coder = YAMLColumn.new
+ coder = YAMLColumn.new("attr_name")
assert_equal [], coder.load([])
end
def test_load_doesnt_swallow_yaml_exceptions
- coder = YAMLColumn.new
+ coder = YAMLColumn.new("attr_name")
bad_yaml = "--- {"
assert_raises(Psych::SyntaxError) do
coder.load(bad_yaml)
@@ -52,7 +54,7 @@ module ActiveRecord
end
def test_load_doesnt_handle_undefined_class_or_module
- coder = YAMLColumn.new
+ coder = YAMLColumn.new("attr_name")
missing_class_yaml = '--- !ruby/object:DoesNotExistAndShouldntEver {}\n'
assert_raises(ArgumentError) do
coder.load(missing_class_yaml)
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index a65bb89052..d230700119 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -14,71 +14,19 @@ module ActiveRecord
# Avoid column definitions in create table statements like:
# `title` varchar(255) DEFAULT NULL
def test_should_not_include_default_clause_when_default_is_null
- column = Column.new("title", nil, SqlTypeMetadata.new(limit: 20))
- column_def = ColumnDefinition.new(
- column.name, "string",
- column.limit, column.precision, column.scale, column.default, column.null)
+ column_def = ColumnDefinition.new("title", "string", limit: 20)
assert_equal "title varchar(20)", @viz.accept(column_def)
end
def test_should_include_default_clause_when_default_is_present
- column = Column.new("title", "Hello", SqlTypeMetadata.new(limit: 20))
- column_def = ColumnDefinition.new(
- column.name, "string",
- column.limit, column.precision, column.scale, column.default, column.null)
+ column_def = ColumnDefinition.new("title", "string", limit: 20, default: "Hello")
assert_equal "title varchar(20) DEFAULT 'Hello'", @viz.accept(column_def)
end
def test_should_specify_not_null_if_null_option_is_false
- type_metadata = SqlTypeMetadata.new(limit: 20)
- column = Column.new("title", "Hello", type_metadata, false)
- column_def = ColumnDefinition.new(
- column.name, "string",
- column.limit, column.precision, column.scale, column.default, column.null)
+ column_def = ColumnDefinition.new("title", "string", limit: 20, default: "Hello", null: false)
assert_equal "title varchar(20) DEFAULT 'Hello' NOT NULL", @viz.accept(column_def)
end
-
- if current_adapter?(:Mysql2Adapter)
- def test_should_set_default_for_mysql_binary_data_types
- type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)")
- binary_column = MySQL::Column.new("title", "a", type)
- assert_equal "a", binary_column.default
-
- type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary")
- varbinary_column = MySQL::Column.new("title", "a", type)
- assert_equal "a", varbinary_column.default
- end
-
- def test_should_be_empty_string_default_for_mysql_binary_data_types
- type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)")
- binary_column = MySQL::Column.new("title", "", type, false)
- assert_equal "", binary_column.default
-
- type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary")
- varbinary_column = MySQL::Column.new("title", "", type, false)
- assert_equal "", varbinary_column.default
- end
-
- def test_should_not_set_default_for_blob_and_text_data_types
- text_type = MySQL::TypeMetadata.new(SqlTypeMetadata.new(type: :text))
-
- text_column = MySQL::Column.new("title", nil, text_type)
- assert_nil text_column.default
-
- not_null_text_column = MySQL::Column.new("title", nil, text_type, false)
- assert_nil not_null_text_column.default
- end
-
- def test_has_default_should_return_false_for_blob_and_text_data_types
- binary_type = SqlTypeMetadata.new(sql_type: "blob")
- blob_column = MySQL::Column.new("title", nil, binary_type)
- assert !blob_column.has_default?
-
- text_type = SqlTypeMetadata.new(type: :text)
- text_column = MySQL::Column.new("title", nil, text_type)
- assert !text_column.has_default?
- end
- end
end
end
end
diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb
index a625299e8d..63f67a9a16 100644
--- a/activerecord/test/cases/comment_test.rb
+++ b/activerecord/test/cases/comment_test.rb
@@ -113,7 +113,7 @@ if ActiveRecord::Base.connection.supports_comments?
assert_match %r[t\.string\s+"content",\s+comment: "Whoa, content describes itself!"], output
assert_match %r[t\.integer\s+"rating",\s+comment: "I am running out of imagination"], output
assert_match %r[t\.index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output
- assert_match %r[t\.index\s+.+\s+name: "idx_obvious",.+\s+comment: "We need to see obvious comments"], output
+ assert_match %r[t\.index\s+.+\s+name: "idx_obvious",\s+comment: "We need to see obvious comments"], output
end
def test_schema_dump_omits_blank_comments
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 2c33bf22ab..681399c8bb 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -20,6 +20,66 @@ module ActiveRecord
@handler.remove_connection("readonly")
end
+ def test_establish_connection_using_3_levels_config
+ previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
+
+ config = {
+ "default_env" => {
+ "readonly" => { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" },
+ "primary" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" }
+ },
+ "another_env" => {
+ "readonly" => { "adapter" => "sqlite3", "database" => "db/bad-readonly.sqlite3" },
+ "primary" => { "adapter" => "sqlite3", "database" => "db/bad-primary.sqlite3" }
+ },
+ "common" => { "adapter" => "sqlite3", "database" => "db/common.sqlite3" }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ @handler.establish_connection(:common)
+ @handler.establish_connection(:primary)
+ @handler.establish_connection(:readonly)
+
+ assert_not_nil pool = @handler.retrieve_connection_pool("readonly")
+ assert_equal "db/readonly.sqlite3", pool.spec.config[:database]
+
+ assert_not_nil pool = @handler.retrieve_connection_pool("primary")
+ assert_equal "db/primary.sqlite3", pool.spec.config[:database]
+
+ assert_not_nil pool = @handler.retrieve_connection_pool("common")
+ assert_equal "db/common.sqlite3", pool.spec.config[:database]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ENV["RAILS_ENV"] = previous_env
+ end
+
+ def test_establish_connection_using_two_level_configurations
+ config = { "development" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" } }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ @handler.establish_connection(:development)
+
+ assert_not_nil pool = @handler.retrieve_connection_pool("development")
+ assert_equal "db/primary.sqlite3", pool.spec.config[:database]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ end
+
+ def test_establish_connection_using_top_level_key_in_two_level_config
+ config = {
+ "development" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" },
+ "development_readonly" => { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ @handler.establish_connection(:development_readonly)
+
+ assert_not_nil pool = @handler.retrieve_connection_pool("development_readonly")
+ assert_equal "db/readonly.sqlite3", pool.spec.config[:database]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ end
+
def test_retrieve_connection
assert @handler.retrieve_connection(@spec_name)
end
@@ -89,6 +149,41 @@ module ActiveRecord
rd.close
end
+ def test_pool_from_any_process_for_uses_most_recent_spec
+ skip unless current_adapter?(:SQLite3Adapter)
+
+ file = Tempfile.new "lol.sqlite3"
+
+ rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
+
+ pid = fork do
+ ActiveRecord::Base.configurations["arunit"]["database"] = file.path
+ ActiveRecord::Base.establish_connection(:arunit)
+
+ pid2 = fork do
+ wr.write ActiveRecord::Base.connection_config[:database]
+ wr.close
+ end
+
+ Process.waitpid pid2
+ end
+
+ Process.waitpid pid
+
+ wr.close
+
+ assert_equal file.path, rd.read
+
+ rd.close
+ ensure
+ if file
+ file.close
+ file.unlink
+ end
+ end
+
def test_a_class_using_custom_pool_and_switching_back_to_primary
klass2 = Class.new(Base) { def self.name; "klass2"; end }
diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
index e2e5445a4e..a348c2d783 100644
--- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
@@ -89,12 +89,20 @@ unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strin
end
def test_decimal_without_scale
- types = %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0) number(2) number(2,0)}
- types.each do |type|
- cast_type = @connection.type_map.lookup(type)
-
- assert_equal :decimal, cast_type.type
- assert_equal 2, cast_type.cast(2.1)
+ if current_adapter?(:OracleAdapter)
+ {
+ decimal: %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0)},
+ integer: %w{number(2) number(2,0)}
+ }
+ else
+ { decimal: %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0) number(2) number(2,0)} }
+ end.each do |expected_type, types|
+ types.each do |type|
+ cast_type = @connection.type_map.lookup(type)
+
+ assert_equal expected_type, cast_type.type
+ assert_equal 2, cast_type.cast(2.1)
+ end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 42600e53fd..afd0ac2dd4 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -341,14 +341,18 @@ module ActiveRecord
end
end
+ class ConnectionTestModel < ActiveRecord::Base
+ end
+
def test_connection_notification_is_called
payloads = []
subscription = ActiveSupport::Notifications.subscribe("!connection.active_record") do |name, started, finished, unique_id, payload|
payloads << payload
end
- ActiveRecord::Base.establish_connection :arunit
+ ConnectionTestModel.establish_connection :arunit
+
assert_equal [:config, :connection_id, :spec_name], payloads[0].keys.sort
- assert_equal "primary", payloads[0][:spec_name]
+ assert_equal "ActiveRecord::ConnectionAdapters::ConnectionPoolTest::ConnectionTestModel", payloads[0][:spec_name]
ensure
ActiveSupport::Notifications.unsubscribe(subscription) if subscription
end
@@ -395,7 +399,7 @@ module ActiveRecord
all_threads_in_new_connection.wait
end
rescue Timeout::Error
- flunk "pool unable to establish connections concurrently or implementation has " <<
+ flunk "pool unable to establish connections concurrently or implementation has " \
"changed, this test then needs to patch a different :new_connection method"
ensure
# clean up the threads
@@ -437,7 +441,7 @@ module ActiveRecord
end
end
- def test_bang_versions_of_disconnect_and_clear_reloadable_connections_if_unable_to_aquire_all_connections_proceed_anyway
+ def test_bang_versions_of_disconnect_and_clear_reloadable_connections_if_unable_to_acquire_all_connections_proceed_anyway
@pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout
[:disconnect!, :clear_reloadable_connections!].each do |group_action_method|
@pool.with_connection do |connection|
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index c7d0ba32b4..46d7526cc0 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -221,6 +221,15 @@ class CounterCacheTest < ActiveRecord::TestCase
assert_equal previously_updated_at, @topic.updated_at
end
+ test "update counters doesn't touch timestamps with touch: []" do
+ @topic.update_column :updated_at, 5.minutes.ago
+ previously_updated_at = @topic.updated_at
+
+ Topic.update_counters(@topic.id, replies_count: -1, touch: [])
+
+ assert_equal previously_updated_at, @topic.updated_at
+ end
+
test "update counters with touch: true" do
assert_touching @topic, :updated_at do
Topic.update_counters(@topic.id, replies_count: -1, touch: true)
diff --git a/activerecord/test/cases/date_time_precision_test.rb b/activerecord/test/cases/date_time_precision_test.rb
index a1c3c5af9c..e4a2f9ee17 100644
--- a/activerecord/test/cases/date_time_precision_test.rb
+++ b/activerecord/test/cases/date_time_precision_test.rb
@@ -73,7 +73,7 @@ if subsecond_precision_supported?
assert_match %r{t\.datetime\s+"updated_at",\s+precision: 6,\s+null: false$}, output
end
- if current_adapter?(:PostgreSQLAdapter)
+ if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter)
def test_datetime_precision_with_zero_should_be_dumped
@connection.create_table(:foos, force: true) do |t|
t.timestamps precision: 0
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 0e58e65a07..a43c06cd6e 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -301,6 +301,14 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal ["arr", "arr matey!"], pirate.catchphrase_change
end
+ def test_virtual_attribute_will_change
+ assert_deprecated do
+ parrot = Parrot.create!(name: "Ruby")
+ parrot.send(:attribute_will_change!, :cancel_save_from_callback)
+ assert parrot.has_changes_to_save?
+ end
+ end
+
def test_association_assignment_changes_foreign_key
pirate = Pirate.create!(catchphrase: "jarl")
pirate.parrot = Parrot.create!(name: "Lorre")
@@ -569,6 +577,7 @@ class DirtyTest < ActiveRecord::TestCase
end
ensure
ActiveRecord::Base.connection.drop_table :testings rescue nil
+ ActiveRecord::Base.clear_cache!
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index ca22fe969c..deec669935 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -117,7 +117,7 @@ class FinderTest < ActiveRecord::TestCase
assert_equal "The Fourth Topic of the day", records[2].title
end
- def test_find_passing_active_record_object_is_deprecated
+ def test_find_passing_active_record_object_is_not_permitted
assert_raises(ArgumentError) do
Topic.find(Topic.last)
end
@@ -167,7 +167,7 @@ class FinderTest < ActiveRecord::TestCase
assert_equal false, relation.exists?(false)
end
- def test_exists_passing_active_record_object_is_not_permited
+ def test_exists_passing_active_record_object_is_not_permitted
assert_raises(ArgumentError) do
Topic.exists?(Topic.new)
end
@@ -339,6 +339,11 @@ class FinderTest < ActiveRecord::TestCase
assert_equal author.post, Post.find_by(author_id: Author.where(id: author))
end
+ def test_find_by_and_where_consistency_with_active_record_instance
+ author = authors(:david)
+ assert_equal Post.where(author_id: author).take, Post.find_by(author_id: author)
+ end
+
def test_take
assert_equal topics(:first), Topic.take
end
@@ -858,13 +863,13 @@ class FinderTest < ActiveRecord::TestCase
end
def test_bind_variables_with_quotes
- Company.create("name" => "37signals' go'es agains")
- assert Company.where(["name = ?", "37signals' go'es agains"]).first
+ Company.create("name" => "37signals' go'es against")
+ assert Company.where(["name = ?", "37signals' go'es against"]).first
end
def test_named_bind_variables_with_quotes
- Company.create("name" => "37signals' go'es agains")
- assert Company.where(["name = :name", { name: "37signals' go'es agains" }]).first
+ Company.create("name" => "37signals' go'es against")
+ assert Company.where(["name = :name", { name: "37signals' go'es against" }]).first
end
def test_named_bind_variables
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index dd48053823..afe761cb55 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -7,6 +7,7 @@ require "models/binary"
require "models/book"
require "models/bulb"
require "models/category"
+require "models/post"
require "models/comment"
require "models/company"
require "models/computer"
@@ -19,7 +20,6 @@ require "models/matey"
require "models/other_dog"
require "models/parrot"
require "models/pirate"
-require "models/post"
require "models/randomly_named_c1"
require "models/reply"
require "models/ship"
@@ -640,6 +640,8 @@ class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase
def test_transaction_created_on_connection_notification
connection = stub(transaction_open?: false)
connection.expects(:begin_transaction).with(joinable: false)
+ pool = connection.stubs(:pool).returns(ActiveRecord::ConnectionAdapters::ConnectionPool.new(ActiveRecord::Base.connection_pool.spec))
+ pool.stubs(:lock_thread=).with(false)
fire_connection_notification(connection)
end
@@ -647,12 +649,16 @@ class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase
# Mocha is not thread-safe so define our own stub to test
connection = Class.new do
attr_accessor :rollback_transaction_called
+ attr_accessor :pool
def transaction_open?; true; end
def begin_transaction(*args); end
def rollback_transaction(*args)
@rollback_transaction_called = true
end
end.new
+ connection.pool = Class.new do
+ def lock_thread=(lock_thread); false; end
+ end.new
fire_connection_notification(connection)
teardown_fixtures
assert(connection.rollback_transaction_called, "Expected <mock connection>#rollback_transaction to be called but was not")
@@ -1012,7 +1018,7 @@ end
class FixtureClassNamesTest < ActiveRecord::TestCase
def setup
- @saved_cache = self.fixture_class_names.dup
+ @saved_cache = fixture_class_names.dup
end
def teardown
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 1ddcbf0e4f..5a3b8e3fb5 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -6,7 +6,6 @@ require "active_record"
require "cases/test_case"
require "active_support/dependencies"
require "active_support/logger"
-require "active_support/core_ext/string/strip"
require "support/config"
require "support/connection"
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index 155e858822..5a1d066aef 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -101,6 +101,17 @@ class JsonSerializationTest < ActiveRecord::TestCase
assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json
end
+ def test_uses_serializable_hash_with_frozen_hash
+ def @contact.serializable_hash(options = nil)
+ super({ only: %w(name) }.freeze)
+ end
+
+ json = @contact.to_json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_no_match %r{awesome}, json
+ assert_no_match %r{age}, json
+ end
+
def test_uses_serializable_hash_with_only_option
def @contact.serializable_hash(options = nil)
super(only: %w(name))
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 95fb670dac..23095618a4 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -18,7 +18,7 @@ class LockWithoutDefault < ActiveRecord::Base; end
class LockWithCustomColumnWithoutDefault < ActiveRecord::Base
self.table_name = :lock_without_defaults_cust
- self.column_defaults # to test @column_defaults caching.
+ column_defaults # to test @column_defaults caching.
self.locking_column = :custom_lock_version
end
@@ -536,7 +536,10 @@ unless in_memory_db?
Person.transaction do
person = Person.find 1
old, person.first_name = person.first_name, "fooman"
- person.lock!
+ # Locking a dirty record is deprecated
+ assert_deprecated do
+ person.lock!
+ end
assert_equal old, person.first_name
end
end
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index 8a4242cf1d..ec817a579b 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -101,12 +101,7 @@ module ActiveRecord
def test_primary_key_creates_primary_key_column
with_change_table do |t|
- if current_adapter?(:Mysql2Adapter)
- @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, { first: true, auto_increment: true, limit: 8, primary_key: true }]
- else
- @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, primary_key: true, first: true]
- end
-
+ @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, primary_key: true, first: true]
t.primary_key :id, first: true
end
end
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index e5a7412bc3..7a80bfb899 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "support/schema_dumping_helper"
module ActiveRecord
class Migration
@@ -103,10 +104,118 @@ module ActiveRecord
end
def test_legacy_migrations_raises_exception_when_inherited
- assert_raises(StandardError) do
- Class.new(ActiveRecord::Migration)
+ e = assert_raises(StandardError) do
+ class_eval("class LegacyMigration < ActiveRecord::Migration; end")
end
+ assert_match(/LegacyMigration < ActiveRecord::Migration\[4\.2\]/, e.message)
end
end
end
end
+
+class LegacyPrimaryKeyTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ self.use_transactional_tests = false
+
+ class LegacyPrimaryKey < ActiveRecord::Base
+ end
+
+ def setup
+ @migration = nil
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+ end
+
+ def teardown
+ @migration.migrate(:down) if @migration
+ ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ LegacyPrimaryKey.reset_column_information
+ end
+
+ def test_legacy_primary_key_should_be_auto_incremented
+ @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ def change
+ create_table :legacy_primary_keys do |t|
+ t.references :legacy_ref
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ legacy_pk = LegacyPrimaryKey.columns_hash["id"]
+ assert_not legacy_pk.bigint?
+ assert_not legacy_pk.null
+
+ legacy_ref = LegacyPrimaryKey.columns_hash["legacy_ref_id"]
+ assert_not legacy_ref.bigint?
+
+ record1 = LegacyPrimaryKey.create!
+ assert_not_nil record1.id
+
+ record1.destroy
+
+ record2 = LegacyPrimaryKey.create!
+ assert_not_nil record2.id
+ assert_operator record2.id, :>, record1.id
+ end
+
+ def test_legacy_integer_primary_key_should_not_be_auto_incremented
+ skip if current_adapter?(:SQLite3Adapter)
+
+ @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ def change
+ create_table :legacy_primary_keys, id: :integer do |t|
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ assert_raises(ActiveRecord::NotNullViolation) do
+ LegacyPrimaryKey.create!
+ end
+
+ schema = dump_table_schema "legacy_primary_keys"
+ assert_match %r{create_table "legacy_primary_keys", id: :integer, default: nil}, schema
+ end
+
+ if current_adapter?(:Mysql2Adapter)
+ def test_legacy_bigint_primary_key_should_be_auto_incremented
+ @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ def change
+ create_table :legacy_primary_keys, id: :bigint
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ legacy_pk = LegacyPrimaryKey.columns_hash["id"]
+ assert legacy_pk.bigint?
+ assert legacy_pk.auto_increment?
+
+ schema = dump_table_schema "legacy_primary_keys"
+ assert_match %r{create_table "legacy_primary_keys", (?!id: :bigint, default: nil)}, schema
+ end
+ else
+ def test_legacy_bigint_primary_key_should_not_be_auto_incremented
+ @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ def change
+ create_table :legacy_primary_keys, id: :bigint do |t|
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ assert_raises(ActiveRecord::NotNullViolation) do
+ LegacyPrimaryKey.create!
+ end
+
+ schema = dump_table_schema "legacy_primary_keys"
+ assert_match %r{create_table "legacy_primary_keys", id: :bigint, default: nil}, schema
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index 9be6667aa1..7762d37915 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -1,12 +1,30 @@
require "cases/helper"
-require "support/ddl_helper"
require "support/schema_dumping_helper"
+if ActiveRecord::Base.connection.supports_foreign_keys_in_create?
+ module ActiveRecord
+ class Migration
+ class ForeignKeyInCreateTest < ActiveRecord::TestCase
+ def test_foreign_keys
+ foreign_keys = ActiveRecord::Base.connection.foreign_keys("fk_test_has_fk")
+ assert_equal 1, foreign_keys.size
+
+ fk = foreign_keys.first
+ assert_equal "fk_test_has_fk", fk.from_table
+ assert_equal "fk_test_has_pk", fk.to_table
+ assert_equal "fk_id", fk.column
+ assert_equal "pk_id", fk.primary_key
+ assert_equal "fk_name", fk.name unless current_adapter?(:SQLite3Adapter)
+ end
+ end
+ end
+ end
+end
+
if ActiveRecord::Base.connection.supports_foreign_keys?
module ActiveRecord
class Migration
class ForeignKeyTest < ActiveRecord::TestCase
- include DdlHelper
include SchemaDumpingHelper
include ActiveSupport::Testing::Stream
@@ -29,10 +47,8 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
teardown do
- if defined?(@connection)
- @connection.drop_table "astronauts", if_exists: true
- @connection.drop_table "rockets", if_exists: true
- end
+ @connection.drop_table "astronauts", if_exists: true
+ @connection.drop_table "rockets", if_exists: true
end
def test_foreign_keys
@@ -76,20 +92,23 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
def test_add_foreign_key_with_non_standard_primary_key
- with_example_table @connection, "space_shuttles", "pk BIGINT PRIMARY KEY" do
- @connection.add_foreign_key(:astronauts, :space_shuttles,
- column: "rocket_id", primary_key: "pk", name: "custom_pk")
+ @connection.create_table :space_shuttles, id: false, force: true do |t|
+ t.bigint :pk, primary_key: true
+ end
- foreign_keys = @connection.foreign_keys("astronauts")
- assert_equal 1, foreign_keys.size
+ @connection.add_foreign_key(:astronauts, :space_shuttles,
+ column: "rocket_id", primary_key: "pk", name: "custom_pk")
- fk = foreign_keys.first
- assert_equal "astronauts", fk.from_table
- assert_equal "space_shuttles", fk.to_table
- assert_equal "pk", fk.primary_key
+ foreign_keys = @connection.foreign_keys("astronauts")
+ assert_equal 1, foreign_keys.size
- @connection.remove_foreign_key :astronauts, name: "custom_pk"
- end
+ fk = foreign_keys.first
+ assert_equal "astronauts", fk.from_table
+ assert_equal "space_shuttles", fk.to_table
+ assert_equal "pk", fk.primary_key
+ ensure
+ @connection.remove_foreign_key :astronauts, name: "custom_pk"
+ @connection.drop_table :space_shuttles
end
def test_add_on_delete_restrict_foreign_key
@@ -229,7 +248,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
create_table("cities") { |t| }
create_table("houses") do |t|
- t.column :city_id, :bigint
+ t.references :city
end
add_foreign_key :houses, :cities, column: "city_id"
@@ -261,7 +280,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
create_table(:schools)
create_table(:classes) do |t|
- t.column :school_id, :bigint
+ t.references :school
end
add_foreign_key :classes, :schools
end
@@ -305,9 +324,11 @@ else
@connection.remove_foreign_key :clubs, :categories
end
- def test_foreign_keys_should_raise_not_implemented
- assert_raises NotImplementedError do
- @connection.foreign_keys("clubs")
+ unless current_adapter?(:SQLite3Adapter)
+ def test_foreign_keys_should_raise_not_implemented
+ assert_raises NotImplementedError do
+ @connection.foreign_keys("clubs")
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index 0f975026b8..f10fcf1398 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -31,9 +31,10 @@ module ActiveRecord
connection.add_index(table_name, [:foo], name: "old_idx")
connection.rename_index(table_name, "old_idx", "new_idx")
- # if the adapter doesn't support the indexes call, pick defaults that let the test pass
- assert_not connection.index_name_exists?(table_name, "old_idx", false)
- assert connection.index_name_exists?(table_name, "new_idx", true)
+ assert_deprecated do
+ assert_not connection.index_name_exists?(table_name, "old_idx", false)
+ assert connection.index_name_exists?(table_name, "new_idx", true)
+ end
end
def test_rename_index_too_long
@@ -45,8 +46,7 @@ module ActiveRecord
}
assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message)
- # if the adapter doesn't support the indexes call, pick defaults that let the test pass
- assert connection.index_name_exists?(table_name, "old_idx", false)
+ assert connection.index_name_exists?(table_name, "old_idx")
end
def test_double_add_index
@@ -63,7 +63,7 @@ module ActiveRecord
def test_add_index_works_with_long_index_names
connection.add_index(table_name, "foo", name: good_index_name)
- assert connection.index_name_exists?(table_name, good_index_name, false)
+ assert connection.index_name_exists?(table_name, good_index_name)
connection.remove_index(table_name, name: good_index_name)
end
@@ -75,7 +75,7 @@ module ActiveRecord
}
assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message)
- assert_not connection.index_name_exists?(table_name, too_long_index_name, false)
+ assert_not connection.index_name_exists?(table_name, too_long_index_name)
connection.add_index(table_name, "foo", name: good_index_name)
end
@@ -83,7 +83,7 @@ module ActiveRecord
good_index_name = "x" * connection.index_name_length
connection.add_index(table_name, "foo", name: good_index_name, internal: true)
- assert connection.index_name_exists?(table_name, good_index_name, false)
+ assert connection.index_name_exists?(table_name, good_index_name)
connection.remove_index(table_name, name: good_index_name)
end
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index 4957ab8b3d..9418995ea0 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -1,9 +1,9 @@
require "cases/helper"
-if ActiveRecord::Base.connection.supports_foreign_keys?
+if ActiveRecord::Base.connection.supports_foreign_keys_in_create?
module ActiveRecord
class Migration
- class ReferencesForeignKeyTest < ActiveRecord::TestCase
+ class ReferencesForeignKeyInCreateTest < ActiveRecord::TestCase
setup do
@connection = ActiveRecord::Base.connection
@connection.create_table(:testing_parents, force: true)
@@ -42,8 +42,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
test "options hash can be passed" do
@connection.change_table :testing_parents do |t|
- t.bigint :other_id
- t.index :other_id, unique: true
+ t.references :other, index: { unique: true }
end
@connection.create_table :testings do |t|
t.references :testing_parent, foreign_key: { primary_key: :other_id }
@@ -61,6 +60,24 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
assert_equal([["testings", "testing_parents", "parent_id"]],
fks.map { |fk| [fk.from_table, fk.to_table, fk.column] })
end
+ end
+ end
+ end
+end
+
+if ActiveRecord::Base.connection.supports_foreign_keys?
+ module ActiveRecord
+ class Migration
+ class ReferencesForeignKeyTest < ActiveRecord::TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table(:testing_parents, force: true)
+ end
+
+ teardown do
+ @connection.drop_table "testings", if_exists: true
+ @connection.drop_table "testing_parents", if_exists: true
+ end
test "foreign keys cannot be added to polymorphic relations when creating the table" do
@connection.create_table :testings do |t|
@@ -92,8 +109,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
test "foreign keys accept options when changing the table" do
@connection.change_table :testing_parents do |t|
- t.bigint :other_id
- t.index :other_id, unique: true
+ t.references :other, index: { unique: true }
end
@connection.create_table :testings
@connection.change_table :testings do |t|
@@ -177,18 +193,15 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
test "multiple foreign keys can be added to the same table" do
@connection.create_table :testings do |t|
- t.bigint :col_1
- t.bigint :col_2
-
- t.foreign_key :testing_parents, column: :col_1
- t.foreign_key :testing_parents, column: :col_2
+ t.references :parent1, foreign_key: { to_table: :testing_parents }
+ t.references :parent2, foreign_key: { to_table: :testing_parents }
end
- fks = @connection.foreign_keys("testings")
+ fks = @connection.foreign_keys("testings").sort_by(&:column)
fk_definitions = fks.map { |fk| [fk.from_table, fk.to_table, fk.column] }
- assert_equal([["testings", "testing_parents", "col_1"],
- ["testings", "testing_parents", "col_2"]], fk_definitions)
+ assert_equal([["testings", "testing_parents", "parent1_id"],
+ ["testings", "testing_parents", "parent2_id"]], fk_definitions)
end
end
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 082cfd3242..de16ecf442 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -43,10 +43,10 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Base.table_name_prefix = ""
ActiveRecord::Base.table_name_suffix = ""
- ActiveRecord::Base.connection.initialize_schema_migrations_table
- ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}"
+ ActiveRecord::SchemaMigration.create_table
+ ActiveRecord::SchemaMigration.delete_all
- %w(things awesome_things prefix_things_suffix p_awesome_things_s ).each do |table|
+ %w(things awesome_things prefix_things_suffix p_awesome_things_s).each do |table|
Thing.connection.drop_table(table) rescue nil
end
Thing.reset_column_information
@@ -399,6 +399,7 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migrator.migrations_paths = old_path
ENV["RAILS_ENV"] = original_rails_env
ENV["RACK_ENV"] = original_rack_env
+ ActiveRecord::Migrator.up(migrations_path)
end
def test_migration_sets_internal_metadata_even_when_fully_migrated
@@ -425,6 +426,7 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migrator.migrations_paths = old_path
ENV["RAILS_ENV"] = original_rails_env
ENV["RACK_ENV"] = original_rack_env
+ ActiveRecord::Migrator.up(migrations_path)
end
def test_internal_metadata_stores_environment_when_other_data_exists
@@ -887,7 +889,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
assert_equal :datetime, column(:birthdate).type
end
- protected
+ private
def with_bulk_change_table
# Reset columns/indexes cache as we're changing the table
@@ -914,7 +916,6 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
@indexes ||= Person.connection.indexes("delete_me")
end
end # AlterTableMigrationsTest
-
end
class CopyMigrationsTest < ActiveRecord::TestCase
@@ -1132,4 +1133,13 @@ class CopyMigrationsTest < ActiveRecord::TestCase
def test_unknown_migration_version_should_raise_an_argument_error
assert_raise(ArgumentError) { ActiveRecord::Migration[1.0] }
end
+
+ def test_deprecate_initialize_internal_tables
+ assert_deprecated { ActiveRecord::Base.connection.initialize_schema_migrations_table }
+ assert_deprecated { ActiveRecord::Base.connection.initialize_internal_metadata_table }
+ end
+
+ def test_deprecate_migration_keys
+ assert_deprecated { ActiveRecord::Base.connection.migration_keys }
+ end
end
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index bb9835394b..20d70b75ac 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -45,10 +45,11 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_migrator_with_duplicate_names
- assert_raises(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do
+ e = assert_raises(ActiveRecord::DuplicateMigrationNameError) do
list = [ActiveRecord::Migration.new("Chunky"), ActiveRecord::Migration.new("Chunky")]
ActiveRecord::Migrator.new(:up, list)
end
+ assert_match(/Multiple migrations have the name Chunky/, e.message)
end
def test_migrator_with_duplicate_versions
@@ -290,6 +291,27 @@ class MigratorTest < ActiveRecord::TestCase
assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
end
+ def test_migrator_output_when_running_multiple_migrations
+ _, migrator = migrator_class(3)
+
+ result = migrator.migrate("valid")
+ assert_equal(3, result.count)
+
+ # Nothing migrated from duplicate run
+ result = migrator.migrate("valid")
+ assert_equal(0, result.count)
+
+ result = migrator.rollback("valid")
+ assert_equal(1, result.count)
+ end
+
+ def test_migrator_output_when_running_single_migration
+ _, migrator = migrator_class(1)
+ result = migrator.run(:up, "valid", 1)
+
+ assert_equal(1, result.version)
+ end
+
def test_migrator_rollback
_, migrator = migrator_class(3)
diff --git a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
index 350a966d40..b9d2acbed2 100644
--- a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
+++ b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
@@ -120,14 +120,14 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
assert_assignment_affects_records_in_target(:birds_with_add)
end
- test("Assignment updates records in target when not loaded" +
+ test("Assignment updates records in target when not loaded" \
" and callback loads target") do
assert_not @pirate.birds_with_add_load.loaded?
@pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes
assert_assignment_affects_records_in_target(:birds_with_add_load)
end
- test("Assignment updates records in target when loaded" +
+ test("Assignment updates records in target when loaded" \
" and callback loads target") do
@pirate.birds_with_add_load.load_target
@pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 3f1da82cb4..5b7e2fd008 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -139,6 +139,14 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal initial_credit + 2, a1.reload.credit_limit
end
+ def test_increment_updates_timestamps
+ topic = topics(:first)
+ topic.update_columns(updated_at: 5.minutes.ago)
+ previous_updated_at = topic.updated_at
+ topic.increment!(:replies_count, touch: true)
+ assert_operator previous_updated_at, :<, topic.reload.updated_at
+ end
+
def test_destroy_all
conditions = "author_name = 'Mary'"
topics_by_mary = Topic.all.merge!(where: conditions, order: "id").to_a
@@ -230,6 +238,14 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal 41, accounts(:signals37, :reload).credit_limit
end
+ def test_decrement_updates_timestamps
+ topic = topics(:first)
+ topic.update_columns(updated_at: 5.minutes.ago)
+ previous_updated_at = topic.updated_at
+ topic.decrement!(:replies_count, touch: true)
+ assert_operator previous_updated_at, :<, topic.reload.updated_at
+ end
+
def test_create
topic = Topic.new
topic.title = "New Topic"
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 1d72899102..03c8644229 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -7,6 +7,7 @@ require "models/movie"
require "models/keyboard"
require "models/mixed_case_monkey"
require "models/dashboard"
+require "models/non_primary_key"
class PrimaryKeysTest < ActiveRecord::TestCase
fixtures :topics, :subscribers, :movies, :mixed_case_monkeys
@@ -89,6 +90,12 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_equal("John Doe", subscriberReloaded.name)
end
+ def test_id_column_that_is_not_primary_key
+ NonPrimaryKey.create!(id: 100)
+ actual = NonPrimaryKey.find_by(id: 100)
+ assert_match %r{<NonPrimaryKey id: 100}, actual.inspect
+ end
+
def test_find_with_more_than_one_string_key
assert_equal 2, Subscriber.find(subscribers(:first).nick, subscribers(:second).nick).length
end
@@ -113,38 +120,45 @@ class PrimaryKeysTest < ActiveRecord::TestCase
def test_delete_should_quote_pkey
assert_nothing_raised { MixedCaseMonkey.delete(1) }
end
+
def test_update_counters_should_quote_pkey_and_quote_counter_columns
assert_nothing_raised { MixedCaseMonkey.update_counters(1, fleaCount: 99) }
end
+
def test_find_with_one_id_should_quote_pkey
assert_nothing_raised { MixedCaseMonkey.find(1) }
end
+
def test_find_with_multiple_ids_should_quote_pkey
assert_nothing_raised { MixedCaseMonkey.find([1, 2]) }
end
+
def test_instance_update_should_quote_pkey
assert_nothing_raised { MixedCaseMonkey.find(1).save }
end
+
def test_instance_destroy_should_quote_pkey
assert_nothing_raised { MixedCaseMonkey.find(1).destroy }
end
- if ActiveRecord::Base.connection.supports_primary_key?
- def test_primary_key_returns_value_if_it_exists
- klass = Class.new(ActiveRecord::Base) do
- self.table_name = "developers"
- end
+ def test_deprecate_supports_primary_key
+ assert_deprecated { ActiveRecord::Base.connection.supports_primary_key? }
+ end
- assert_equal "id", klass.primary_key
+ def test_primary_key_returns_value_if_it_exists
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "developers"
end
- def test_primary_key_returns_nil_if_it_does_not_exist
- klass = Class.new(ActiveRecord::Base) do
- self.table_name = "developers_projects"
- end
+ assert_equal "id", klass.primary_key
+ end
- assert_nil klass.primary_key
+ def test_primary_key_returns_nil_if_it_does_not_exist
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "developers_projects"
end
+
+ assert_nil klass.primary_key
end
def test_quoted_primary_key_after_set_primary_key
@@ -224,13 +238,13 @@ class PrimaryKeyWithAutoIncrementTest < ActiveRecord::TestCase
@connection.drop_table(:auto_increments, if_exists: true)
end
- def test_primary_key_with_auto_increment
- @connection.create_table(:auto_increments, id: :integer, auto_increment: true, force: true)
+ def test_primary_key_with_integer
+ @connection.create_table(:auto_increments, id: :integer, force: true)
assert_auto_incremented
end
- def test_primary_key_with_auto_increment_and_bigint
- @connection.create_table(:auto_increments, id: :bigint, auto_increment: true, force: true)
+ def test_primary_key_with_bigint
+ @connection.create_table(:auto_increments, id: :bigint, force: true)
assert_auto_incremented
end
@@ -291,6 +305,10 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
t.string :region
t.integer :code
end
+ @connection.create_table(:barcodes_reverse, primary_key: ["code", "region"], force: true) do |t|
+ t.string :region
+ t.integer :code
+ end
end
def teardown
@@ -301,6 +319,11 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
assert_equal ["region", "code"], @connection.primary_keys("barcodes")
end
+ def test_composite_primary_key_out_of_order
+ skip if current_adapter?(:SQLite3Adapter)
+ assert_equal ["code", "region"], @connection.primary_keys("barcodes_reverse")
+ end
+
def test_primary_key_issues_warning
model = Class.new(ActiveRecord::Base) do
def self.table_name
@@ -313,76 +336,106 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
assert_match(/WARNING: Active Record does not support composite primary key\./, warning)
end
- def test_collectly_dump_composite_primary_key
+ def test_dumping_composite_primary_key
schema = dump_table_schema "barcodes"
assert_match %r{create_table "barcodes", primary_key: \["region", "code"\]}, schema
end
+
+ def test_dumping_composite_primary_key_out_of_order
+ skip if current_adapter?(:SQLite3Adapter)
+ schema = dump_table_schema "barcodes_reverse"
+ assert_match %r{create_table "barcodes_reverse", primary_key: \["code", "region"\]}, schema
+ end
end
-if current_adapter?(:Mysql2Adapter)
- class PrimaryKeyIntegerNilDefaultTest < ActiveRecord::TestCase
- include SchemaDumpingHelper
+class PrimaryKeyIntegerNilDefaultTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
- self.use_transactional_tests = false
+ self.use_transactional_tests = false
- def setup
- @connection = ActiveRecord::Base.connection
- @connection.create_table(:int_defaults, id: :integer, default: nil, force: true)
- end
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
- def teardown
- @connection.drop_table :int_defaults, if_exists: true
- end
+ def teardown
+ @connection.drop_table :int_defaults, if_exists: true
+ end
- test "primary key with integer allows default override via nil" do
- column = @connection.columns(:int_defaults).find { |c| c.name == "id" }
- assert_equal :integer, column.type
- assert_not column.auto_increment?
- end
+ def test_schema_dump_primary_key_integer_with_default_nil
+ skip if current_adapter?(:SQLite3Adapter)
+ @connection.create_table(:int_defaults, id: :integer, default: nil, force: true)
+ schema = dump_table_schema "int_defaults"
+ assert_match %r{create_table "int_defaults", id: :integer, default: nil}, schema
+ end
- test "schema dump primary key with int default nil" do
- schema = dump_table_schema "int_defaults"
- assert_match %r{create_table "int_defaults", id: :integer, default: nil}, schema
- end
+ def test_schema_dump_primary_key_bigint_with_default_nil
+ @connection.create_table(:int_defaults, id: :bigint, default: nil, force: true)
+ schema = dump_table_schema "int_defaults"
+ assert_match %r{create_table "int_defaults", id: :bigint, default: nil}, schema
end
end
-class PrimaryKeyIntegerTest < ActiveRecord::TestCase
- include SchemaDumpingHelper
+if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
+ class PrimaryKeyIntegerTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
- self.use_transactional_tests = false
+ self.use_transactional_tests = false
- class Widget < ActiveRecord::Base
- end
+ class Widget < ActiveRecord::Base
+ end
- setup do
- @connection = ActiveRecord::Base.connection
- @connection.create_table(:widgets, force: true)
- end
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @pk_type = current_adapter?(:PostgreSQLAdapter) ? :serial : :integer
+ end
- teardown do
- @connection.drop_table :widgets, if_exists: true
- Widget.reset_column_information
- end
+ teardown do
+ @connection.drop_table :widgets, if_exists: true
+ end
- if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
- test "schema dump primary key with bigserial" do
- schema = dump_table_schema "widgets"
- assert_match %r{create_table "widgets", force: :cascade}, schema
+ test "primary key column type with serial/integer" do
+ @connection.create_table(:widgets, id: @pk_type, force: true)
+ column = @connection.columns(:widgets).find { |c| c.name == "id" }
+ assert_equal :integer, column.type
+ assert_not column.bigint?
end
- end
- test "primary key column type" do
- column_type = Widget.type_for_attribute(Widget.primary_key)
- assert_equal :integer, column_type.type
+ test "primary key with serial/integer are automatically numbered" do
+ @connection.create_table(:widgets, id: @pk_type, force: true)
+ widget = Widget.create!
+ assert_not_nil widget.id
+ end
- if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
- assert_equal 8, column_type.limit
+ test "schema dump primary key with serial/integer" do
+ @connection.create_table(:widgets, id: @pk_type, force: true)
+ schema = dump_table_schema "widgets"
+ assert_match %r{create_table "widgets", id: :#{@pk_type}, force: :cascade}, schema
end
if current_adapter?(:Mysql2Adapter)
- column = @connection.columns(:widgets).find { |c| c.name == "id" }
- assert column.auto_increment?
+ test "primary key column type with options" do
+ @connection.create_table(:widgets, id: :primary_key, limit: 4, unsigned: true, force: true)
+ column = @connection.columns(:widgets).find { |c| c.name == "id" }
+ assert column.auto_increment?
+ assert_equal :integer, column.type
+ assert_not column.bigint?
+ assert column.unsigned?
+
+ schema = dump_table_schema "widgets"
+ assert_match %r{create_table "widgets", id: :integer, unsigned: true, force: :cascade}, schema
+ end
+
+ test "bigint primary key with unsigned" do
+ @connection.create_table(:widgets, id: :bigint, unsigned: true, force: true)
+ column = @connection.columns(:widgets).find { |c| c.name == "id" }
+ assert column.auto_increment?
+ assert_equal :integer, column.type
+ assert column.bigint?
+ assert column.unsigned?
+
+ schema = dump_table_schema "widgets"
+ assert_match %r{create_table "widgets", id: :bigint, unsigned: true, force: :cascade}, schema
+ end
end
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 4a49bfe9b1..494663eb04 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -50,32 +50,36 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_cache :off
end
- def test_query_cache_across_threads
- ActiveRecord::Base.connection_pool.connections.each do |conn|
- assert_cache :off, conn
- end
-
- assert !ActiveRecord::Base.connection.nil?
- assert_cache :off
-
- middleware {
- assert_cache :clean
-
- Task.find 1
- assert_cache :dirty
+ 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
- thread_1_connection = ActiveRecord::Base.connection
- ActiveRecord::Base.clear_active_connections!
- assert_cache :off, thread_1_connection
+ yield
+ ensure
+ ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = old_pool
+ end
- started = Concurrent::Event.new
- checked = Concurrent::Event.new
+ def test_query_cache_across_threads
+ with_temporary_connection_pool do
+ begin
+ if in_memory_db?
+ # Separate connections to an in-memory database create an entirely new database,
+ # with an empty schema etc, so we just stub out this schema on the fly.
+ ActiveRecord::Base.connection_pool.with_connection do |connection|
+ connection.create_table :tasks do |t|
+ t.datetime :starting
+ t.datetime :ending
+ end
+ end
+ ActiveRecord::FixtureSet.create_fixtures(self.class.fixture_path, ["tasks"], {}, ActiveRecord::Base)
+ end
- thread_2_connection = nil
- thread = Thread.new {
- thread_2_connection = ActiveRecord::Base.connection
+ ActiveRecord::Base.connection_pool.connections.each do |conn|
+ assert_cache :off, conn
+ end
- assert_equal thread_2_connection, thread_1_connection
+ assert !ActiveRecord::Base.connection.nil?
assert_cache :off
middleware {
@@ -84,29 +88,51 @@ class QueryCacheTest < ActiveRecord::TestCase
Task.find 1
assert_cache :dirty
- started.set
- checked.wait
-
+ thread_1_connection = ActiveRecord::Base.connection
ActiveRecord::Base.clear_active_connections!
- }.call({})
- }
+ assert_cache :off, thread_1_connection
+
+ started = Concurrent::Event.new
+ checked = Concurrent::Event.new
+
+ thread_2_connection = nil
+ thread = Thread.new {
+ thread_2_connection = ActiveRecord::Base.connection
+
+ assert_equal thread_2_connection, thread_1_connection
+ assert_cache :off
- started.wait
+ middleware {
+ assert_cache :clean
- thread_1_connection = ActiveRecord::Base.connection
- assert_not_equal thread_1_connection, thread_2_connection
- assert_cache :dirty, thread_2_connection
- checked.set
- thread.join
+ Task.find 1
+ assert_cache :dirty
- assert_cache :off, thread_2_connection
- }.call({})
+ started.set
+ checked.wait
- ActiveRecord::Base.connection_pool.connections.each do |conn|
- assert_cache :off, conn
+ ActiveRecord::Base.clear_active_connections!
+ }.call({})
+ }
+
+ started.wait
+
+ thread_1_connection = ActiveRecord::Base.connection
+ assert_not_equal thread_1_connection, thread_2_connection
+ assert_cache :dirty, thread_2_connection
+ checked.set
+ thread.join
+
+ assert_cache :off, thread_2_connection
+ }.call({})
+
+ ActiveRecord::Base.connection_pool.connections.each do |conn|
+ assert_cache :off, conn
+ end
+ ensure
+ ActiveRecord::Base.clear_all_connections!
+ end
end
- ensure
- ActiveRecord::Base.clear_all_connections!
end
def test_middleware_delegates
@@ -260,19 +286,51 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_cache_is_not_available_when_using_a_not_connected_connection
- spec_name = Task.connection_specification_name
- conf = ActiveRecord::Base.configurations["arunit"].merge("name" => "test2")
- ActiveRecord::Base.connection_handler.establish_connection(conf)
- Task.connection_specification_name = "test2"
- refute Task.connected?
+ with_temporary_connection_pool do
+ spec_name = Task.connection_specification_name
+ conf = ActiveRecord::Base.configurations["arunit"].merge("name" => "test2")
+ ActiveRecord::Base.connection_handler.establish_connection(conf)
+ Task.connection_specification_name = "test2"
+ refute Task.connected?
- Task.cache do
- Task.connection # warmup postgresql connection setup queries
- assert_queries(2) { Task.find(1); Task.find(1) }
+ Task.cache do
+ begin
+ if in_memory_db?
+ Task.connection.create_table :tasks do |t|
+ t.datetime :starting
+ t.datetime :ending
+ end
+ ActiveRecord::FixtureSet.create_fixtures(self.class.fixture_path, ["tasks"], {}, ActiveRecord::Base)
+ end
+ Task.connection # warmup postgresql connection setup queries
+ assert_queries(2) { Task.find(1); Task.find(1) }
+ ensure
+ ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name)
+ Task.connection_specification_name = spec_name
+ end
+ end
+ end
+ end
+
+ def test_query_cache_executes_new_queries_within_block
+ ActiveRecord::Base.connection.enable_query_cache!
+
+ # Warm up the cache by running the query
+ assert_queries(1) do
+ assert_equal 0, Post.where(title: "test").to_a.count
+ end
+
+ # Check that if the same query is run again, no queries are executed
+ assert_queries(0) do
+ assert_equal 0, Post.where(title: "test").to_a.count
+ end
+
+ ActiveRecord::Base.connection.uncached do
+ # Check that new query is executed, avoiding the cache
+ assert_queries(1) do
+ assert_equal 0, Post.where(title: "test").to_a.count
+ end
end
- ensure
- ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name)
- Task.connection_specification_name = spec_name
end
def test_query_cache_doesnt_leak_cached_results_of_rolled_back_queries
@@ -328,37 +386,44 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_query_cache_does_not_establish_connection_if_unconnected
- ActiveRecord::Base.clear_active_connections!
- refute ActiveRecord::Base.connection_handler.active_connections? # sanity check
+ with_temporary_connection_pool do
+ ActiveRecord::Base.clear_active_connections!
+ refute ActiveRecord::Base.connection_handler.active_connections? # sanity check
- middleware {
- refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in setup"
- }.call({})
+ middleware {
+ refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in setup"
+ }.call({})
- refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in cleanup"
+ refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in cleanup"
+ end
end
def test_query_cache_is_enabled_on_connections_established_after_middleware_runs
- ActiveRecord::Base.clear_active_connections!
- refute ActiveRecord::Base.connection_handler.active_connections? # sanity check
+ with_temporary_connection_pool do
+ ActiveRecord::Base.clear_active_connections!
+ refute ActiveRecord::Base.connection_handler.active_connections? # sanity check
- middleware {
- assert ActiveRecord::Base.connection.query_cache_enabled, "QueryCache did not get lazily enabled"
- }.call({})
+ middleware {
+ assert ActiveRecord::Base.connection.query_cache_enabled, "QueryCache did not get lazily enabled"
+ }.call({})
+ end
end
def test_query_caching_is_local_to_the_current_thread
- ActiveRecord::Base.clear_active_connections!
+ with_temporary_connection_pool do
+ ActiveRecord::Base.clear_active_connections!
+
+ middleware {
+ assert ActiveRecord::Base.connection_pool.query_cache_enabled
+ assert ActiveRecord::Base.connection.query_cache_enabled
- middleware {
- assert ActiveRecord::Base.connection_pool.query_cache_enabled
- assert ActiveRecord::Base.connection.query_cache_enabled
+ Thread.new {
+ refute ActiveRecord::Base.connection_pool.query_cache_enabled
+ refute ActiveRecord::Base.connection.query_cache_enabled
+ }.join
+ }.call({})
- Thread.new {
- refute ActiveRecord::Base.connection_pool.query_cache_enabled
- refute ActiveRecord::Base.connection.query_cache_enabled
- }.join
- }.call({})
+ end
end
private
@@ -388,6 +453,10 @@ end
class QueryCacheExpiryTest < ActiveRecord::TestCase
fixtures :tasks, :posts, :categories, :categories_posts
+ def teardown
+ Task.connection.clear_query_cache
+ end
+
def test_cache_gets_cleared_after_migration
# warm the cache
Post.find(1)
@@ -463,4 +532,16 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
end
end
end
+
+ test "threads use the same connection" do
+ @connection_1 = ActiveRecord::Base.connection.object_id
+
+ thread_a = Thread.new do
+ @connection_2 = ActiveRecord::Base.connection.object_id
+ end
+
+ thread_a.join
+
+ assert_equal @connection_1, @connection_2
+ end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 0ef51272b9..c1c2efb9c8 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -258,7 +258,9 @@ class ReflectionTest < ActiveRecord::TestCase
[Post.reflect_on_association(:first_taggings).scope],
[Author.reflect_on_association(:misc_posts).scope]
]
- actual = Author.reflect_on_association(:misc_post_first_blue_tags).scope_chain
+ actual = assert_deprecated do
+ Author.reflect_on_association(:misc_post_first_blue_tags).scope_chain
+ end
assert_equal expected, actual
expected = [
@@ -270,7 +272,9 @@ class ReflectionTest < ActiveRecord::TestCase
[],
[]
]
- actual = Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
+ actual = assert_deprecated do
+ Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
+ end
assert_equal expected, actual
end
@@ -331,6 +335,15 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal "custom_primary_key", Author.reflect_on_association(:tags_with_primary_key).association_primary_key.to_s # nested
end
+ def test_association_primary_key_type
+ # Normal Association
+ assert_equal :integer, Author.reflect_on_association(:posts).association_primary_key_type.type
+ assert_equal :string, Author.reflect_on_association(:essay).association_primary_key_type.type
+
+ # Through Association
+ assert_equal :string, Author.reflect_on_association(:essay_category).association_primary_key_type.type
+ end
+
def test_association_primary_key_raises_when_missing_primary_key
reflection = ActiveRecord::Reflection.create(:has_many, :edge, nil, {}, Author)
assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.association_primary_key }
@@ -395,15 +408,27 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_through_reflection_scope_chain_does_not_modify_other_reflections
- orig_conds = Post.reflect_on_association(:first_blue_tags_2).scope_chain.inspect
- Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
- assert_equal orig_conds, Post.reflect_on_association(:first_blue_tags_2).scope_chain.inspect
+ orig_conds = assert_deprecated do
+ Post.reflect_on_association(:first_blue_tags_2).scope_chain
+ end.inspect
+ assert_deprecated do
+ Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
+ end
+ assert_equal orig_conds, assert_deprecated {
+ Post.reflect_on_association(:first_blue_tags_2).scope_chain
+ }.inspect
end
def test_symbol_for_class_name
assert_equal Client, Firm.reflect_on_association(:unsorted_clients_with_symbol).klass
end
+ def test_class_for_class_name
+ assert_deprecated do
+ assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { class_name: Client }, Firm), :validate?
+ end
+ end
+
def test_join_table
category = Struct.new(:table_name, :pluralize_table_names).new("categories", true)
product = Struct.new(:table_name, :pluralize_table_names).new("products", true)
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index d2382b9bb2..49d4aeafc9 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -32,7 +32,8 @@ module ActiveRecord
:exclude?, :find_all, :flat_map, :group_by, :include?, :length,
:map, :none?, :one?, :partition, :reject, :reverse,
:sample, :second, :sort, :sort_by, :third,
- :to_ary, :to_set, :to_xml, :to_yaml, :join
+ :to_ary, :to_set, :to_xml, :to_yaml, :join,
+ :in_groups, :in_groups_of, :to_sentence, :to_formatted_s
]
ARRAY_DELEGATES.each do |method|
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 2cbbc775ce..4f92f71a09 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -3,7 +3,7 @@ require "models/post"
module ActiveRecord
class RelationMutationTest < ActiveRecord::TestCase
- class FakeKlass < Struct.new(:table_name, :name)
+ FakeKlass = Struct.new(:table_name, :name) do
extend ActiveRecord::Delegation::DelegateCache
inherited self
@@ -108,7 +108,7 @@ module ActiveRecord
end
test "#reorder!" do
- @relation = self.relation.order("foo")
+ @relation = relation.order("foo")
assert relation.reorder!("bar").equal?(relation)
assert_equal ["bar"], relation.order_values
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index d5af0cc9a5..1241bb54a4 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -8,7 +8,7 @@ module ActiveRecord
class RelationTest < ActiveRecord::TestCase
fixtures :posts, :comments, :authors
- class FakeKlass < Struct.new(:table_name, :name)
+ FakeKlass = Struct.new(:table_name, :name) do
extend ActiveRecord::Delegation::DelegateCache
inherited self
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 9519fec0c4..0c94e891eb 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1617,9 +1617,9 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "David", topic2.reload.author_name
end
- def test_update_on_relation_passing_active_record_object_is_deprecated
+ def test_update_on_relation_passing_active_record_object_is_not_permitted
topic = Topic.create!(title: "Foo", author_name: nil)
- assert_deprecated(/update/) do
+ assert_raises(ArgumentError) do
Topic.where(id: topic.id).update(topic, title: "Bar")
end
end
@@ -1935,6 +1935,18 @@ class RelationTest < ActiveRecord::TestCase
assert !Post.all.respond_to?(:by_lifo)
end
+ def test_unscope_with_subquery
+ p1 = Post.where(id: 1)
+ p2 = Post.where(id: 2)
+
+ assert_not_equal p1, p2
+
+ comments = Comment.where(post: p1).unscope(where: :post_id).where(post: p2)
+
+ assert_not_equal p1.first.comments, comments
+ assert_equal p2.first.comments, comments
+ end
+
def test_unscope_removes_binds
left = Post.where(id: Arel::Nodes::BindParam.new)
column = Post.columns_hash["id"]
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index bea78d2a95..9584318e86 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -178,24 +178,20 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def test_schema_dumps_index_columns_in_right_order
- index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_index/).first.strip
+ index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_index/).first.strip
if current_adapter?(:PostgreSQLAdapter)
- assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }, using: :btree', index_definition
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }', index_definition
elsif current_adapter?(:Mysql2Adapter)
- assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }, using: :btree', index_definition
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }', index_definition
else
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition
end
end
def test_schema_dumps_partial_indices
- index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_partial_index/).first.strip
- if current_adapter?(:PostgreSQLAdapter)
- assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
- elsif current_adapter?(:Mysql2Adapter)
- assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
- elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index?
- assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition
+ index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_partial_index/).first.strip
+ if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index?
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)"', index_definition
else
assert_equal 't.index ["firm_id", "type"], name: "company_partial_index"', index_definition
end
@@ -248,9 +244,9 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def test_schema_dumps_index_type
- output = standard_dump
- assert_match %r{t\.index \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output
- assert_match %r{t\.index \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output
+ output = dump_table_schema "key_tests"
+ assert_match %r{t\.index \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext$}, output
+ assert_match %r{t\.index \["pizza"\], name: "index_key_tests_on_pizza"$}, output
end
end
@@ -261,23 +257,34 @@ class SchemaDumperTest < ActiveRecord::TestCase
if current_adapter?(:PostgreSQLAdapter)
def test_schema_dump_includes_bigint_default
- output = standard_dump
+ output = dump_table_schema "defaults"
assert_match %r{t\.bigint\s+"bigint_default",\s+default: 0}, output
end
def test_schema_dump_includes_limit_on_array_type
- output = standard_dump
+ output = dump_table_schema "bigint_array"
assert_match %r{t\.bigint\s+"big_int_data_points\",\s+array: true}, output
end
def test_schema_dump_allows_array_of_decimal_defaults
- output = standard_dump
+ output = dump_table_schema "bigint_array"
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 = standard_dump.split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
- assert_equal 't.index "lower((name)::text)", name: "company_expression_index", using: :btree', index_definition
+ index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
+ assert_equal 't.index "lower((name)::text)", name: "company_expression_index"', index_definition
+ end
+
+ def test_schema_dump_interval_type
+ output = dump_table_schema "postgresql_times"
+ assert_match %r{t\.interval\s+"time_interval"$}, output
+ assert_match %r{t\.interval\s+"scaled_time_interval",\s+precision: 6$}, output
+ end
+
+ def test_schema_dump_oid_type
+ output = dump_table_schema "postgresql_oids"
+ assert_match %r{t\.oid\s+"obj_id"$}, output
end
if ActiveRecord::Base.connection.supports_extensions?
@@ -341,9 +348,9 @@ class SchemaDumperTest < ActiveRecord::TestCase
create_table("dogs") do |t|
t.column :name, :string
- t.column :owner_id, :bigint
+ t.references :owner
t.index [:name]
- t.foreign_key :dog_owners, column: "owner_id" if supports_foreign_keys?
+ t.foreign_key :dog_owners, column: "owner_id"
end
end
def down
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 3a04f4bf7d..14fb2fbbfa 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -10,6 +10,8 @@ require "concurrent/atomic/cyclic_barrier"
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts, :comments
+ self.use_transactional_tests = false
+
def test_default_scope
expected = Developer.all.merge!(order: "salary DESC").to_a.collect(&:salary)
received = DeveloperOrderedBySalary.all.collect(&:salary)
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index a469da0a5b..673392b4c4 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -240,6 +240,20 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal [], light.long_state
end
+ def test_unexpected_serialized_type
+ Topic.serialize :content, Hash
+ topic = Topic.create!(content: { zomg: true })
+
+ Topic.serialize :content, Array
+
+ topic.reload
+ error = assert_raise(ActiveRecord::SerializationTypeMismatch) do
+ topic.content
+ end
+ expected = "can't load `content`: was supposed to be a Array, but was a Hash. -- {:zomg=>true}"
+ assert_equal expected, error.to_s
+ end
+
def test_serialized_column_should_unserialize_after_update_column
t = Topic.create(content: "first")
assert_equal("first", t.content)
diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb
index f45f63c68e..fab3648564 100644
--- a/activerecord/test/cases/statement_cache_test.rb
+++ b/activerecord/test/cases/statement_cache_test.rb
@@ -105,5 +105,31 @@ module ActiveRecord
refute_equal book, other_book
end
+
+ def test_find_by_does_not_use_statement_cache_if_table_name_is_changed
+ book = Book.create(name: "my book")
+
+ Book.find_by(name: book.name) # warming the statement cache.
+
+ # changing the table name should change the query that is not cached.
+ Book.table_name = :birds
+ assert_nil Book.find_by(name: book.name)
+ ensure
+ Book.table_name = :books
+ end
+
+ def test_find_does_not_use_statement_cache_if_table_name_is_changed
+ book = Book.create(name: "my book")
+
+ Book.find(book.id) # warming the statement cache.
+
+ # changing the table name should change the query that is not cached.
+ Book.table_name = :birds
+ assert_raise ActiveRecord::RecordNotFound do
+ Book.find(book.id)
+ end
+ ensure
+ Book.table_name = :books
+ end
end
end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index f7c53b5801..5653fd83fd 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -100,6 +100,8 @@ module ActiveRecord
@configurations = { "development" => { "database" => "my-db" } }
ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ # To refrain from connecting to a newly created empty DB in sqlite3_mem tests
+ ActiveRecord::Base.connection_handler.stubs(:establish_connection)
end
def test_ignores_configurations_without_databases
diff --git a/activerecord/test/cases/time_precision_test.rb b/activerecord/test/cases/time_precision_test.rb
index 03f6c234e8..09c585167e 100644
--- a/activerecord/test/cases/time_precision_test.rb
+++ b/activerecord/test/cases/time_precision_test.rb
@@ -68,7 +68,7 @@ if subsecond_precision_supported?
assert_match %r{t\.time\s+"finish",\s+precision: 6$}, output
end
- if current_adapter?(:PostgreSQLAdapter)
+ if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter)
def test_time_precision_with_zero_should_be_dumped
@connection.create_table(:foos, force: true) do |t|
t.time :start, precision: 0
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 7766a74612..39b40e3411 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -422,7 +422,7 @@ class TimestampTest < ActiveRecord::TestCase
self.table_name = "people"
before_create do
- self.born_at = self.created_at
+ self.born_at = created_at
end
end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 9b1cca8583..111495c481 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -206,16 +206,6 @@ class TransactionTest < ActiveRecord::TestCase
assert_equal posts_count, author.posts.reload.size
end
- def test_cancellation_from_returning_false_in_before_filter
- def @first.before_save_for_transaction
- false
- end
-
- assert_deprecated do
- @first.save
- end
- end
-
def test_cancellation_from_before_destroy_rollbacks_in_destroy
add_cancelling_before_destroy_with_db_side_effect_to_topic @first
nbooks_before_destroy = Book.count
@@ -279,7 +269,11 @@ class TransactionTest < ActiveRecord::TestCase
e = assert_raises(RuntimeError) { new_topic.save }
assert_equal "Make the transaction rollback", e.message
assert_equal new_record_snapshot, !new_topic.persisted?, "The topic should have its old persisted value"
- assert_equal id_snapshot, new_topic.id, "The topic should have its old id"
+ if id_snapshot.nil?
+ assert_nil new_topic.id, "The topic should have its old id"
+ else
+ assert_equal id_snapshot, new_topic.id, "The topic should have its old id"
+ end
assert_equal id_present, new_topic.has_attribute?(Topic.primary_key)
end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 6d22638592..277280b42e 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -372,7 +372,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
e2 = Event.create(title: "abcdefgh")
assert_not e2.valid?, "Created an event whose title is not unique"
- elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
+ elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter)
assert_raise(ActiveRecord::ValueTooLong) do
Event.create(title: "abcdefgh")
end
@@ -391,7 +391,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
e2 = Event.create(title: "一二三四五六七八")
assert_not e2.valid?, "Created an event whose title is not unique"
- elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
+ elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter)
assert_raise(ActiveRecord::ValueTooLong) do
Event.create(title: "一二三四五六七八")
end
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index d055968a56..07288568e8 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -154,7 +154,7 @@ if ActiveRecord::Base.connection.supports_views?
end
# sqlite dose not support CREATE, INSERT, and DELETE for VIEW
- if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
class UpdateableViewTest < ActiveRecord::TestCase
self.use_transactional_tests = false
fixtures :books
@@ -200,7 +200,7 @@ if ActiveRecord::Base.connection.supports_views?
end
end
end
- end # end of `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)`
+ end # end of `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)`
end # end of `if ActiveRecord::Base.connection.supports_views?`
if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) &&
diff --git a/activerecord/test/fixtures/subscribers.yml b/activerecord/test/fixtures/subscribers.yml
index c6a8c2fa24..0f6e0cd48e 100644
--- a/activerecord/test/fixtures/subscribers.yml
+++ b/activerecord/test/fixtures/subscribers.yml
@@ -6,6 +6,6 @@ second:
nick: webster132
name: David Heinemeier Hansson
-thrid:
+third:
nick: swistak
name: Marcin Raczkowski \ No newline at end of file
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 4561b3132b..20e37710e7 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -216,14 +216,12 @@ class Account < ActiveRecord::Base
validate :check_empty_credit_limit
- protected
+ private
def check_empty_credit_limit
errors.add("credit_limit", :blank) if credit_limit.blank?
end
- private
-
def private_method
"Sir, yes sir!"
end
diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb
index 60af7c2247..3d40cb1ace 100644
--- a/activerecord/test/models/customer.rb
+++ b/activerecord/test/models/customer.rb
@@ -56,7 +56,7 @@ class GpsLocation
end
def ==(other)
- self.latitude == other.latitude && self.longitude == other.longitude
+ latitude == other.latitude && longitude == other.longitude
end
end
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index ea4f719517..654830ba11 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -260,3 +260,9 @@ class CachedDeveloper < ActiveRecord::Base
self.table_name = "developers"
self.cache_timestamp_format = :number
end
+
+class DeveloperWithIncorrectlyOrderedHasManyThrough < ActiveRecord::Base
+ self.table_name = "developers"
+ has_many :companies, through: :contracts
+ has_many :contracts, foreign_key: :developer_id
+end
diff --git a/activerecord/test/models/family.rb b/activerecord/test/models/family.rb
new file mode 100644
index 0000000000..5ae5a78c95
--- /dev/null
+++ b/activerecord/test/models/family.rb
@@ -0,0 +1,4 @@
+class Family < ActiveRecord::Base
+ has_many :family_trees, -> { where(token: nil) }
+ has_many :members, through: :family_trees
+end
diff --git a/activerecord/test/models/family_tree.rb b/activerecord/test/models/family_tree.rb
new file mode 100644
index 0000000000..cd9829fedd
--- /dev/null
+++ b/activerecord/test/models/family_tree.rb
@@ -0,0 +1,4 @@
+class FamilyTree < ActiveRecord::Base
+ belongs_to :member, class_name: "User", foreign_key: "member_id"
+ belongs_to :family
+end
diff --git a/activerecord/test/models/non_primary_key.rb b/activerecord/test/models/non_primary_key.rb
new file mode 100644
index 0000000000..1cafb09608
--- /dev/null
+++ b/activerecord/test/models/non_primary_key.rb
@@ -0,0 +1,2 @@
+class NonPrimaryKey < ActiveRecord::Base
+end
diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb
index 5009f8f54b..4fbd986e40 100644
--- a/activerecord/test/models/project.rb
+++ b/activerecord/test/models/project.rb
@@ -11,7 +11,7 @@ class Project < ActiveRecord::Base
after_add: Proc.new { |o, r| o.developers_log << "after_adding#{r.id || '<new>'}" },
before_remove: Proc.new { |o, r| o.developers_log << "before_removing#{r.id}" },
after_remove: Proc.new { |o, r| o.developers_log << "after_removing#{r.id}" }
- has_and_belongs_to_many :well_payed_salary_groups, -> { group("developers.salary").having("SUM(salary) > 10000").select("SUM(salary) as salary") }, class_name: "Developer"
+ has_and_belongs_to_many :well_paid_salary_groups, -> { group("developers.salary").having("SUM(salary) > 10000").select("SUM(salary) as salary") }, class_name: "Developer"
belongs_to :firm
has_one :lead_developer, through: :firm, inverse_of: :contracted_projects
diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb
index 47649e0a77..5089a795f4 100644
--- a/activerecord/test/models/user.rb
+++ b/activerecord/test/models/user.rb
@@ -5,8 +5,12 @@ class User < ActiveRecord::Base
has_secure_token :auth_token
has_and_belongs_to_many :jobs_pool,
- class_name: Job,
+ class_name: "Job",
join_table: "jobs_pool"
+
+ has_one :family_tree, -> { where(token: nil) }, foreign_key: "member_id"
+ has_one :family, through: :family_tree
+ has_many :family_members, through: :family, source: :members
end
class UserWithNotification < User
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 15ba2d67ab..860c63b27c 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -23,16 +23,24 @@ ActiveRecord::Schema.define do
t.string :char2, limit: 50, default: "a varchar field"
t.text :char3, default: "a text field"
t.bigint :bigint_default, default: -> { "0::bigint" }
- t.text :multiline_default, default: '--- []
+ t.text :multiline_default, default: "--- []
-'
+"
end
- %w(postgresql_times postgresql_oids postgresql_timestamp_with_zones
- postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
- drop_table table_name, if_exists: true
+ create_table :postgresql_times, force: true do |t|
+ t.interval :time_interval
+ t.interval :scaled_time_interval, precision: 6
end
+ create_table :postgresql_oids, force: true do |t|
+ t.oid :obj_id
+ end
+
+ drop_table "postgresql_timestamp_with_zones", if_exists: true
+ drop_table "postgresql_partitioned_table", if_exists: true
+ drop_table "postgresql_partitioned_table_parent", if_exists: true
+
execute "DROP SEQUENCE IF EXISTS companies_nonstd_seq CASCADE"
execute "CREATE SEQUENCE companies_nonstd_seq START 101 OWNED BY companies.id"
execute "ALTER TABLE companies ALTER COLUMN id SET DEFAULT nextval('companies_nonstd_seq')"
@@ -45,21 +53,6 @@ ActiveRecord::Schema.define do
end
execute <<_SQL
- CREATE TABLE postgresql_times (
- id SERIAL PRIMARY KEY,
- time_interval INTERVAL,
- scaled_time_interval INTERVAL(6)
- );
-_SQL
-
- execute <<_SQL
- CREATE TABLE postgresql_oids (
- id SERIAL PRIMARY KEY,
- obj_id OID
- );
-_SQL
-
- execute <<_SQL
CREATE TABLE postgresql_timestamp_with_zones (
id SERIAL PRIMARY KEY,
time TIMESTAMP WITH TIME ZONE
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index ba6f5de894..08bef08abc 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -54,8 +54,8 @@ ActiveRecord::Schema.define do
create_table :authors, force: true do |t|
t.string :name, null: false
- t.bigint :author_address_id
- t.integer :author_address_extra_id
+ t.references :author_address
+ t.references :author_address_extra
t.string :organization_id
t.string :owned_essay_id
end
@@ -88,7 +88,7 @@ ActiveRecord::Schema.define do
end
create_table :books, force: true do |t|
- t.integer :author_id
+ t.references :author
t.string :format
t.column :name, :string
t.column :status, :integer, default: 0
@@ -201,7 +201,7 @@ ActiveRecord::Schema.define do
t.integer :account_id
t.string :description, default: ""
t.index [:firm_id, :type, :rating], name: "company_index", length: { type: 10 }, order: { rating: :desc }
- t.index [:firm_id, :type], name: "company_partial_index", where: "rating > 10"
+ t.index [:firm_id, :type], name: "company_partial_index", where: "(rating > 10)"
t.index :name, name: "company_name_index", using: :btree
t.index "lower(name)", name: "company_expression_index" if supports_expression_index?
end
@@ -306,7 +306,7 @@ ActiveRecord::Schema.define do
end
create_table :engines, force: true do |t|
- t.bigint :car_id
+ t.references :car, index: false
end
create_table :entrants, force: true do |t|
@@ -329,6 +329,15 @@ ActiveRecord::Schema.define do
create_table :eyes, force: true do |t|
end
+ create_table :families, force: true do |t|
+ end
+
+ create_table :family_trees, force: true do |t|
+ t.references :family
+ t.references :member
+ t.string :token
+ end
+
create_table :funny_jokes, force: true do |t|
t.string :name
end
@@ -655,8 +664,8 @@ ActiveRecord::Schema.define do
end
create_table :posts, force: true do |t|
- t.integer :author_id
- t.string :title, null: false
+ t.references :author
+ t.string :title, null: false
# use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in
# Oracle SELECT WHERE clause which causes many unit test failures
if current_adapter?(:OracleAdapter)
@@ -1005,16 +1014,14 @@ ActiveRecord::Schema.define do
create_table :records, force: true do |t|
end
- if supports_foreign_keys?
- # fk_test_has_fk should be before fk_test_has_pk
- create_table :fk_test_has_fk, force: true do |t|
- t.bigint :fk_id, null: false
+ disable_referential_integrity do
+ create_table :fk_test_has_pk, primary_key: "pk_id", force: :cascade do |t|
end
- create_table :fk_test_has_pk, force: true, primary_key: "pk_id" do |t|
+ create_table :fk_test_has_fk, force: true do |t|
+ t.references :fk, null: false
+ t.foreign_key :fk_test_has_pk, column: "fk_id", name: "fk_name", primary_key: "pk_id"
end
-
- add_foreign_key :fk_test_has_fk, :fk_test_has_pk, column: "fk_id", name: "fk_name", primary_key: "pk_id"
end
create_table :overloaded_types, force: true do |t|
@@ -1032,6 +1039,10 @@ ActiveRecord::Schema.define do
create_table :test_with_keyword_column_name, force: true do |t|
t.string :desc
end
+
+ create_table :non_primary_keys, force: true, id: false do |t|
+ t.integer :id
+ end
end
Course.connection.create_table :courses, force: true do |t|
diff --git a/activerecord/test/schema/sqlite_specific_schema.rb b/activerecord/test/schema/sqlite_specific_schema.rb
deleted file mode 100644
index cc7c36fe2b..0000000000
--- a/activerecord/test/schema/sqlite_specific_schema.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-ActiveRecord::Schema.define do
- execute "DROP TABLE fk_test_has_fk" rescue nil
- execute "DROP TABLE fk_test_has_pk" rescue nil
- execute <<_SQL
- CREATE TABLE 'fk_test_has_pk' (
- 'pk_id' INTEGER NOT NULL PRIMARY KEY
- );
-_SQL
-
- execute <<_SQL
- CREATE TABLE 'fk_test_has_fk' (
- 'id' INTEGER NOT NULL PRIMARY KEY,
- 'fk_id' INTEGER NOT NULL,
-
- FOREIGN KEY ('fk_id') REFERENCES 'fk_test_has_pk'('pk_id')
- );
-_SQL
-end