aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md233
-rw-r--r--activerecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/associations/association.rb9
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb3
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb22
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb15
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb9
-rw-r--r--activerecord/lib/active_record/associations/preloader/collection_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_one.rb8
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb12
-rw-r--r--activerecord/lib/active_record/callbacks.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb45
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb32
-rw-r--r--activerecord/lib/active_record/core.rb4
-rw-r--r--activerecord/lib/active_record/enum.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb16
-rw-r--r--activerecord/lib/active_record/fixtures.rb4
-rw-r--r--activerecord/lib/active_record/inheritance.rb38
-rw-r--r--activerecord/lib/active_record/migration.rb117
-rw-r--r--activerecord/lib/active_record/model_schema.rb3
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake14
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb45
-rw-r--r--activerecord/lib/active_record/relation/record_fetch_warning.rb2
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb10
-rw-r--r--activerecord/lib/active_record/sanitization.rb16
-rw-r--r--activerecord/lib/active_record/schema_migration.rb2
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb6
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb8
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb6
-rw-r--r--activerecord/lib/active_record/type/type_map.rb2
-rw-r--r--activerecord/lib/active_record/type_caster.rb2
-rw-r--r--activerecord/lib/active_record/type_caster/connection.rb2
-rw-r--r--activerecord/lib/active_record/type_caster/map.rb2
-rw-r--r--activerecord/lib/active_record/validations/associated.rb10
-rw-r--r--activerecord/test/cases/adapter_test.rb41
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb28
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb50
-rw-r--r--activerecord/test/cases/adapters/mysql/sp_test.rb23
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb28
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb40
-rw-r--r--activerecord/test/cases/adapters/mysql2/sp_test.rb23
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb46
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb26
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb12
-rw-r--r--activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb41
-rw-r--r--activerecord/test/cases/associations/eager_test.rb24
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb25
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb15
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb79
-rw-r--r--activerecord/test/cases/attributes_test.rb13
-rw-r--r--activerecord/test/cases/autosave_association_test.rb36
-rw-r--r--activerecord/test/cases/base_test.rb81
-rw-r--r--activerecord/test/cases/batches_test.rb2
-rw-r--r--activerecord/test/cases/connection_management_test.rb9
-rw-r--r--activerecord/test/cases/connection_pool_test.rb2
-rw-r--r--activerecord/test/cases/fixtures_test.rb14
-rw-r--r--activerecord/test/cases/inheritance_test.rb81
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb54
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb2
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb4
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb24
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb2
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb4
-rw-r--r--activerecord/test/cases/migration_test.rb102
-rw-r--r--activerecord/test/cases/migrator_test.rb4
-rw-r--r--activerecord/test/cases/persistence_test.rb16
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb2
-rw-r--r--activerecord/test/cases/primary_keys_test.rb28
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb6
-rw-r--r--activerecord/test/cases/relation_test.rb11
-rw-r--r--activerecord/test/cases/relations_test.rb5
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb32
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb21
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb2
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb2
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb12
-rw-r--r--activerecord/test/cases/view_test.rb14
-rw-r--r--activerecord/test/fixtures/content.yml3
-rw-r--r--activerecord/test/fixtures/content_positions.yml3
-rw-r--r--activerecord/test/models/bulb.rb1
-rw-r--r--activerecord/test/models/car.rb1
-rw-r--r--activerecord/test/models/content.rb40
-rw-r--r--activerecord/test/models/guitar.rb4
-rw-r--r--activerecord/test/models/post.rb7
-rw-r--r--activerecord/test/models/project.rb8
-rw-r--r--activerecord/test/models/tuning_peg.rb4
-rw-r--r--activerecord/test/schema/schema.rb19
-rw-r--r--activerecord/test/support/schema_dumping_helper.rb2
107 files changed, 1595 insertions, 441 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 48005eee1a..2461dd517b 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,201 @@
+* Except keys of `build_record`'s argument from `create_scope` in `initialize_attributes`.
+
+ Fixes #21893.
+
+ *Yuichiro Kaneko*
+
+* Deprecate `connection.tables` on the SQLite3 and MySQL adapters.
+ Also deprecate passing arguments to `#tables`.
+ And deprecate `table_exists?`.
+
+ The `#tables` method of some adapters (mysql, mysql2, sqlite3) would return
+ both tables and views while others (postgresql) just return tables. To make
+ their behavior consistent, `#tables` will return only tables in the future.
+
+ The `#table_exists?` method would check both tables and views. To make
+ their behavior consistent with `#tables`, `#table_exists?` will check only
+ tables in the future.
+
+ *Yuichiro Kaneko*
+
+* Improve support for non Active Record objects on `validates_associated`
+
+ Skipping `marked_for_destruction?` when the associated object does not responds
+ to it make easier to validate virtual associations built on top of Active Model
+ objects and/or serialized objects that implement a `valid?` instance method.
+
+ *Kassio Borges*, *Lucas Mazza*
+
+* Change connection management middleware to return a new response with
+ a body proxy, rather than mutating the original.
+
+ *Kevin Buchanan*
+
+* Make `db:migrate:status` to render `1_some.rb` format migrate files.
+
+ These files are in `db/migrate`:
+
+ * 1_valid_people_have_last_names.rb
+ * 20150819202140_irreversible_migration.rb
+ * 20150823202140_add_admin_flag_to_users.rb
+ * 20150823202141_migration_tests.rb
+ * 2_we_need_reminders.rb
+ * 3_innocent_jointable.rb
+
+ Before:
+
+ $ bundle exec rake db:migrate:status
+ ...
+
+ Status Migration ID Migration Name
+ --------------------------------------------------
+ up 001 ********** NO FILE **********
+ up 002 ********** NO FILE **********
+ up 003 ********** NO FILE **********
+ up 20150819202140 Irreversible migration
+ up 20150823202140 Add admin flag to users
+ up 20150823202141 Migration tests
+
+ After:
+
+ $ bundle exec rake db:migrate:status
+ ...
+
+ Status Migration ID Migration Name
+ --------------------------------------------------
+ up 001 Valid people have last names
+ up 002 We need reminders
+ up 003 Innocent jointable
+ up 20150819202140 Irreversible migration
+ up 20150823202140 Add admin flag to users
+ up 20150823202141 Migration tests
+
+ *Yuichiro Kaneko*
+
+* Define `ActiveRecord::Sanitization.sanitize_sql_for_order` and use it inside
+ `preprocess_order_args`.
+
+ *Yuichiro Kaneko*
+
+* Allow bigint with default nil for avoiding auto increment primary key.
+
+ *Ryuta Kamizono*
+
+* Remove `DEFAULT_CHARSET` and `DEFAULT_COLLATION` in `MySQLDatabaseTasks`.
+
+ We should omit the collation entirely rather than providing a default.
+ Then the choice is the responsibility of the server and MySQL distribution.
+
+ *Ryuta Kamizono*
+
+* Alias `ActiveRecord::Relation#left_joins` to
+ `ActiveRecord::Relation#left_outer_joins`.
+
+ *Takashi Kokubun*
+
+* Use advisory locking to raise a `ConcurrentMigrationError` instead of
+ attempting to migrate when another migration is currently running.
+
+ *Sam Davies*
+
+* Added `ActiveRecord::Relation#left_outer_joins`.
+
+ Example:
+
+ User.left_outer_joins(:posts)
+ # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON
+ "posts"."user_id" = "users"."id"
+
+ *Florian Thomas*
+
+* Support passing an array to `order` for SQL parameter sanitization.
+
+ *Aaron Suggs*
+
+* Avoid disabling errors on the PostgreSQL connection when enabling the
+ `standard_conforming_strings` setting. Errors were previously disabled because
+ the setting wasn't writable in Postgres 8.1 and didn't exist in earlier
+ versions. Now Rails only supports Postgres 8.2+ we're fine to assume the
+ setting exists. Disabling errors caused problems when using a connection
+ pooling tool like PgBouncer because it's not guaranteed to have the same
+ connection between calls to `execute` and it could leave the connection
+ with errors disabled.
+
+ Fixes #22101.
+
+ *Harry Marr*
+
+* Set `scope.reordering_value` to `true` if `:reordering`-values are specified.
+
+ Fixes #21886.
+
+ *Hiroaki Izu*
+
+* Add support for bidirectional destroy dependencies.
+
+ Fixes #13609.
+
+ Example:
+
+ class Content < ActiveRecord::Base
+ has_one :position, dependent: :destroy
+ end
+
+ class Position < ActiveRecord::Base
+ belongs_to :content, dependent: :destroy
+ end
+
+ *Seb Jacobs*
+
+* Includes HABTM returns correct size now. It's caused by the join dependency
+ only instantiates one HABTM object because the join table hasn't a primary key.
+
+ Fixes #16032.
+
+ Examples:
+
+ before:
+
+ Project.first.salaried_developers.size # => 3
+ Project.includes(:salaried_developers).first.salaried_developers.size # => 1
+
+ after:
+
+ Project.first.salaried_developers.size # => 3
+ Project.includes(:salaried_developers).first.salaried_developers.size # => 3
+
+ *Bigxiang*
+
+* Add option to index errors in nested attributes
+
+ For models which have nested attributes, errors within those models will
+ now be indexed if :index_errors is specified when defining a
+ has_many relationship, or if its set in the global config.
+
+ Example:
+
+ class Guitar < ActiveRecord::Base
+ has_many :tuning_pegs
+ accepts_nested_attributes_for :tuning_pegs
+ end
+
+ class TuningPeg < ActiveRecord::Base
+ belongs_to :guitar
+ validates_numericality_of :pitch
+ end
+
+ # Old style
+ guitar.errors["tuning_pegs.pitch"] = ["is not a number"]
+
+ # New style (if defined globally, or set in has_many_relationship)
+ guitar.errors["tuning_pegs[1].pitch"] = ["is not a number"]
+
+ *Michael Probber*, *Terence Sun*
+
+* Exit with non-zero status for failed database rake tasks.
+
+ *Jay Hayes*
+
* Queries such as `Computer.joins(:monitor).group(:status).count` will now be
interpreted as `Computer.joins(:monitor).group('computers.status').count`
so that when `Computer` and `Monitor` have both `status` columns we don't
@@ -5,21 +203,23 @@
*Rafael Sales*
-* Add ability to default to `uuid` as primary key when generating database migrations
+* Add ability to default to `uuid` as primary key when generating database migrations.
+
+ Example:
- config.generators do |g|
- g.orm :active_record, primary_key_type: :uuid
- end
+ config.generators do |g|
+ g.orm :active_record, primary_key_type: :uuid
+ end
*Jon McCartie*
-* Don't cache arguments in #find_by if they are an ActiveRecord::Relation
+* Don't cache arguments in `#find_by` if they are an `ActiveRecord::Relation`.
Fixes #20817
*Hiroaki Izu*
-* Qualify column name inserted by `group` in calculation
+* Qualify column name inserted by `group` in calculation.
Giving `group` an unqualified column name now works, even if the relation
has `JOIN` with another table which also has a column of the name.
@@ -44,7 +244,7 @@
*Jake Worth*
* Add an immutable string type to help reduce memory usage for apps which do
- not need mutation detection on Strings.
+ not need mutation detection on strings.
*Sean Griffin*
@@ -62,7 +262,7 @@
*Yves Senn*
-* No longer pass depreacted option `-i` to `pg_dump`.
+* No longer pass deprecated option `-i` to `pg_dump`.
*Paul Sadauskas*
@@ -77,7 +277,7 @@
*Matthew Draper*, *Jean Boussier*
-* Remove unused `pk_and_sequence_for` in AbstractMysqlAdapter.
+* Remove unused `pk_and_sequence_for` in `AbstractMysqlAdapter`.
*Ryuta Kamizono*
@@ -103,16 +303,15 @@
*Jimmy Bourassa*
-* Fixed taking precision into count when assigning a value to timestamp attribute
+* Fixed taking precision into count when assigning a value to timestamp attribute.
Timestamp column can have less precision than ruby timestamp
In result in how big a fraction of a second can be stored in the
database.
- m = Model.create!
- m.created_at.usec == m.reload.created_at.usec
- # => false
+ m = Model.create!
+ m.created_at.usec == m.reload.created_at.usec # => false
# due to different precision in Time.now and database column
If the precision is low enough, (mysql default is 0, so it is always low
@@ -134,7 +333,7 @@
*Yves Senn*, *Matthew Draper*
* Add `ActiveRecord::Base.ignored_columns` to make some columns
- invisible from ActiveRecord.
+ invisible from Active Record.
*Jean Boussier*
@@ -300,9 +499,9 @@
Example:
- @users = User.where("name like ?", "%Alberto%")
- @users.cache_key
- => "/users/query-5942b155a43b139f2471b872ac54251f-3-20150714212107656125000"
+ @users = User.where("name like ?", "%Alberto%")
+ @users.cache_key
+ # => "/users/query-5942b155a43b139f2471b872ac54251f-3-20150714212107656125000"
*Alberto Fernández-Capel*
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index d4f079e346..b806a2f832 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -149,7 +149,7 @@ module ActiveRecord
class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc:
end
- # This error is raised when trying to eager load a poloymorphic association using a JOIN.
+ # This error is raised when trying to eager load a polymorphic association using a JOIN.
# Eager loading polymorphic associations is only possible with
# {ActiveRecord::Relation#preload}[rdoc-ref:QueryMethods#preload].
class EagerLoadPolymorphicError < ActiveRecordError
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index c7b396f3d4..d64ab64c99 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -163,9 +163,12 @@ module ActiveRecord
@reflection = @owner.class._reflect_on_association(reflection_name)
end
- def initialize_attributes(record) #:nodoc:
+ def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc:
+ except_from_scope_attributes ||= {}
skip_assign = [reflection.foreign_key, reflection.type].compact
- attributes = create_scope.except(*(record.changed - skip_assign))
+ assigned_keys = record.changed
+ assigned_keys += except_from_scope_attributes.keys.map(&:to_s)
+ attributes = create_scope.except(*(assigned_keys - skip_assign))
record.assign_attributes(attributes)
set_inverse_instance(record)
end
@@ -248,7 +251,7 @@ module ActiveRecord
def build_record(attributes)
reflection.build_association(attributes) do |record|
- initialize_attributes(record)
+ initialize_attributes(record, attributes)
end
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index dae468ba54..81c535d962 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -116,8 +116,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
def self.add_destroy_callbacks(model, reflection)
- name = reflection.name
- model.after_destroy lambda { |o| o.association(name).handle_dependency }
+ model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency }
end
def self.define_validations(model, reflection)
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index a5c9f1666e..b888148841 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -62,13 +62,13 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
def self.add_left_association(name, options)
- belongs_to name, options
+ belongs_to name, required: false, **options
self.left_reflection = _reflect_on_association(name)
end
def self.add_right_association(name, options)
rhs_name = name.to_s.singularize.to_sym
- belongs_to rhs_name, options
+ belongs_to rhs_name, required: false, **options
self.right_reflection = _reflect_on_association(rhs_name)
end
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index f71b10120f..7864d4c536 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
def self.valid_options(options)
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type]
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type, :index_errors]
end
def self.valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index ca473beea3..0e4e951269 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)
+ class Table < Struct.new(:node, :columns) # :nodoc:
def table
Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
end
@@ -103,9 +103,14 @@ module ActiveRecord
join_root.drop(1).map!(&:reflection)
end
- def join_constraints(outer_joins)
+ def join_constraints(outer_joins, join_type)
joins = join_root.children.flat_map { |child|
- make_inner_joins join_root, child
+
+ if join_type == Arel::Nodes::OuterJoin
+ make_left_outer_joins join_root, child
+ else
+ make_inner_joins join_root, child
+ end
}
joins.concat outer_joins.flat_map { |oj|
@@ -150,7 +155,8 @@ module ActiveRecord
message_bus.instrument('instantiation.active_record', payload) do
result_set.each { |row_hash|
- parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases)
+ parent_key = primary_key ? row_hash[primary_key] : row_hash
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases)
construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
}
end
@@ -175,6 +181,14 @@ module ActiveRecord
[info] + child.children.flat_map { |c| make_outer_joins(child, c) }
end
+ def make_left_outer_joins(parent, child)
+ tables = child.tables
+ join_type = Arel::Nodes::OuterJoin
+ info = make_constraints parent, child, tables, join_type
+
+ [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) }
+ end
+
def make_inner_joins(parent, child)
tables = child.tables
join_type = Arel::Nodes::InnerJoin
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 3992a240b9..ecf6fb8643 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -54,6 +54,8 @@ module ActiveRecord
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
end
+ NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
+
# Eager loads the named associations for the given Active Record record(s).
#
# In this description, 'association name' shall refer to the name passed
@@ -88,9 +90,6 @@ module ActiveRecord
# [ :books, :author ]
# { author: :avatar }
# [ :books, { author: :avatar } ]
-
- NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
-
def preload(records, associations, preload_scope = nil)
records = Array.wrap(records).compact.uniq
associations = Array.wrap(associations)
@@ -107,6 +106,7 @@ module ActiveRecord
private
+ # Loads all the given data into +records+ for the +association+.
def preloaders_on(association, records, scope)
case association
when Hash
@@ -132,6 +132,11 @@ module ActiveRecord
}
end
+ # Loads all the given data into +records+ for a singular +association+.
+ #
+ # Functions by instantiating a preloader class such as Preloader::HasManyThrough and
+ # call the +run+ method for each passed in class in the +records+ argument.
+ #
# Not all records have the same class, so group then preload group on the reflection
# itself so that if various subclass share the same association then we do not split
# them unnecessarily
@@ -181,6 +186,10 @@ module ActiveRecord
def self.preloaded_records; []; end
end
+ # Returns a class containing the logic needed to load preload the data
+ # and attach it to a relation. For example +Preloader::Association+ or
+ # +Preloader::HasManyThrough+. The class returned implements a `run` method
+ # that accepts a preloader.
def preloader_for(reflection, owners, rhs_klass)
return NullPreloader unless rhs_klass
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 29dd0643d6..e11a5cfb8a 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -134,7 +134,14 @@ module ActiveRecord
else
scope.joins!(reflection_scope.joins_values)
end
- scope.order! preload_values[:order] || values[:order]
+
+ if order_values = preload_values[:order] || values[:order]
+ scope.order!(order_values)
+ end
+
+ if preload_values[:reordering] || values[:reordering]
+ scope.reordering_value = true
+ end
if preload_values[:readonly] || values[:readonly]
scope.readonly!
diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb
index 5adffcd831..9939280fa4 100644
--- a/activerecord/lib/active_record/associations/preloader/collection_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/collection_association.rb
@@ -2,13 +2,8 @@ module ActiveRecord
module Associations
class Preloader
class CollectionAssociation < Association #:nodoc:
-
private
- def build_scope
- super.order(preload_scope.values[:order] || reflection_scope.values[:order])
- end
-
def preload(preloader)
associated_records_by_owner(preloader).each do |owner, records|
association = owner.association(reflection.name)
@@ -17,7 +12,6 @@ module ActiveRecord
records.each { |record| association.set_inverse_instance(record) }
end
end
-
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_one.rb b/activerecord/lib/active_record/associations/preloader/has_one.rb
index 24728e9f01..c4add621ca 100644
--- a/activerecord/lib/active_record/associations/preloader/has_one.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_one.rb
@@ -2,7 +2,6 @@ module ActiveRecord
module Associations
class Preloader
class HasOne < SingularAssociation #:nodoc:
-
def association_key_name
reflection.foreign_key
end
@@ -10,13 +9,6 @@ module ActiveRecord
def owner_key_name
reflection.active_record_primary_key
end
-
- private
-
- def build_scope
- super.order(preload_scope.values[:order] || reflection_scope.values[:order])
- end
-
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 56aa23b173..24aa47bb51 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -84,7 +84,9 @@ module ActiveRecord
end
scope.references! reflection_scope.values[:references]
- scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
+ if scope.eager_loading? && order_values = reflection_scope.values[:order]
+ scope = scope.order(order_values)
+ end
end
scope
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index cbdd4950a6..4ae585d3f5 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -1,7 +1,7 @@
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/string/filters'
require 'mutex_m'
-require 'concurrent'
+require 'concurrent/map'
module ActiveRecord
# = Active Record Attribute Methods
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index d35bc3e794..fc12c3f45a 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -140,6 +140,8 @@ module ActiveRecord
included do
Associations::Builder::Association.extensions << AssociationBuilderExtension
+ mattr_accessor :index_nested_attribute_errors, instance_writer: false
+ self.index_nested_attribute_errors = false
end
module ClassMethods # :nodoc:
@@ -315,7 +317,7 @@ module ActiveRecord
def validate_collection_association(reflection)
if association = association_instance_get(reflection.name)
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
- records.each { |record| association_valid?(reflection, record) }
+ records.each_with_index { |record, index| association_valid?(reflection, record, index) }
end
end
end
@@ -323,14 +325,18 @@ module ActiveRecord
# Returns whether or not the association is valid and applies any errors to
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
# enabled records if they're marked_for_destruction? or destroyed.
- def association_valid?(reflection, record)
+ 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)
unless valid = record.valid?(validation_context)
if reflection.options[:autosave]
record.errors.each do |attribute, message|
- attribute = "#{reflection.name}.#{attribute}"
+ if index.nil? || (!reflection.options[:index_errors] && !ActiveRecord::Base.index_nested_attribute_errors)
+ attribute = "#{reflection.name}.#{attribute}"
+ else
+ attribute = "#{reflection.name}[#{index}].#{attribute}"
+ end
errors[attribute] << message
errors[attribute].uniq!
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index bfedc4af3f..cfd8cbda67 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -292,10 +292,15 @@ module ActiveRecord
end
def destroy #:nodoc:
+ @_destroy_callback_already_called ||= false
+ return if @_destroy_callback_already_called
+ @_destroy_callback_already_called = true
_run_destroy_callbacks { super }
rescue RecordNotDestroyed => e
@_association_destroy_exception = e
false
+ ensure
+ @_destroy_callback_already_called = false
end
def touch(*) #:nodoc:
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 0d850c7625..486b7b6d25 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,5 +1,5 @@
require 'thread'
-require 'concurrent'
+require 'concurrent/map'
require 'monitor'
module ActiveRecord
@@ -960,12 +960,11 @@ module ActiveRecord
def call(env)
testing = env['rack.test']
- response = @app.call(env)
- response[2] = ::Rack::BodyProxy.new(response[2]) do
+ status, headers, body = @app.call(env)
+ proxy = ::Rack::BodyProxy.new(body) do
ActiveRecord::Base.clear_active_connections! unless testing
end
-
- response
+ [status, headers, proxy]
rescue Exception
ActiveRecord::Base.clear_active_connections! unless testing
raise
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 e2ef56798b..abf0124562 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -202,13 +202,9 @@ module ActiveRecord
# end
# end
#
- # The table definitions
- # The Columns are stored as a ColumnDefinition in the #columns attribute.
class TableDefinition
include ColumnMethods
- # An array of ColumnDefinition objects, representing the column changes
- # that have been defined.
attr_accessor :indexes
attr_reader :name, :temporary, :options, :as, :foreign_keys, :native
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 d5f8dbc8fc..5cacf6eddc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -263,7 +263,7 @@ module ActiveRecord
yield td if block_given?
- if options[:force] && table_exists?(table_name)
+ if options[:force] && data_source_exists?(table_name)
drop_table(table_name, options)
end
@@ -1088,7 +1088,7 @@ module ActiveRecord
if index_name.length > max_index_length
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
end
- if table_exists?(table_name) && index_name_exists?(table_name, index_name, false)
+ if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false)
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 402159ac13..4d4dc07b04 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -214,6 +214,11 @@ module ActiveRecord
false
end
+ # Does this adapter support application-enforced advisory locking?
+ def supports_advisory_locks?
+ false
+ end
+
# Should primary key values be selected from their corresponding
# sequence before the insert statement? If true, next_sequence_value
# is called before each insert to set the record's primary key.
@@ -280,6 +285,20 @@ module ActiveRecord
def enable_extension(name)
end
+ # This is meant to be implemented by the adapters that support advisory
+ # locks
+ #
+ # Return true if we got the lock, otherwise false
+ def get_advisory_lock(lock_id) # :nodoc:
+ end
+
+ # This is meant to be implemented by the adapters that support advisory
+ # locks.
+ #
+ # Return true if we released the lock, otherwise false
+ def release_advisory_lock(lock_id) # :nodoc:
+ end
+
# A list of extensions, to be filled in by adapters that support them.
def extensions
[]
@@ -519,7 +538,7 @@ module ActiveRecord
def translate_exception(exception, message)
# override in derived class
- ActiveRecord::StatementInvalid.new(message, exception)
+ ActiveRecord::StatementInvalid.new(message)
end
def without_prepared_statement?(binds)
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 251acf1c83..deef246c37 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -134,9 +134,7 @@ module ActiveRecord
time: { name: "time" },
date: { name: "date" },
binary: { name: "blob" },
- blob: { name: "blob" },
boolean: { name: "tinyint", limit: 1 },
- bigint: { name: "bigint" },
json: { name: "json" },
}
@@ -220,6 +218,20 @@ module ActiveRecord
version >= '5.6.4'
end
+ # 5.0.0 definitely supports it, possibly supported by earlier versions but
+ # not sure
+ def supports_advisory_locks?
+ version >= '5.0.0'
+ end
+
+ def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
+ select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
+ end
+
+ def release_advisory_lock(lock_name) # :nodoc:
+ select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
+ end
+
def native_database_types
NATIVE_DATABASE_TYPES
end
@@ -483,18 +495,43 @@ module ActiveRecord
end
def tables(name = nil) # :nodoc:
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ #tables currently returns both tables and views.
+ This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
+ Use #data_sources instead.
+ MSG
+
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing arguments to #tables is deprecated without replacement.
+ MSG
+ end
+
+ data_sources
+ end
+
+ def data_sources
sql = "SELECT table_name FROM information_schema.tables "
sql << "WHERE table_schema = #{quote(@config[:database])}"
select_values(sql, 'SCHEMA')
end
- alias data_sources tables
def truncate(table_name, name = nil)
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
end
def table_exists?(table_name)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ #table_exists? currently checks both tables and views.
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
+ Use #data_source_exists? instead.
+ MSG
+
+ data_source_exists?(table_name)
+ end
+
+ def data_source_exists?(table_name)
return false unless table_name.present?
schema, name = table_name.to_s.split('.', 2)
@@ -505,7 +542,6 @@ module ActiveRecord
select_values(sql, 'SCHEMA').any?
end
- alias data_source_exists? table_exists?
def views # :nodoc:
select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA')
@@ -853,9 +889,9 @@ module ActiveRecord
def translate_exception(exception, message)
case error_number(exception)
when 1062
- RecordNotUnique.new(message, exception)
+ RecordNotUnique.new(message)
when 1452
- InvalidForeignKey.new(message, exception)
+ InvalidForeignKey.new(message)
else
super
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 29e8c73d46..ca7dfda80d 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module MySQL
module ColumnMethods
def primary_key(name, type = :primary_key, **options)
- options[:auto_increment] = true if type == :bigint
+ options[:auto_increment] = true if type == :bigint && !options.key?(:default)
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
index 3c48d0554e..9dee3172f4 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -4,8 +4,11 @@ module ActiveRecord
module ColumnDumper
def column_spec_for_primary_key(column)
spec = {}
- if column.auto_increment?
- spec[:id] = ':bigint' if column.bigint?
+ if column.bigint?
+ spec[:id] = ':bigint'
+ spec[:default] = schema_default(column) || 'nil' unless column.auto_increment?
+ spec[:unsigned] = 'true' if column.unsigned?
+ elsif column.auto_increment?
spec[:unsigned] = 'true' if column.unsigned?
return if spec.empty?
else
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 42c4a14f00..773ecbe126 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -20,7 +20,7 @@ module ActiveRecord
ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
rescue Mysql2::Error => error
if error.message.include?("Unknown database")
- raise ActiveRecord::NoDatabaseError.new(error.message, error)
+ raise ActiveRecord::NoDatabaseError
else
raise
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index fddb318553..89d18ee14e 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -38,7 +38,7 @@ module ActiveRecord
ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
rescue Mysql::Error => error
if error.message.include?("Unknown database")
- raise ActiveRecord::NoDatabaseError.new(error.message, error)
+ raise ActiveRecord::NoDatabaseError
else
raise
end
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 aaf5b2898b..a48d64f7bd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -70,6 +70,12 @@ module ActiveRecord
# Returns the list of all tables in the schema search path.
def tables(name = nil)
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing arguments to #tables is deprecated without replacement.
+ MSG
+ end
+
select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA')
end
@@ -87,6 +93,16 @@ module ActiveRecord
# If the schema is not specified as part of +name+ then it will only find tables within
# the current schema search path (regardless of permissions to access tables in other schemas)
def table_exists?(name)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ #table_exists? currently checks both tables and views.
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
+ Use #data_source_exists? instead.
+ MSG
+
+ data_source_exists?(name)
+ end
+
+ def data_source_exists?(name)
name = Utils.extract_schema_qualified_name(name.to_s)
return false unless name.identifier
@@ -99,7 +115,6 @@ module ActiveRecord
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
SQL
end
- alias data_source_exists? table_exists?
def views # :nodoc:
select_values(<<-SQL, 'SCHEMA')
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 236c067fd5..719592349b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -19,12 +19,6 @@ require 'ipaddr'
module ActiveRecord
module ConnectionHandling # :nodoc:
- VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
- :client_encoding, :options, :application_name, :fallback_application_name,
- :keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
- :tty, :sslmode, :requiressl, :sslcompression, :sslcert, :sslkey,
- :sslrootcert, :sslcrl, :requirepeer, :krbsrvname, :gsslib, :service]
-
# Establishes a connection to the database that's used by all Active Record objects
def postgresql_connection(config)
conn_params = config.symbolize_keys
@@ -36,7 +30,8 @@ module ActiveRecord
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
# Forward only valid config params to PGconn.connect.
- conn_params.keep_if { |k, _| VALID_CONN_PARAMS.include?(k) }
+ valid_conn_param_keys = PGconn.conndefaults_hash.keys + [:requiressl]
+ conn_params.slice!(*valid_conn_param_keys)
# The postgres drivers don't allow the creation of an unconnected PGconn object,
# so just pass a nil connection object for the time being.
@@ -77,7 +72,6 @@ module ActiveRecord
NATIVE_DATABASE_TYPES = {
primary_key: "serial primary key",
- bigserial: "bigserial",
string: { name: "character varying" },
text: { name: "text" },
integer: { name: "integer" },
@@ -94,7 +88,6 @@ module ActiveRecord
int8range: { name: "int8range" },
binary: { name: "bytea" },
boolean: { name: "boolean" },
- bigint: { name: "bigint" },
xml: { name: "xml" },
tsvector: { name: "tsvector" },
hstore: { name: "hstore" },
@@ -281,18 +274,18 @@ module ActiveRecord
true
end
- # Enable standard-conforming strings if available.
def set_standard_conforming_strings
- old, self.client_min_messages = client_min_messages, 'panic'
- execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
- ensure
- self.client_min_messages = old
+ execute('SET standard_conforming_strings = on', 'SCHEMA')
end
def supports_ddl_transactions?
true
end
+ def supports_advisory_locks?
+ true
+ end
+
def supports_explain?
true
end
@@ -311,6 +304,20 @@ module ActiveRecord
postgresql_version >= 90300
end
+ def get_advisory_lock(lock_id) # :nodoc:
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
+ raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
+ end
+ select_value("SELECT pg_try_advisory_lock(#{lock_id});")
+ end
+
+ def release_advisory_lock(lock_id) # :nodoc:
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
+ raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
+ end
+ select_value("SELECT pg_advisory_unlock(#{lock_id})")
+ end
+
def enable_extension(name)
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
reload_type_map
@@ -393,9 +400,9 @@ module ActiveRecord
case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
when UNIQUE_VIOLATION
- RecordNotUnique.new(message, exception)
+ RecordNotUnique.new(message)
when FOREIGN_KEY_VIOLATION
- InvalidForeignKey.new(message, exception)
+ InvalidForeignKey.new(message)
else
super
end
@@ -580,7 +587,7 @@ module ActiveRecord
@connection.exec_prepared(stmt_key, type_casted_binds)
end
rescue ActiveRecord::StatementInvalid => e
- pgerror = e.original_exception
+ pgerror = e.cause
# Get the PG code for the failure. Annoyingly, the code for
# prepared statements whose return value may have changed is
@@ -636,7 +643,7 @@ module ActiveRecord
configure_connection
rescue ::PG::Error => error
if error.message.include?("does not exist")
- raise ActiveRecord::NoDatabaseError.new(error.message, error)
+ raise ActiveRecord::NoDatabaseError
else
raise
end
@@ -651,7 +658,7 @@ module ActiveRecord
self.client_min_messages = @config[:min_messages] || 'warning'
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
- # Use standard-conforming strings if available so we don't have to do the E'...' dance.
+ # Use standard-conforming strings so we don't have to do the E'...' dance.
set_standard_conforming_strings
# If using Active Record's time zone support configure the connection to return
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 9028c1fcb9..90df9b8825 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -33,7 +33,7 @@ module ActiveRecord
ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
rescue Errno::ENOENT => error
if error.message.include?("No such file or directory")
- raise ActiveRecord::NoDatabaseError.new(error.message, error)
+ raise ActiveRecord::NoDatabaseError
else
raise
end
@@ -312,11 +312,36 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
def tables(name = nil) # :nodoc:
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ #tables currently returns both tables and views.
+ This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
+ Use #data_sources instead.
+ MSG
+
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing arguments to #tables is deprecated without replacement.
+ MSG
+ end
+
+ data_sources
+ end
+
+ def data_sources
select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", 'SCHEMA')
end
- alias data_sources tables
def table_exists?(table_name)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ #table_exists? currently checks both tables and views.
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
+ Use #data_source_exists? instead.
+ MSG
+
+ data_source_exists?(table_name)
+ end
+
+ def data_source_exists?(table_name)
return false unless table_name.present?
sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
@@ -324,7 +349,6 @@ module ActiveRecord
select_values(sql, 'SCHEMA').any?
end
- alias data_source_exists? table_exists?
def views # :nodoc:
select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", 'SCHEMA')
@@ -559,7 +583,7 @@ module ActiveRecord
# Older versions of SQLite return:
# column *column_name* is not unique
when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
- RecordNotUnique.new(message, exception)
+ RecordNotUnique.new(message)
else
super
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 142b6e8599..1250f8a3c3 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -193,8 +193,8 @@ module ActiveRecord
}
begin
statement.execute(hash.values, self, connection).first
- rescue TypeError => e
- raise ActiveRecord::StatementInvalid.new(e.message, e)
+ rescue TypeError
+ raise ActiveRecord::StatementInvalid
rescue RangeError
nil
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 8fba6fcc35..7ded96f8fb 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -104,7 +104,7 @@ module ActiveRecord
super
end
- class EnumType < Type::Value
+ class EnumType < Type::Value # :nodoc:
def initialize(name, mapping)
@name = name
@mapping = mapping
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 533c86a6a9..1cd2c2ef8c 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -94,13 +94,21 @@ module ActiveRecord
# Superclass for all database execution errors.
#
- # Wraps the underlying database error as +original_exception+.
+ # Wraps the underlying database error as +cause+.
class StatementInvalid < ActiveRecordError
- attr_reader :original_exception
def initialize(message = nil, original_exception = nil)
- @original_exception = original_exception
- super(message)
+ if original_exception
+ ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
+ "Exceptions will automatically capture the original exception.", caller)
+ end
+
+ super(message || $!.try(:message))
+ end
+
+ def original_exception
+ ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
+ cause
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 17e7c828b9..deb7d7d06d 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -875,9 +875,7 @@ module ActiveRecord
self.pre_loaded_fixtures = false
self.config = ActiveRecord::Base
- self.fixture_class_names = Hash.new do |h, fixture_set_name|
- h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name, self.config)
- end
+ self.fixture_class_names = {}
silence_warnings do
define_singleton_method :use_transactional_tests do
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index c26842014d..589c70db0d 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -55,7 +55,7 @@ module ActiveRecord
subclass = subclass_from_attributes(attrs)
end
- if subclass
+ if subclass && subclass != self
subclass.new(*args, &block)
else
super
@@ -167,17 +167,23 @@ module ActiveRecord
end
def find_sti_class(type_name)
- if store_full_sti_class
- ActiveSupport::Dependencies.constantize(type_name)
- else
- compute_type(type_name)
+ subclass = begin
+ if store_full_sti_class
+ ActiveSupport::Dependencies.constantize(type_name)
+ else
+ compute_type(type_name)
+ end
+ rescue NameError
+ raise SubclassNotFound,
+ "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " \
+ "or overwrite #{name}.inheritance_column to use another column for that information."
+ end
+ unless subclass == self || descendants.include?(subclass)
+ raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}"
end
- rescue NameError
- raise SubclassNotFound,
- "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
- "or overwrite #{name}.inheritance_column to use another column for that information."
+ subclass
end
def type_condition(table = arel_table)
@@ -199,15 +205,7 @@ module ActiveRecord
subclass_name = attrs.with_indifferent_access[inheritance_column]
if subclass_name.present?
- subclass = find_sti_class(subclass_name)
-
- if subclass.name != self.name
- unless descendants.include?(subclass)
- raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}")
- end
-
- subclass
- end
+ find_sti_class(subclass_name)
end
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index c8b96b8de0..ca2537cdc3 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -135,6 +135,14 @@ module ActiveRecord
end
end
+ class ConcurrentMigrationError < MigrationError #:nodoc:
+ DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running.".freeze
+
+ def initialize(message = DEFAULT_MESSAGE)
+ super
+ end
+ end
+
# = Active Record Migrations
#
# Migrations can manage the evolution of a schema used by several physical
@@ -469,6 +477,7 @@ module ActiveRecord
class Migration
autoload :CommandRecorder, 'active_record/migration/command_recorder'
+ MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc:
# This class is used to verify that all migrations have been run before
# loading a web page if config.active_record.migration_error is set to :page_load
@@ -958,10 +967,12 @@ module ActiveRecord
end
def get_all_versions(connection = Base.connection)
- if connection.table_exists?(schema_migrations_table_name)
- SchemaMigration.all.map { |x| x.version.to_i }.sort
- else
- []
+ ActiveSupport::Deprecation.silence do
+ if connection.table_exists?(schema_migrations_table_name)
+ SchemaMigration.all.map { |x| x.version.to_i }.sort
+ else
+ []
+ end
end
end
@@ -987,14 +998,21 @@ module ActiveRecord
Array(@migrations_paths)
end
+ def match_to_migration_filename?(filename) # :nodoc:
+ File.basename(filename) =~ Migration::MigrationFilenameRegexp
+ end
+
+ def parse_migration_filename(filename) # :nodoc:
+ File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
+ end
+
def migrations(paths)
paths = Array(paths)
files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }]
migrations = files.map do |file|
- version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/).first
-
+ version, name, scope = parse_migration_filename(file)
raise IllegalMigrationNameError.new(file) unless version
version = version.to_i
name = name.camelize
@@ -1042,32 +1060,18 @@ module ActiveRecord
alias :current :current_migration
def run
- migration = migrations.detect { |m| m.version == @target_version }
- raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
- unless (up? && migrated.include?(migration.version.to_i)) || (down? && !migrated.include?(migration.version.to_i))
- begin
- execute_migration_in_transaction(migration, @direction)
- rescue => e
- canceled_msg = use_transaction?(migration) ? ", this migration was canceled" : ""
- raise StandardError, "An error has occurred#{canceled_msg}:\n\n#{e}", e.backtrace
- end
+ if use_advisory_lock?
+ with_advisory_lock { run_without_lock }
+ else
+ run_without_lock
end
end
def migrate
- if !target && @target_version && @target_version > 0
- raise UnknownMigrationVersionError.new(@target_version)
- end
-
- runnable.each do |migration|
- Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
-
- begin
- execute_migration_in_transaction(migration, @direction)
- rescue => e
- canceled_msg = use_transaction?(migration) ? "this and " : ""
- raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
- end
+ if use_advisory_lock?
+ with_advisory_lock { migrate_without_lock }
+ else
+ migrate_without_lock
end
end
@@ -1092,10 +1096,45 @@ module ActiveRecord
end
def migrated
- @migrated_versions ||= Set.new(self.class.get_all_versions)
+ @migrated_versions || load_migrated
+ end
+
+ def load_migrated
+ @migrated_versions = Set.new(self.class.get_all_versions)
end
private
+
+ def run_without_lock
+ migration = migrations.detect { |m| m.version == @target_version }
+ raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
+ unless (up? && migrated.include?(migration.version.to_i)) || (down? && !migrated.include?(migration.version.to_i))
+ begin
+ execute_migration_in_transaction(migration, @direction)
+ rescue => e
+ canceled_msg = use_transaction?(migration) ? ", this migration was canceled" : ""
+ raise StandardError, "An error has occurred#{canceled_msg}:\n\n#{e}", e.backtrace
+ end
+ end
+ end
+
+ def migrate_without_lock
+ if !target && @target_version && @target_version > 0
+ raise UnknownMigrationVersionError.new(@target_version)
+ end
+
+ runnable.each do |migration|
+ Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
+
+ begin
+ execute_migration_in_transaction(migration, @direction)
+ rescue => e
+ canceled_msg = use_transaction?(migration) ? "this and " : ""
+ raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
+ end
+ end
+ end
+
def ran?(migration)
migrated.include?(migration.version.to_i)
end
@@ -1157,5 +1196,25 @@ module ActiveRecord
def use_transaction?(migration)
!migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions?
end
+
+ def use_advisory_lock?
+ Base.connection.supports_advisory_locks?
+ end
+
+ def with_advisory_lock
+ lock_id = generate_migrator_advisory_lock_id
+ got_lock = Base.connection.get_advisory_lock(lock_id)
+ raise ConcurrentMigrationError unless got_lock
+ load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock
+ yield
+ ensure
+ Base.connection.release_advisory_lock(lock_id) if got_lock
+ end
+
+ MIGRATOR_SALT = 2053462845
+ def generate_migrator_advisory_lock_id
+ db_name_hash = Zlib.crc32(Base.connection.current_database)
+ MIGRATOR_SALT * db_name_hash
+ end
end
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index a9bd094a66..e3f304b0af 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -339,6 +339,9 @@ module ActiveRecord
@columns = nil
@columns_hash = nil
@attribute_names = nil
+ direct_descendants.each do |descendant|
+ descendant.send(:reload_schema_from_cache)
+ end
end
# Guesses the table name, but does not decorate it with prefix and suffix information.
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 87a1988f2f..1f429cfd94 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -7,7 +7,7 @@ module ActiveRecord
delegate :find_by, :find_by!, to: :all
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all
delegate :find_each, :find_in_batches, :in_batches, to: :all
- delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :or,
+ delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or,
:where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly,
:having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all
delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d940ac244a..5b1ea16f0b 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -34,7 +34,7 @@ db_namespace = namespace :db do
end
end
- # desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases."
+ # desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:purge:all to purge all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases."
task :purge => [:load_config] do
ActiveRecord::Tasks::DatabaseTasks.purge_current
end
@@ -100,12 +100,14 @@ db_namespace = namespace :db do
file_list =
ActiveRecord::Tasks::DatabaseTasks.migrations_paths.flat_map do |path|
- # match "20091231235959_some_name.rb" and "001_some_name.rb" pattern
- Dir.foreach(path).grep(/^(\d{3,})_(.+)\.rb$/) do
- version = ActiveRecord::SchemaMigration.normalize_migration_number($1)
+ Dir.foreach(path).map do |file|
+ next unless ActiveRecord::Migrator.match_to_migration_filename?(file)
+
+ version, name, scope = ActiveRecord::Migrator.parse_migration_filename(file)
+ version = ActiveRecord::SchemaMigration.normalize_migration_number(version)
status = db_list.delete(version) ? 'up' : 'down'
- [status, version, $2.humanize]
- end
+ [status, version, (name + scope).humanize]
+ end.compact
end
db_list.map! do |version|
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 5b9d45d871..a549b28f16 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -371,7 +371,7 @@ module ActiveRecord
end
def foreign_key
- @foreign_key ||= options[:foreign_key] || derive_foreign_key
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
end
def association_foreign_key
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 392b462aa9..f100476374 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -4,7 +4,7 @@ module ActiveRecord
# = Active Record \Relation
class Relation
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
- :order, :joins, :references,
+ :order, :joins, :left_joins, :left_outer_joins, :references,
:extending, :unscope]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index f5afc1000d..2dc52982c9 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -428,6 +428,27 @@ module ActiveRecord
self
end
+ # Performs a left outer joins on +args+:
+ #
+ # User.left_outer_joins(:posts)
+ # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
+ #
+ def left_outer_joins(*args)
+ check_if_method_has_arguments!(:left_outer_joins, args)
+
+ args.compact!
+ args.flatten!
+
+ spawn.left_outer_joins!(*args)
+ end
+ alias :left_joins :left_outer_joins
+
+ def left_outer_joins!(*args) # :nodoc:
+ self.left_outer_joins_values += args
+ self
+ end
+ alias :left_joins! :left_outer_joins!
+
# Returns a new relation, which is the result of filtering the current relation
# according to the conditions in the arguments.
#
@@ -878,6 +899,7 @@ module ActiveRecord
arel = Arel::SelectManager.new(table)
build_joins(arel, joins_values.flatten) unless joins_values.empty?
+ build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty?
arel.where(where_clause.ast) unless where_clause.empty?
arel.having(having_clause.ast) unless having_clause.empty?
@@ -937,6 +959,19 @@ module ActiveRecord
end
end
+ def build_left_outer_joins(manager, outer_joins)
+ buckets = outer_joins.group_by do |join|
+ case join
+ when Hash, Symbol, Array
+ :association_join
+ else
+ raise ArgumentError, 'only Hash, Symbol and Array are allowed'
+ end
+ end
+
+ build_join_query(manager, buckets, Arel::Nodes::OuterJoin)
+ end
+
def build_joins(manager, joins)
buckets = joins.group_by do |join|
case join
@@ -952,6 +987,11 @@ module ActiveRecord
raise 'unknown class: %s' % join.class.name
end
end
+
+ build_join_query(manager, buckets, Arel::Nodes::InnerJoin)
+ end
+
+ def build_join_query(manager, buckets, join_type)
buckets.default = []
association_joins = buckets[:association_join]
@@ -967,7 +1007,7 @@ module ActiveRecord
join_list
)
- join_infos = join_dependency.join_constraints stashed_association_joins
+ join_infos = join_dependency.join_constraints stashed_association_joins, join_type
join_infos.each do |info|
info.joins.each { |join| manager.from(join) }
@@ -1045,6 +1085,9 @@ module ActiveRecord
end
def preprocess_order_args(order_args)
+ order_args.map! do |arg|
+ klass.send(:sanitize_sql_for_order, arg)
+ end
order_args.flatten!
validate_order_args(order_args)
diff --git a/activerecord/lib/active_record/relation/record_fetch_warning.rb b/activerecord/lib/active_record/relation/record_fetch_warning.rb
index 14e1bf89fa..0a1814b3dd 100644
--- a/activerecord/lib/active_record/relation/record_fetch_warning.rb
+++ b/activerecord/lib/active_record/relation/record_fetch_warning.rb
@@ -23,11 +23,13 @@ module ActiveRecord
end
end
+ # :stopdoc:
ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
payload = args.last
QueryRegistry.queries << payload[:sql]
end
+ # :startdoc:
class QueryRegistry # :nodoc:
extend ActiveSupport::PerThreadRegistry
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 5c3318651a..67d7f83cb4 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -12,6 +12,7 @@ module ActiveRecord
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
# Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
+ #
# Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
# # Performs a single join query with both where conditions.
#
@@ -37,11 +38,14 @@ module ActiveRecord
end
def merge!(other) # :nodoc:
- if !other.is_a?(Relation) && other.respond_to?(:to_proc)
+ if other.is_a?(Hash)
+ Relation::HashMerger.new(self, other).merge
+ elsif other.is_a?(Relation)
+ Relation::Merger.new(self, other).merge
+ elsif other.respond_to?(:to_proc)
instance_exec(&other)
else
- klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger
- klass.new(self, other).merge
+ raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation"
end
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 1cf4b09bf3..0c15f45db9 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -53,6 +53,22 @@ module ActiveRecord
end
end
+ # Accepts an array, or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for a ORDER clause.
+ #
+ # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
+ # # => "field(id, 1,3,2)"
+ #
+ # sanitize_sql_for_order("id ASC")
+ # # => "id ASC"
+ def sanitize_sql_for_order(condition)
+ if condition.is_a?(Array) && condition.first.to_s.include?('?')
+ sanitize_sql_array(condition)
+ else
+ condition
+ end
+ end
+
# Accepts a hash of SQL conditions and replaces those attributes
# that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of]
# relationship with their expanded aggregate attribute values.
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index b384529e75..51b9b17395 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -21,7 +21,7 @@ module ActiveRecord
end
def table_exists?
- connection.table_exists?(table_name)
+ ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) }
end
def create_table(limit=nil)
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index ea7927a435..c0c29a618c 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -94,8 +94,9 @@ module ActiveRecord
rescue DatabaseAlreadyExists
$stderr.puts "#{configuration['database']} already exists"
rescue Exception => error
- $stderr.puts error, *(error.backtrace)
+ $stderr.puts error
$stderr.puts "Couldn't create database for #{configuration.inspect}"
+ raise
end
def create_all
@@ -115,8 +116,9 @@ module ActiveRecord
rescue ActiveRecord::NoDatabaseError
$stderr.puts "Database '#{configuration['database']}' does not exist"
rescue Exception => error
- $stderr.puts error, *(error.backtrace)
+ $stderr.puts error
$stderr.puts "Couldn't drop #{configuration['database']}"
+ raise
end
def drop_all
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 8929aa85c8..100df9c6c6 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -1,8 +1,6 @@
module ActiveRecord
module Tasks # :nodoc:
class MySQLDatabaseTasks # :nodoc:
- DEFAULT_CHARSET = ENV['CHARSET'] || 'utf8'
- DEFAULT_COLLATION = ENV['COLLATION'] || 'utf8_unicode_ci'
ACCESS_DENIED_ERROR = 1045
delegate :connection, :establish_connection, to: ActiveRecord::Base
@@ -87,12 +85,6 @@ module ActiveRecord
Hash.new.tap do |options|
options[:charset] = configuration['encoding'] if configuration.include? 'encoding'
options[:collation] = configuration['collation'] if configuration.include? 'collation'
-
- # Set default charset only when collation isn't set.
- options[:charset] ||= DEFAULT_CHARSET unless options[:collation]
-
- # Set default collation only when charset is also default.
- options[:collation] ||= DEFAULT_COLLATION if options[:charset] == DEFAULT_CHARSET
end
end
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
index 9ab64d0325..9ec3c8a94a 100644
--- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -19,11 +19,15 @@ module ActiveRecord
path = Pathname.new configuration['database']
file = path.absolute? ? path.to_s : File.join(root, path)
- FileUtils.rm(file) if File.exist?(file)
+ FileUtils.rm(file)
+ rescue Errno::ENOENT => error
+ raise NoDatabaseError.new(error.message, error)
end
def purge
drop
+ rescue NoDatabaseError
+ ensure
create
end
diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb
index 81d7ed39bb..850a7a4e09 100644
--- a/activerecord/lib/active_record/type/type_map.rb
+++ b/activerecord/lib/active_record/type/type_map.rb
@@ -1,4 +1,4 @@
-require 'concurrent'
+require 'concurrent/map'
module ActiveRecord
module Type
diff --git a/activerecord/lib/active_record/type_caster.rb b/activerecord/lib/active_record/type_caster.rb
index 63ba10c289..accc339d00 100644
--- a/activerecord/lib/active_record/type_caster.rb
+++ b/activerecord/lib/active_record/type_caster.rb
@@ -2,6 +2,6 @@ require 'active_record/type_caster/map'
require 'active_record/type_caster/connection'
module ActiveRecord
- module TypeCaster
+ module TypeCaster # :nodoc:
end
end
diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb
index 868d08ed44..7ed8dcc313 100644
--- a/activerecord/lib/active_record/type_caster/connection.rb
+++ b/activerecord/lib/active_record/type_caster/connection.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module TypeCaster
- class Connection
+ class Connection # :nodoc:
def initialize(klass, table_name)
@klass = klass
@table_name = table_name
diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb
index 4b1941351c..3a367b3999 100644
--- a/activerecord/lib/active_record/type_caster/map.rb
+++ b/activerecord/lib/active_record/type_caster/map.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module TypeCaster
- class Map
+ class Map # :nodoc:
def initialize(types)
@types = types
end
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index 32fbaf0a91..b14db85167 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -2,10 +2,16 @@ module ActiveRecord
module Validations
class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
def validate_each(record, attribute, value)
- if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any?
- record.errors.add(attribute, :invalid, options.merge(:value => value))
+ if Array(value).reject { |r| valid_object?(r) }.any?
+ record.errors.add(attribute, :invalid, options.merge(value: value))
end
end
+
+ private
+
+ def valid_object?(record)
+ (record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid?
+ end
end
module ClassMethods
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 62579a4a7a..abe1ea7c90 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -23,7 +23,8 @@ module ActiveRecord
end
def test_tables
- tables = @connection.tables
+ tables = nil
+ ActiveSupport::Deprecation.silence { tables = @connection.tables }
assert tables.include?("accounts")
assert tables.include?("authors")
assert tables.include?("tasks")
@@ -31,9 +32,15 @@ module ActiveRecord
end
def test_table_exists?
- assert @connection.table_exists?("accounts")
- assert !@connection.table_exists?("nonexistingtable")
- assert !@connection.table_exists?(nil)
+ ActiveSupport::Deprecation.silence do
+ assert @connection.table_exists?("accounts")
+ assert !@connection.table_exists?("nonexistingtable")
+ assert !@connection.table_exists?(nil)
+ end
+ end
+
+ def test_table_exists_checking_both_tables_and_views_is_deprecated
+ assert_deprecated { @connection.table_exists?("accounts") }
end
def test_data_sources
@@ -151,14 +158,16 @@ module ActiveRecord
def test_uniqueness_violations_are_translated_to_specific_exception
@connection.execute "INSERT INTO subscribers(nick) VALUES('me')"
- assert_raises(ActiveRecord::RecordNotUnique) do
+ error = assert_raises(ActiveRecord::RecordNotUnique) do
@connection.execute "INSERT INTO subscribers(nick) VALUES('me')"
end
+
+ assert_not_nil error.cause
end
unless current_adapter?(:SQLite3Adapter)
def test_foreign_key_violations_are_translated_to_specific_exception
- assert_raises(ActiveRecord::InvalidForeignKey) do
+ 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"))
@@ -167,6 +176,8 @@ module ActiveRecord
@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
@@ -174,11 +185,13 @@ module ActiveRecord
self.table_name = 'fk_test_has_fk'
end
- assert_raises(ActiveRecord::InvalidForeignKey) do
+ 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
end
@@ -231,13 +244,25 @@ module ActiveRecord
unless current_adapter?(:PostgreSQLAdapter)
def test_log_invalid_encoding
- assert_raise ActiveRecord::StatementInvalid do
+ error = assert_raise ActiveRecord::StatementInvalid do
@connection.send :log, "SELECT 'Ñ‹' FROM DUAL" do
raise 'Ñ‹'.force_encoding(Encoding::ASCII_8BIT)
end
end
+
+ assert_not_nil error.cause
+ end
+ end
+
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :SQLite3Adapter)
+ def test_tables_returning_both_tables_and_views_is_deprecated
+ assert_deprecated { @connection.tables }
end
end
+
+ def test_passing_arguments_to_tables_is_deprecated
+ assert_deprecated { @connection.tables(:books) }
+ end
end
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index decac9e83b..390dd15b92 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -170,6 +170,34 @@ class MysqlConnectionTest < ActiveRecord::MysqlTestCase
end
end
+ def test_get_and_release_advisory_lock
+ lock_name = "test_lock_name"
+
+ got_lock = @connection.get_advisory_lock(lock_name)
+ assert got_lock, "get_advisory_lock should have returned true but it didn't"
+
+ assert_equal test_lock_free(lock_name), false,
+ "expected the test advisory lock to be held but it wasn't"
+
+ released_lock = @connection.release_advisory_lock(lock_name)
+ assert released_lock, "expected release_advisory_lock to return true but it didn't"
+
+ assert test_lock_free(lock_name), 'expected the test lock to be available after releasing'
+ end
+
+ def test_release_non_existent_advisory_lock
+ lock_name = "fake_lock_name"
+ released_non_existent_lock = @connection.release_advisory_lock(lock_name)
+ assert_equal released_non_existent_lock, false,
+ 'expected release_advisory_lock to return false when there was no lock to release'
+ end
+
+ protected
+
+ def test_lock_free(lock_name)
+ @connection.select_value("SELECT IS_FREE_LOCK('#{lock_name}');") == '1'
+ end
+
private
def with_example_table(&block)
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index 2e18f609fd..14dbdd375b 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -14,38 +14,30 @@ module ActiveRecord
@db_name = db
@omgpost = Class.new(ActiveRecord::Base) do
+ self.inheritance_column = :disabled
self.table_name = "#{db}.#{table}"
def self.name; 'Post'; end
end
-
- @connection.create_table "mysql_doubles"
- end
-
- teardown do
- @connection.drop_table "mysql_doubles", if_exists: true
- end
-
- class MysqlDouble < ActiveRecord::Base
- self.table_name = "mysql_doubles"
end
def test_float_limits
- @connection.add_column :mysql_doubles, :float_no_limit, :float
- @connection.add_column :mysql_doubles, :float_short, :float, limit: 5
- @connection.add_column :mysql_doubles, :float_long, :float, limit: 53
-
- @connection.add_column :mysql_doubles, :float_23, :float, limit: 23
- @connection.add_column :mysql_doubles, :float_24, :float, limit: 24
- @connection.add_column :mysql_doubles, :float_25, :float, limit: 25
- MysqlDouble.reset_column_information
+ @connection.create_table :mysql_doubles do |t|
+ t.float :float_no_limit
+ t.float :float_short, limit: 5
+ t.float :float_long, limit: 53
+
+ t.float :float_23, limit: 23
+ t.float :float_24, limit: 24
+ t.float :float_25, limit: 25
+ end
- column_no_limit = MysqlDouble.columns.find { |c| c.name == 'float_no_limit' }
- column_short = MysqlDouble.columns.find { |c| c.name == 'float_short' }
- column_long = MysqlDouble.columns.find { |c| c.name == 'float_long' }
+ column_no_limit = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_no_limit' }
+ column_short = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_short' }
+ column_long = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_long' }
- column_23 = MysqlDouble.columns.find { |c| c.name == 'float_23' }
- column_24 = MysqlDouble.columns.find { |c| c.name == 'float_24' }
- column_25 = MysqlDouble.columns.find { |c| c.name == 'float_25' }
+ column_23 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_23' }
+ column_24 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_24' }
+ column_25 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_25' }
# Mysql floats are precision 0..24, Mysql doubles are precision 25..53
assert_equal 24, column_no_limit.limit
@@ -55,6 +47,8 @@ module ActiveRecord
assert_equal 24, column_23.limit
assert_equal 24, column_24.limit
assert_equal 53, column_25.limit
+ ensure
+ @connection.drop_table "mysql_doubles", if_exists: true
end
def test_schema
@@ -65,13 +59,13 @@ module ActiveRecord
assert_equal 'id', @omgpost.primary_key
end
- def test_table_exists?
+ def test_data_source_exists?
name = @omgpost.table_name
- assert @connection.table_exists?(name), "#{name} table should exist"
+ assert @connection.data_source_exists?(name), "#{name} data_source should exist"
end
- def test_table_exists_wrong_schema
- assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
+ def test_data_source_exists_wrong_schema
+ assert(!@connection.data_source_exists?("#{@db_name}.zomg"), "data_source should not exist")
end
def test_dump_indexes
diff --git a/activerecord/test/cases/adapters/mysql/sp_test.rb b/activerecord/test/cases/adapters/mysql/sp_test.rb
index 579c3273c6..7849248dcc 100644
--- a/activerecord/test/cases/adapters/mysql/sp_test.rb
+++ b/activerecord/test/cases/adapters/mysql/sp_test.rb
@@ -7,23 +7,24 @@ class MysqlStoredProcedureTest < ActiveRecord::MysqlTestCase
def setup
@connection = ActiveRecord::Base.connection
+ unless ActiveRecord::Base.connection.version >= '5.6.0' || Mysql.const_defined?(:CLIENT_MULTI_RESULTS)
+ skip("no stored procedure support")
+ end
end
# Test that MySQL allows multiple results for stored procedures
#
# In MySQL 5.6, CLIENT_MULTI_RESULTS is enabled by default.
# http://dev.mysql.com/doc/refman/5.6/en/call.html
- if ActiveRecord::Base.connection.version >= '5.6.0' || Mysql.const_defined?(:CLIENT_MULTI_RESULTS)
- def test_multi_results
- rows = @connection.select_rows('CALL ten();')
- assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
- assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'"
- end
+ def test_multi_results
+ rows = @connection.select_rows('CALL ten();')
+ assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
+ assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'"
+ end
- def test_multi_results_from_find_by_sql
- topics = Topic.find_by_sql 'CALL topics(3);'
- assert_equal 3, topics.size
- assert @connection.active?, "Bad connection use by 'MysqlAdapter.select'"
- end
+ def test_multi_results_from_find_by_sql
+ topics = Topic.find_by_sql 'CALL topics(3);'
+ assert_equal 3, topics.size
+ assert @connection.active?, "Bad connection use by 'MysqlAdapter.select'"
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 31dc69a45b..b97eb3e228 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -17,7 +17,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def test_add_index
# add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed
- def (ActiveRecord::Base.connection).table_exists?(*); true; end
+ def (ActiveRecord::Base.connection).data_source_exists?(*); true; end
def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
@@ -60,7 +60,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end
def test_index_in_create
- def (ActiveRecord::Base.connection).table_exists?(*); false; end
+ def (ActiveRecord::Base.connection).data_source_exists?(*); false; end
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB"
@@ -78,7 +78,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end
def test_index_in_bulk_change
- def (ActiveRecord::Base.connection).table_exists?(*); true; end
+ def (ActiveRecord::Base.connection).data_source_exists?(*); true; end
def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
@@ -152,7 +152,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end
def test_indexes_in_create
- ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false)
+ ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false)
ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false)
expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query"
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 000bcadebe..507d024bb6 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -131,4 +131,32 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
ensure
@connection.execute "DROP TABLE `bar_baz`"
end
+
+ def test_get_and_release_advisory_lock
+ lock_name = "test_lock_name"
+
+ got_lock = @connection.get_advisory_lock(lock_name)
+ assert got_lock, "get_advisory_lock should have returned true but it didn't"
+
+ assert_equal test_lock_free(lock_name), false,
+ "expected the test advisory lock to be held but it wasn't"
+
+ released_lock = @connection.release_advisory_lock(lock_name)
+ assert released_lock, "expected release_advisory_lock to return true but it didn't"
+
+ assert test_lock_free(lock_name), 'expected the test lock to be available after releasing'
+ end
+
+ def test_release_non_existent_advisory_lock
+ lock_name = "fake_lock_name"
+ released_non_existent_lock = @connection.release_advisory_lock(lock_name)
+ assert_equal released_non_existent_lock, false,
+ 'expected release_advisory_lock to return false when there was no lock to release'
+ end
+
+ protected
+
+ def test_lock_free(lock_name)
+ @connection.select_value("SELECT IS_FREE_LOCK('#{lock_name}');") == 1
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index faf2acb9cb..43957791b1 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -14,11 +14,43 @@ module ActiveRecord
@db_name = db
@omgpost = Class.new(ActiveRecord::Base) do
+ self.inheritance_column = :disabled
self.table_name = "#{db}.#{table}"
def self.name; 'Post'; end
end
end
+ def test_float_limits
+ @connection.create_table :mysql_doubles do |t|
+ t.float :float_no_limit
+ t.float :float_short, limit: 5
+ t.float :float_long, limit: 53
+
+ t.float :float_23, limit: 23
+ t.float :float_24, limit: 24
+ t.float :float_25, limit: 25
+ end
+
+ column_no_limit = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_no_limit' }
+ column_short = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_short' }
+ column_long = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_long' }
+
+ column_23 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_23' }
+ column_24 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_24' }
+ column_25 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_25' }
+
+ # Mysql floats are precision 0..24, Mysql doubles are precision 25..53
+ assert_equal 24, column_no_limit.limit
+ assert_equal 24, column_short.limit
+ assert_equal 53, column_long.limit
+
+ assert_equal 24, column_23.limit
+ assert_equal 24, column_24.limit
+ assert_equal 53, column_25.limit
+ ensure
+ @connection.drop_table "mysql_doubles", if_exists: true
+ end
+
def test_schema
assert @omgpost.first
end
@@ -27,13 +59,13 @@ module ActiveRecord
assert_equal 'id', @omgpost.primary_key
end
- def test_table_exists?
+ def test_data_source_exists?
name = @omgpost.table_name
- assert @connection.table_exists?(name), "#{name} table should exist"
+ assert @connection.data_source_exists?(name), "#{name} data_source should exist"
end
- def test_table_exists_wrong_schema
- assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
+ def test_data_source_exists_wrong_schema
+ assert(!@connection.data_source_exists?("#{@db_name}.zomg"), "data_source should not exist")
end
def test_dump_indexes
diff --git a/activerecord/test/cases/adapters/mysql2/sp_test.rb b/activerecord/test/cases/adapters/mysql2/sp_test.rb
index 8b12945f24..cdaa2cca44 100644
--- a/activerecord/test/cases/adapters/mysql2/sp_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/sp_test.rb
@@ -7,23 +7,24 @@ class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase
def setup
@connection = ActiveRecord::Base.connection
+ unless ActiveRecord::Base.connection.version >= '5.6.0'
+ skip("no stored procedure support")
+ end
end
# Test that MySQL allows multiple results for stored procedures
#
# In MySQL 5.6, CLIENT_MULTI_RESULTS is enabled by default.
# http://dev.mysql.com/doc/refman/5.6/en/call.html
- if ActiveRecord::Base.connection.version >= '5.6.0'
- def test_multi_results
- rows = @connection.select_rows('CALL ten();')
- assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
- assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_rows'"
- end
+ def test_multi_results
+ rows = @connection.select_rows('CALL ten();')
+ assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
+ assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_rows'"
+ end
- def test_multi_results_from_find_by_sql
- topics = Topic.find_by_sql 'CALL topics(3);'
- assert_equal 3, topics.size
- assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select'"
- end
+ def test_multi_results_from_find_by_sql
+ topics = Topic.find_by_sql 'CALL topics(3);'
+ assert_equal 3, topics.size
+ assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select'"
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 722e2377c1..d559de3e28 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -90,7 +90,7 @@ module ActiveRecord
end
def test_tables_logs_name
- @connection.tables('hello')
+ ActiveSupport::Deprecation.silence { @connection.tables('hello') }
assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
@@ -100,7 +100,7 @@ module ActiveRecord
end
def test_table_exists_logs_name
- @connection.table_exists?('items')
+ ActiveSupport::Deprecation.silence { @connection.table_exists?('items') }
assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
@@ -209,5 +209,47 @@ module ActiveRecord
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => :default}}))
end
end
+
+ def test_get_and_release_advisory_lock
+ lock_id = 5295901941911233559
+ list_advisory_locks = <<-SQL
+ SELECT locktype,
+ (classid::bigint << 32) | objid::bigint AS lock_id
+ FROM pg_locks
+ WHERE locktype = 'advisory'
+ SQL
+
+ got_lock = @connection.get_advisory_lock(lock_id)
+ assert got_lock, "get_advisory_lock should have returned true but it didn't"
+
+ advisory_lock = @connection.query(list_advisory_locks).find {|l| l[1] == lock_id}
+ assert advisory_lock,
+ "expected to find an advisory lock with lock_id #{lock_id} but there wasn't one"
+
+ released_lock = @connection.release_advisory_lock(lock_id)
+ assert released_lock, "expected release_advisory_lock to return true but it didn't"
+
+ advisory_locks = @connection.query(list_advisory_locks).select {|l| l[1] == lock_id}
+ assert_empty advisory_locks,
+ "expected to have released advisory lock with lock_id #{lock_id} but it was still held"
+ end
+
+ def test_release_non_existent_advisory_lock
+ fake_lock_id = 2940075057017742022
+ with_warning_suppression do
+ released_non_existent_lock = @connection.release_advisory_lock(fake_lock_id)
+ assert_equal released_non_existent_lock, false,
+ 'expected release_advisory_lock to return false when there was no lock to release'
+ end
+ end
+
+ protected
+
+ def with_warning_suppression
+ log_level = @connection.client_min_messages
+ @connection.client_min_messages = 'error'
+ yield
+ @connection.client_min_messages = log_level
+ end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 93e98ec872..7c9169f6e2 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -184,42 +184,42 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
@connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered
end
- def test_table_exists?
+ def test_data_source_exists?
[Thing1, Thing2, Thing3, Thing4].each do |klass|
name = klass.table_name
- assert @connection.table_exists?(name), "'#{name}' table should exist"
+ assert @connection.data_source_exists?(name), "'#{name}' data_source should exist"
end
end
- def test_table_exists_when_on_schema_search_path
+ def test_data_source_exists_when_on_schema_search_path
with_schema_search_path(SCHEMA_NAME) do
- assert(@connection.table_exists?(TABLE_NAME), "table should exist and be found")
+ assert(@connection.data_source_exists?(TABLE_NAME), "data_source should exist and be found")
end
end
- def test_table_exists_when_not_on_schema_search_path
+ def test_data_source_exists_when_not_on_schema_search_path
with_schema_search_path('PUBLIC') do
- assert(!@connection.table_exists?(TABLE_NAME), "table exists but should not be found")
+ assert(!@connection.data_source_exists?(TABLE_NAME), "data_source exists but should not be found")
end
end
- def test_table_exists_wrong_schema
- assert(!@connection.table_exists?("foo.things"), "table should not exist")
+ def test_data_source_exists_wrong_schema
+ assert(!@connection.data_source_exists?("foo.things"), "data_source should not exist")
end
- def test_table_exists_quoted_names
+ def test_data_source_exists_quoted_names
[ %("#{SCHEMA_NAME}"."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}")].each do |given|
- assert(@connection.table_exists?(given), "table should exist when specified as #{given}")
+ assert(@connection.data_source_exists?(given), "data_source should exist when specified as #{given}")
end
with_schema_search_path(SCHEMA_NAME) do
given = %("#{TABLE_NAME}")
- assert(@connection.table_exists?(given), "table should exist when specified as #{given}")
+ assert(@connection.data_source_exists?(given), "data_source should exist when specified as #{given}")
end
end
- def test_table_exists_quoted_table
+ def test_data_source_exists_quoted_table
with_schema_search_path(SCHEMA_NAME) do
- assert(@connection.table_exists?('"things.table"'), "table should exist")
+ assert(@connection.data_source_exists?('"things.table"'), "data_source should exist")
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 640df31e2e..a2fd1177a6 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -284,9 +284,9 @@ module ActiveRecord
def test_tables
with_example_table do
- assert_equal %w{ ex }, @conn.tables
+ ActiveSupport::Deprecation.silence { assert_equal %w{ ex }, @conn.tables }
with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer', 'people' do
- assert_equal %w{ ex people }.sort, @conn.tables.sort
+ ActiveSupport::Deprecation.silence { assert_equal %w{ ex people }.sort, @conn.tables.sort }
end
end
end
@@ -297,7 +297,9 @@ module ActiveRecord
WHERE type IN ('table','view') AND name <> 'sqlite_sequence'
SQL
assert_logged [[sql.squish, 'SCHEMA', []]] do
- @conn.tables('hello')
+ ActiveSupport::Deprecation.silence do
+ @conn.tables('hello')
+ end
end
end
@@ -316,7 +318,9 @@ module ActiveRecord
WHERE type IN ('table','view') AND name <> 'sqlite_sequence' AND name = 'ex'
SQL
assert_logged [[sql.squish, 'SCHEMA', []]] do
- assert @conn.table_exists?('ex')
+ ActiveSupport::Deprecation.silence do
+ assert @conn.table_exists?('ex')
+ end
end
end
end
diff --git a/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb b/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb
new file mode 100644
index 0000000000..2b867965ba
--- /dev/null
+++ b/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb
@@ -0,0 +1,41 @@
+require 'cases/helper'
+require 'models/content'
+
+class BidirectionalDestroyDependenciesTest < ActiveRecord::TestCase
+ fixtures :content, :content_positions
+
+ def setup
+ Content.destroyed_ids.clear
+ ContentPosition.destroyed_ids.clear
+ end
+
+ def test_bidirectional_dependence_when_destroying_item_with_belongs_to_association
+ content_position = ContentPosition.find(1)
+ content = content_position.content
+ assert_not_nil content
+
+ content_position.destroy
+
+ assert_equal [content_position.id], ContentPosition.destroyed_ids
+ assert_equal [content.id], Content.destroyed_ids
+ end
+
+ def test_bidirectional_dependence_when_destroying_item_with_has_one_association
+ content = Content.find(1)
+ content_position = content.content_position
+ assert_not_nil content_position
+
+ content.destroy
+
+ assert_equal [content.id], Content.destroyed_ids
+ assert_equal [content_position.id], ContentPosition.destroyed_ids
+ end
+
+ def test_bidirectional_dependence_when_destroying_item_with_has_one_association_fails_first_time
+ content = ContentWhichRequiresTwoDestroyCalls.find(1)
+
+ 2.times { content.destroy }
+
+ assert_equal content.destroyed?, true
+ end
+end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index bc1bff79d3..0c09713971 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1179,6 +1179,24 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal 1, mary.unique_categorized_post_ids.length
end
+ def test_preloading_has_one_using_reorder
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; "TempAuthor"; end
+ self.table_name = "authors"
+ has_one :post, class_name: "PostWithDefaultScope", foreign_key: :author_id
+ has_one :reorderd_post, -> { reorder(title: :desc) }, class_name: "PostWithDefaultScope", foreign_key: :author_id
+ end
+
+ author = klass.first
+ # PRECONDITION: make sure ordering results in different results
+ assert_not_equal author.post, author.reorderd_post
+
+ preloaded_reorderd_post = klass.preload(:reorderd_post).first.reorderd_post
+
+ assert_equal author.reorderd_post, preloaded_reorderd_post
+ assert_equal Post.order(title: :desc).first.title, preloaded_reorderd_post.title
+ end
+
def test_preloading_polymorphic_with_custom_foreign_type
sponsor = sponsors(:moustache_club_sponsor_for_groucho)
groucho = members(:groucho)
@@ -1203,12 +1221,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
- def test_join_eager_with_nil_order_should_generate_valid_sql
- assert_nothing_raised(ActiveRecord::StatementInvalid) do
- Post.includes(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first
- end
- end
-
def test_deep_including_through_habtm
# warm up habtm cache
posts = Post.all.merge!(:includes => {:categories => :categorizations}, :order => "posts.id").to_a
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 20af436e02..ccb062f991 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
@@ -957,4 +957,29 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
projects = ProjectUnscopingDavidDefaultScope.includes(:developers).where(id: project.id)
assert_equal 1, projects.first.developers.size
end
+
+ def test_preloaded_associations_size
+ assert_equal Project.first.salaried_developers.size,
+ Project.preload(:salaried_developers).first.salaried_developers.size
+
+ assert_equal Project.includes(:salaried_developers).references(:salaried_developers).first.salaried_developers.size,
+ Project.preload(:salaried_developers).first.salaried_developers.size
+
+ # Nested HATBM
+ first_project = Developer.first.projects.first
+ preloaded_first_project =
+ Developer.preload(projects: :salaried_developers).
+ first.
+ projects.
+ detect { |p| p.id == first_project.id }
+
+ assert preloaded_first_project.salaried_developers.loaded?, true
+ assert_equal first_project.salaried_developers.size, preloaded_first_project.salaried_developers.size
+ end
+
+ def test_has_and_belongs_to_many_is_useable_with_belongs_to_required_by_default
+ assert_difference "Project.first.developers_required_by_default.size", 1 do
+ Project.first.developers_required_by_default.create!(name: "Sean", salary: 50000)
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index eb94870a35..50ca6537cc 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -203,9 +203,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
bulb = car.bulbs.create
assert_equal 'defaulty', bulb.name
+ end
+
+ def test_build_and_create_from_association_should_respect_passed_attributes_over_default_scope
+ car = Car.create(name: 'honda')
+
+ bulb = car.bulbs.build(name: 'exotic')
+ assert_equal 'exotic', bulb.name
- bulb = car.bulbs.create(:name => 'exotic')
+ bulb = car.bulbs.create(name: 'exotic')
assert_equal 'exotic', bulb.name
+
+ bulb = car.awesome_bulbs.build(frickinawesome: false)
+ assert_equal false, bulb.frickinawesome
+
+ bulb = car.awesome_bulbs.create(frickinawesome: false)
+ assert_equal false, bulb.frickinawesome
end
def test_build_from_association_should_respect_scope
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 cf730e4fe7..226ecf5447 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -1111,10 +1111,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_through_with_default_scope_on_the_target
person = people(:michael)
- assert_equal [posts(:thinking)], person.first_posts
+ assert_equal [posts(:thinking).id], person.first_posts.map(&:id)
readers(:michael_authorless).update(first_post_id: 1)
- assert_equal [posts(:thinking)], person.reload.first_posts
+ assert_equal [posts(:thinking).id], person.reload.first_posts.map(&:id)
end
def test_has_many_through_with_includes_in_through_association_scope
diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb
new file mode 100644
index 0000000000..4af791b758
--- /dev/null
+++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb
@@ -0,0 +1,79 @@
+require "cases/helper"
+require 'models/post'
+require 'models/comment'
+require 'models/author'
+require 'models/essay'
+require 'models/categorization'
+require 'models/person'
+
+class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
+ fixtures :authors, :essays, :posts, :comments, :categorizations, :people
+
+ def test_construct_finder_sql_applies_aliases_tables_on_association_conditions
+ result = Author.left_outer_joins(:thinking_posts, :welcome_posts).to_a
+ assert_equal authors(:david), result.first
+ end
+
+ def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associations
+ assert_nothing_raised do
+ queries = capture_sql do
+ Person.left_outer_joins(:agents => {:agents => :agents})
+ .left_outer_joins(:agents => {:agents => {:primary_contact => :agents}}).to_a
+ end
+ assert queries.any? { |sql| /agents_people_4/i =~ sql }
+ end
+ end
+
+ def test_construct_finder_sql_executes_a_left_outer_join
+ assert_not_equal Author.count, Author.joins(:posts).count
+ assert_equal Author.count, Author.left_outer_joins(:posts).count
+ end
+
+ def test_left_outer_join_by_left_joins
+ assert_not_equal Author.count, Author.joins(:posts).count
+ assert_equal Author.count, Author.left_joins(:posts).count
+ end
+
+ def test_construct_finder_sql_ignores_empty_left_outer_joins_hash
+ queries = capture_sql { Author.left_outer_joins({}) }
+ assert queries.none? { |sql| /LEFT OUTER JOIN/i =~ sql }
+ end
+
+ def test_construct_finder_sql_ignores_empty_left_outer_joins_array
+ queries = capture_sql { Author.left_outer_joins([]) }
+ assert queries.none? { |sql| /LEFT OUTER JOIN/i =~ sql }
+ end
+
+ def test_left_outer_joins_forbids_to_use_string_as_argument
+ assert_raise(ArgumentError){ Author.left_outer_joins('LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"').to_a }
+ end
+
+ def test_join_conditions_added_to_join_clause
+ queries = capture_sql { Author.left_outer_joins(:essays).to_a }
+ assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1)/i =~ sql }
+ assert queries.none? { |sql| /WHERE/i =~ sql }
+ end
+
+ def test_find_with_sti_join
+ scope = Post.left_outer_joins(:special_comments).where(:id => posts(:sti_comments).id)
+
+ # The join should match SpecialComment and its subclasses only
+ assert scope.where("comments.type" => "Comment").empty?
+ assert !scope.where("comments.type" => "SpecialComment").empty?
+ assert !scope.where("comments.type" => "SubSpecialComment").empty?
+ end
+
+ def test_does_not_override_select
+ authors = Author.select("authors.name, #{%{(authors.author_address_id || ' ' || authors.author_address_extra_id) as addr_id}}").left_outer_joins(:posts)
+ assert authors.any?
+ assert authors.first.respond_to?(:addr_id)
+ end
+
+ test "the default scope of the target is applied when joining associations" do
+ author = Author.create! name: "Jon"
+ author.categorizations.create!
+ author.categorizations.create! special: true
+
+ assert_equal [author], Author.where(id: author).left_outer_joins(:special_categorizations)
+ end
+end
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 264b275181..2991ca8b76 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -172,5 +172,18 @@ module ActiveRecord
assert_equal int_range, klass.type_for_attribute("my_int_range")
end
end
+
+ test "attributes added after subclasses load are inherited" do
+ parent = Class.new(ActiveRecord::Base) do
+ self.table_name = "topics"
+ end
+
+ child = Class.new(parent)
+ child.new # => force a schema load
+
+ parent.attribute(:foo, Type::Value.new)
+
+ assert_equal(:bar, child.new(foo: :bar).foo)
+ end
end
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 37ec3f1106..0df8f1f798 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -24,6 +24,8 @@ require 'models/molecule'
require 'models/member'
require 'models/member_detail'
require 'models/organization'
+require 'models/guitar'
+require 'models/tuning_peg'
class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
def test_autosave_validation
@@ -397,6 +399,40 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
assert_not molecule.persisted?, 'Molecule should not be persisted when its electrons are invalid'
end
+ def test_errors_should_be_indexed_when_passed_as_array
+ guitar = Guitar.new
+ tuning_peg_valid = TuningPeg.new
+ tuning_peg_valid.pitch = 440.0
+ tuning_peg_invalid = TuningPeg.new
+
+ guitar.tuning_pegs = [tuning_peg_valid, tuning_peg_invalid]
+
+ assert_not tuning_peg_invalid.valid?
+ assert tuning_peg_valid.valid?
+ assert_not guitar.valid?
+ assert_equal ["is not a number"], guitar.errors["tuning_pegs[1].pitch"]
+ assert_not_equal ["is not a number"], guitar.errors["tuning_pegs.pitch"]
+ end
+
+ def test_errors_should_be_indexed_when_global_flag_is_set
+ old_attribute_config = ActiveRecord::Base.index_nested_attribute_errors
+ ActiveRecord::Base.index_nested_attribute_errors = true
+
+ molecule = Molecule.new
+ valid_electron = Electron.new(name: 'electron')
+ invalid_electron = Electron.new
+
+ molecule.electrons = [valid_electron, invalid_electron]
+
+ assert_not invalid_electron.valid?
+ assert valid_electron.valid?
+ assert_not molecule.valid?
+ assert_equal ["can't be blank"], molecule.errors["electrons[1].name"]
+ assert_not_equal ["can't be blank"], molecule.errors["electrons.name"]
+ ensure
+ ActiveRecord::Base.index_nested_attribute_errors = old_attribute_config
+ end
+
def test_valid_adding_with_nested_attributes
molecule = Molecule.new
valid_electron = Electron.new(name: 'electron')
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index dbbcaa075d..d961f4710e 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -26,7 +26,7 @@ require 'models/bird'
require 'models/car'
require 'models/bulb'
require 'rexml/document'
-require 'concurrent/atomics'
+require 'concurrent/atomic/count_down_latch'
class FirstAbstractClass < ActiveRecord::Base
self.abstract_class = true
@@ -1204,42 +1204,10 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal last, Developer.all.merge!(:order => :salary).to_a.last
end
- def test_abstract_class
- assert !ActiveRecord::Base.abstract_class?
- assert LoosePerson.abstract_class?
- assert !LooseDescendant.abstract_class?
- end
-
def test_abstract_class_table_name
assert_nil AbstractCompany.table_name
end
- def test_descends_from_active_record
- assert !ActiveRecord::Base.descends_from_active_record?
-
- # Abstract subclass of AR::Base.
- assert LoosePerson.descends_from_active_record?
-
- # Concrete subclass of an abstract class.
- assert LooseDescendant.descends_from_active_record?
-
- # Concrete subclass of AR::Base.
- assert TightPerson.descends_from_active_record?
-
- # Concrete subclass of a concrete class but has no type column.
- assert TightDescendant.descends_from_active_record?
-
- # Concrete subclass of AR::Base.
- assert Post.descends_from_active_record?
-
- # Abstract subclass of a concrete class which has a type column.
- # This is pathological, as you'll never have Sub < Abstract < Concrete.
- assert !StiPost.descends_from_active_record?
-
- # Concrete subclasses an abstract class which has a type column.
- assert !SubStiPost.descends_from_active_record?
- end
-
def test_find_on_abstract_base_class_doesnt_use_type_condition
old_class = LooseDescendant
Object.send :remove_const, :LooseDescendant
@@ -1284,53 +1252,6 @@ class BasicsTest < ActiveRecord::TestCase
ActiveRecord::Base.logger = original_logger
end
- def test_compute_type_success
- assert_equal Author, ActiveRecord::Base.send(:compute_type, 'Author')
- end
-
- def test_compute_type_nonexistent_constant
- e = assert_raises NameError do
- ActiveRecord::Base.send :compute_type, 'NonexistentModel'
- end
- assert_equal 'uninitialized constant ActiveRecord::Base::NonexistentModel', e.message
- assert_equal 'ActiveRecord::Base::NonexistentModel', e.name
- end
-
- def test_compute_type_no_method_error
- ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise NoMethodError }) do
- assert_raises NoMethodError do
- ActiveRecord::Base.send :compute_type, 'InvalidModel'
- end
- end
- end
-
- def test_compute_type_on_undefined_method
- error = nil
- begin
- Class.new(Author) do
- alias_method :foo, :bar
- end
- rescue => e
- error = e
- end
-
- ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise e }) do
-
- exception = assert_raises NameError do
- ActiveRecord::Base.send :compute_type, 'InvalidModel'
- end
- assert_equal error.message, exception.message
- end
- end
-
- def test_compute_type_argument_error
- ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise ArgumentError }) do
- assert_raises ArgumentError do
- ActiveRecord::Base.send :compute_type, 'InvalidModel'
- end
- end
- end
-
def test_clear_cache!
# preheat cache
c1 = Post.connection.schema_cache.columns('posts')
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 9cb70ee239..da65336305 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -161,7 +161,7 @@ class EachTest < ActiveRecord::TestCase
end
# posts.first will be ordered using id only. Title order scope should not apply here
assert_not_equal first_post, posts.first
- assert_equal posts(:welcome), posts.first
+ assert_equal posts(:welcome).id, posts.first.id
end
def test_find_in_batches_should_not_ignore_the_default_scope_if_it_is_other_then_order
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index dff6ea0fb0..d43668e57c 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -92,7 +92,14 @@ module ActiveRecord
app = lambda { |_| [200, {}, body] }
response_body = ConnectionManagement.new(app).call(@env)[2]
assert response_body.respond_to?(:to_path)
- assert_equal response_body.to_path, "/path"
+ assert_equal "/path", response_body.to_path
+ end
+
+ test "doesn't mutate the original response" do
+ original_response = [200, {}, 'hi']
+ app = lambda { |_| original_response }
+ ConnectionManagement.new(app).call(@env)[2]
+ assert_equal 'hi', original_response.last
end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 7ef5c93a48..efa3e0455e 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -1,5 +1,5 @@
require "cases/helper"
-require 'concurrent/atomics'
+require 'concurrent/atomic/count_down_latch'
module ActiveRecord
module ConnectionAdapters
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index a0eaa66e94..f30ed4fcc8 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -955,3 +955,17 @@ class FixturesWithAbstractBelongsTo < ActiveRecord::TestCase
assert_equal pirates(:blackbeard), doubloons(:blackbeards_doubloon).pirate
end
end
+
+class FixtureClassNamesTest < ActiveRecord::TestCase
+ def setup
+ @saved_cache = self.fixture_class_names.dup
+ end
+
+ def teardown
+ self.fixture_class_names.replace(@saved_cache)
+ end
+
+ test "fixture_class_names returns nil for unregistered identifier" do
+ assert_nil self.fixture_class_names['unregistered_identifier']
+ end
+end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index f67d85603a..52e3734dd0 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -1,4 +1,5 @@
require 'cases/helper'
+require 'models/author'
require 'models/company'
require 'models/person'
require 'models/post'
@@ -55,6 +56,53 @@ class InheritanceTest < ActiveRecord::TestCase
end
end
+ def test_compute_type_success
+ assert_equal Author, ActiveRecord::Base.send(:compute_type, 'Author')
+ end
+
+ def test_compute_type_nonexistent_constant
+ e = assert_raises NameError do
+ ActiveRecord::Base.send :compute_type, 'NonexistentModel'
+ end
+ assert_equal 'uninitialized constant ActiveRecord::Base::NonexistentModel', e.message
+ assert_equal 'ActiveRecord::Base::NonexistentModel', e.name
+ end
+
+ def test_compute_type_no_method_error
+ ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise NoMethodError }) do
+ assert_raises NoMethodError do
+ ActiveRecord::Base.send :compute_type, 'InvalidModel'
+ end
+ end
+ end
+
+ def test_compute_type_on_undefined_method
+ error = nil
+ begin
+ Class.new(Author) do
+ alias_method :foo, :bar
+ end
+ rescue => e
+ error = e
+ end
+
+ ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise e }) do
+
+ exception = assert_raises NameError do
+ ActiveRecord::Base.send :compute_type, 'InvalidModel'
+ end
+ assert_equal error.message, exception.message
+ end
+ end
+
+ def test_compute_type_argument_error
+ ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise ArgumentError }) do
+ assert_raises ArgumentError do
+ ActiveRecord::Base.send :compute_type, 'InvalidModel'
+ end
+ end
+ end
+
def test_should_store_demodulized_class_name_with_store_full_sti_class_option_disabled
without_store_full_sti_class do
item = Namespaced::Company.new
@@ -77,6 +125,32 @@ class InheritanceTest < ActiveRecord::TestCase
end
end
+ def test_descends_from_active_record
+ assert !ActiveRecord::Base.descends_from_active_record?
+
+ # Abstract subclass of AR::Base.
+ assert LoosePerson.descends_from_active_record?
+
+ # Concrete subclass of an abstract class.
+ assert LooseDescendant.descends_from_active_record?
+
+ # Concrete subclass of AR::Base.
+ assert TightPerson.descends_from_active_record?
+
+ # Concrete subclass of a concrete class but has no type column.
+ assert TightDescendant.descends_from_active_record?
+
+ # Concrete subclass of AR::Base.
+ assert Post.descends_from_active_record?
+
+ # Abstract subclass of a concrete class which has a type column.
+ # This is pathological, as you'll never have Sub < Abstract < Concrete.
+ assert !StiPost.descends_from_active_record?
+
+ # Concrete subclasses an abstract class which has a type column.
+ assert !SubStiPost.descends_from_active_record?
+ end
+
def test_company_descends_from_active_record
assert !ActiveRecord::Base.descends_from_active_record?
assert AbstractCompany.descends_from_active_record?, 'AbstractCompany should descend from ActiveRecord::Base'
@@ -84,6 +158,12 @@ class InheritanceTest < ActiveRecord::TestCase
assert !Class.new(Company).descends_from_active_record?, 'Company subclass should not descend from ActiveRecord::Base'
end
+ def test_abstract_class
+ assert !ActiveRecord::Base.abstract_class?
+ assert LoosePerson.abstract_class?
+ assert !LooseDescendant.abstract_class?
+ end
+
def test_inheritance_base_class
assert_equal Post, Post.base_class
assert_equal Post, SpecialPost.base_class
@@ -223,7 +303,6 @@ class InheritanceTest < ActiveRecord::TestCase
end
end
-
def test_new_with_complex_inheritance
assert_nothing_raised { Client.new(type: 'VerySpecialClient') }
end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 84b0ff8fcb..0da58040c8 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -157,8 +157,10 @@ module ActiveRecord
teardown do
%w[horses new_horses].each do |table|
- if ActiveRecord::Base.connection.table_exists?(table)
- ActiveRecord::Base.connection.drop_table(table)
+ ActiveSupport::Deprecation.silence do
+ if ActiveRecord::Base.connection.table_exists?(table)
+ ActiveRecord::Base.connection.drop_table(table)
+ end
end
end
ActiveRecord::Migration.verbose = @verbose_was
@@ -189,14 +191,14 @@ module ActiveRecord
def test_migrate_up
migration = InvertibleMigration.new
migration.migrate(:up)
- assert migration.connection.table_exists?("horses"), "horses should exist"
+ ActiveSupport::Deprecation.silence { assert migration.connection.table_exists?("horses"), "horses should exist" }
end
def test_migrate_down
migration = InvertibleMigration.new
migration.migrate :up
migration.migrate :down
- assert !migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
end
def test_migrate_revert
@@ -204,11 +206,11 @@ module ActiveRecord
revert = InvertibleRevertMigration.new
migration.migrate :up
revert.migrate :up
- assert !migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
revert.migrate :down
- assert migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert migration.connection.table_exists?("horses") }
migration.migrate :down
- assert !migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
end
def test_migrate_revert_by_part
@@ -216,18 +218,24 @@ module ActiveRecord
received = []
migration = InvertibleByPartsMigration.new
migration.test = ->(dir){
- assert migration.connection.table_exists?("horses")
- assert migration.connection.table_exists?("new_horses")
+ ActiveSupport::Deprecation.silence do
+ assert migration.connection.table_exists?("horses")
+ assert migration.connection.table_exists?("new_horses")
+ end
received << dir
}
migration.migrate :up
assert_equal [:both, :up], received
- assert !migration.connection.table_exists?("horses")
- assert migration.connection.table_exists?("new_horses")
+ ActiveSupport::Deprecation.silence do
+ assert !migration.connection.table_exists?("horses")
+ assert migration.connection.table_exists?("new_horses")
+ end
migration.migrate :down
assert_equal [:both, :up, :both, :down], received
- assert migration.connection.table_exists?("horses")
- assert !migration.connection.table_exists?("new_horses")
+ ActiveSupport::Deprecation.silence do
+ assert migration.connection.table_exists?("horses")
+ assert !migration.connection.table_exists?("new_horses")
+ end
end
def test_migrate_revert_whole_migration
@@ -236,20 +244,20 @@ module ActiveRecord
revert = RevertWholeMigration.new(klass)
migration.migrate :up
revert.migrate :up
- assert !migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
revert.migrate :down
- assert migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert migration.connection.table_exists?("horses") }
migration.migrate :down
- assert !migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
end
end
def test_migrate_nested_revert_whole_migration
revert = NestedRevertWholeMigration.new(InvertibleRevertMigration)
revert.migrate :down
- assert revert.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert revert.connection.table_exists?("horses") }
revert.migrate :up
- assert !revert.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert !revert.connection.table_exists?("horses") }
end
def test_migrate_revert_change_column_default
@@ -314,24 +322,24 @@ module ActiveRecord
def test_legacy_up
LegacyMigration.migrate :up
- assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist"
+ ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist" }
end
def test_legacy_down
LegacyMigration.migrate :up
LegacyMigration.migrate :down
- assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
+ ActiveSupport::Deprecation.silence { assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" }
end
def test_up
LegacyMigration.up
- assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist"
+ ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist" }
end
def test_down
LegacyMigration.up
LegacyMigration.down
- assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
+ ActiveSupport::Deprecation.silence { assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" }
end
def test_migrate_down_with_table_name_prefix
@@ -340,7 +348,7 @@ module ActiveRecord
migration = InvertibleMigration.new
migration.migrate(:up)
assert_nothing_raised { migration.migrate(:down) }
- assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist"
+ ActiveSupport::Deprecation.silence { assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist" }
ensure
ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = ''
end
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 3846ba8e7f..707a2d1da1 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -209,7 +209,7 @@ class LogSubscriberTest < ActiveRecord::TestCase
Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join
end
- unless current_adapter?(:Mysql2Adapter)
+ if ActiveRecord::Base.connection.prepared_statements
def test_binary_data_is_not_logged
Binary.create(data: 'some binary data')
wait
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 83e50048ec..2ff9cf8cf5 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -405,9 +405,9 @@ module ActiveRecord
def test_drop_table_if_exists
connection.create_table(:testings)
- assert connection.table_exists?(:testings)
+ ActiveSupport::Deprecation.silence { assert connection.table_exists?(:testings) }
connection.drop_table(:testings, if_exists: true)
- assert_not connection.table_exists?(:testings)
+ ActiveSupport::Deprecation.silence { assert_not connection.table_exists?(:testings) }
end
def test_drop_table_if_exists_nothing_raised
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index 8fd08fe4ce..0a7b57455c 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -12,7 +12,9 @@ module ActiveRecord
teardown do
%w(artists_musics musics_videos catalog).each do |table_name|
- connection.drop_table table_name if connection.tables.include?(table_name)
+ ActiveSupport::Deprecation.silence do
+ connection.drop_table table_name if connection.table_exists?(table_name)
+ end
end
end
@@ -82,62 +84,62 @@ module ActiveRecord
connection.create_join_table :artists, :musics
connection.drop_join_table :artists, :musics
- assert !connection.tables.include?('artists_musics')
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('artists_musics') }
end
def test_drop_join_table_with_strings
connection.create_join_table :artists, :musics
connection.drop_join_table 'artists', 'musics'
- assert !connection.tables.include?('artists_musics')
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('artists_musics') }
end
def test_drop_join_table_with_the_proper_order
connection.create_join_table :videos, :musics
connection.drop_join_table :videos, :musics
- assert !connection.tables.include?('musics_videos')
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('musics_videos') }
end
def test_drop_join_table_with_the_table_name
connection.create_join_table :artists, :musics, table_name: :catalog
connection.drop_join_table :artists, :musics, table_name: :catalog
- assert !connection.tables.include?('catalog')
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('catalog') }
end
def test_drop_join_table_with_the_table_name_as_string
connection.create_join_table :artists, :musics, table_name: 'catalog'
connection.drop_join_table :artists, :musics, table_name: 'catalog'
- assert !connection.tables.include?('catalog')
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('catalog') }
end
def test_drop_join_table_with_column_options
connection.create_join_table :artists, :musics, column_options: {null: true}
connection.drop_join_table :artists, :musics, column_options: {null: true}
- assert !connection.tables.include?('artists_musics')
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('artists_musics') }
end
def test_create_and_drop_join_table_with_common_prefix
with_table_cleanup do
connection.create_join_table 'audio_artists', 'audio_musics'
- assert_includes connection.tables, 'audio_artists_musics'
+ ActiveSupport::Deprecation.silence { assert connection.table_exists?('audio_artists_musics') }
connection.drop_join_table 'audio_artists', 'audio_musics'
- assert !connection.tables.include?('audio_artists_musics'), "Should have dropped join table, but didn't"
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('audio_artists_musics'), "Should have dropped join table, but didn't" }
end
end
private
def with_table_cleanup
- tables_before = connection.tables
+ tables_before = connection.data_sources
yield
ensure
- tables_after = connection.tables - tables_before
+ tables_after = connection.data_sources - tables_before
tables_after.each do |table|
connection.drop_table table
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index 84ec657398..edbc8abe4d 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -164,7 +164,7 @@ class ReferencesWithoutForeignKeySupportTest < ActiveRecord::TestCase
t.references :testing_parent, foreign_key: true
end
- assert_includes @connection.tables, "testings"
+ assert_includes @connection.data_sources, "testings"
end
end
end
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index 6d742d3f2f..8eb027d53f 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -15,7 +15,7 @@ module ActiveRecord
end
def teardown
- rename_table :octopi, :test_models if connection.table_exists? :octopi
+ ActiveSupport::Deprecation.silence { rename_table :octopi, :test_models if connection.table_exists? :octopi }
super
end
@@ -83,7 +83,7 @@ module ActiveRecord
enable_extension!('uuid-ossp', connection)
connection.create_table :cats, id: :uuid
assert_nothing_raised { rename_table :cats, :felines }
- assert connection.table_exists? :felines
+ ActiveSupport::Deprecation.silence { assert connection.table_exists? :felines }
ensure
disable_extension!('uuid-ossp', connection)
connection.drop_table :cats, if_exists: true
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 10f1c7216f..c3c204cf9f 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1,6 +1,7 @@
-require "cases/helper"
-require "cases/migration/helper"
+require 'cases/helper'
+require 'cases/migration/helper'
require 'bigdecimal/util'
+require 'concurrent/atomic/count_down_latch'
require 'models/person'
require 'models/topic'
@@ -522,6 +523,79 @@ class MigrationTest < ActiveRecord::TestCase
end
end
+ if ActiveRecord::Base.connection.supports_advisory_locks?
+ def test_migrator_generates_valid_lock_id
+ migration = Class.new(ActiveRecord::Migration).new
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+
+ lock_id = migrator.send(:generate_migrator_advisory_lock_id)
+
+ assert ActiveRecord::Base.connection.get_advisory_lock(lock_id),
+ "the Migrator should have generated a valid lock id, but it didn't"
+ assert ActiveRecord::Base.connection.release_advisory_lock(lock_id),
+ "the Migrator should have generated a valid lock id, but it didn't"
+ end
+
+ def test_generate_migrator_advisory_lock_id
+ # It is important we are consistent with how we generate this so that
+ # exclusive locking works across migrator versions
+ migration = Class.new(ActiveRecord::Migration).new
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+
+ lock_id = migrator.send(:generate_migrator_advisory_lock_id)
+
+ current_database = ActiveRecord::Base.connection.current_database
+ salt = ActiveRecord::Migrator::MIGRATOR_SALT
+ expected_id = Zlib.crc32(current_database) * salt
+
+ assert lock_id == expected_id, "expected lock id generated by the migrator to be #{expected_id}, but it was #{lock_id} instead"
+ assert lock_id.is_a?(Fixnum), "expected lock id to be a Fixnum, but it wasn't"
+ assert lock_id.bit_length <= 63, "lock id must be a signed integer of max 63 bits magnitude"
+ end
+
+ def test_migrator_one_up_with_unavailable_lock
+ assert_no_column Person, :last_name
+
+ migration = Class.new(ActiveRecord::Migration) {
+ def version; 100 end
+ def migrate(x)
+ add_column "people", "last_name", :string
+ end
+ }.new
+
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ lock_id = migrator.send(:generate_migrator_advisory_lock_id)
+
+ with_another_process_holding_lock(lock_id) do
+ assert_raise(ActiveRecord::ConcurrentMigrationError) { migrator.migrate }
+ end
+
+ assert_no_column Person, :last_name,
+ "without an advisory lock, the Migrator should not make any changes, but it did."
+ end
+
+ def test_migrator_one_up_with_unavailable_lock_using_run
+ assert_no_column Person, :last_name
+
+ migration = Class.new(ActiveRecord::Migration) {
+ def version; 100 end
+ def migrate(x)
+ add_column "people", "last_name", :string
+ end
+ }.new
+
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ lock_id = migrator.send(:generate_migrator_advisory_lock_id)
+
+ with_another_process_holding_lock(lock_id) do
+ assert_raise(ActiveRecord::ConcurrentMigrationError) { migrator.run }
+ end
+
+ assert_no_column Person, :last_name,
+ "without an advisory lock, the Migrator should not make any changes, but it did."
+ end
+ end
+
protected
# This is needed to isolate class_attribute assignments like `table_name_prefix`
# for each test case.
@@ -531,6 +605,30 @@ class MigrationTest < ActiveRecord::TestCase
def self.base_class; self; end
}
end
+
+ def with_another_process_holding_lock(lock_id)
+ thread_lock = Concurrent::CountDownLatch.new
+ test_terminated = Concurrent::CountDownLatch.new
+
+ other_process = Thread.new do
+ begin
+ conn = ActiveRecord::Base.connection_pool.checkout
+ conn.get_advisory_lock(lock_id)
+ thread_lock.count_down
+ test_terminated.wait # hold the lock open until we tested everything
+ ensure
+ conn.release_advisory_lock(lock_id)
+ ActiveRecord::Base.connection_pool.checkin(conn)
+ end
+ end
+
+ thread_lock.wait # wait until the 'other process' has the lock
+
+ yield
+
+ test_terminated.count_down
+ other_process.join
+ end
end
class ReservedWordsMigrationTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index 2ff6938e7b..dbf088f455 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -313,9 +313,9 @@ class MigratorTest < ActiveRecord::TestCase
_, migrator = migrator_class(3)
ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
- assert_not ActiveRecord::Base.connection.table_exists?('schema_migrations')
+ ActiveSupport::Deprecation.silence { assert_not ActiveRecord::Base.connection.table_exists?('schema_migrations') }
migrator.migrate("valid", 1)
- assert ActiveRecord::Base.connection.table_exists?('schema_migrations')
+ ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?('schema_migrations') }
end
def test_migrator_forward
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 31686bde3f..b2e59e3970 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -744,9 +744,10 @@ class PersistenceTest < ActiveRecord::TestCase
assert !topic.approved?
assert_equal "The First Topic", topic.title
- assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do
+ error = assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do
topic.update_attributes(id: 3, title: "Hm is it possible?")
end
+ assert_not_nil error.cause
assert_not_equal "Hm is it possible?", Topic.find(3).title
topic.update_attributes(id: 1234)
@@ -965,4 +966,17 @@ class PersistenceTest < ActiveRecord::TestCase
widget.reset_column_information
end
end
+
+ def test_reset_column_information_resets_children
+ child = Class.new(Topic)
+ child.new # force schema to load
+
+ ActiveRecord::Base.connection.add_column(:topics, :foo, :string)
+ Topic.reset_column_information
+
+ assert_equal "bar", child.new(foo: :bar).foo
+ ensure
+ ActiveRecord::Base.connection.remove_column(:topics, :foo)
+ Topic.reset_column_information
+ end
end
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index daa3271777..bca50dd008 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -44,7 +44,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase
conn = ActiveRecord::Base.connection_pool.checkout
ActiveRecord::Base.connection_pool.checkin conn
@connection_count += 1
- ActiveRecord::Base.connection.tables
+ ActiveRecord::Base.connection.data_sources
rescue ActiveRecord::ConnectionTimeoutError
@timed_out += 1
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 5e4ba47988..d883784553 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -224,7 +224,7 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
end
teardown do
- @connection.drop_table(:barcodes) if @connection.table_exists? :barcodes
+ @connection.drop_table(:barcodes, if_exists: true)
end
def test_any_type_primary_key
@@ -280,6 +280,32 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
con.reconnect!
end
end
+
+ class PrimaryKeyBigintNilDefaultTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ self.use_transactional_tests = false
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table(:bigint_defaults, id: :bigint, default: nil, force: true)
+ end
+
+ def teardown
+ @connection.drop_table :bigint_defaults, if_exists: true
+ end
+
+ test "primary key with bigint allows default override via nil" do
+ column = @connection.columns(:bigint_defaults).find { |c| c.name == 'id' }
+ assert column.bigint?
+ assert_not column.auto_increment?
+ end
+
+ test "schema dump primary key with bigint default nil" do
+ schema = dump_table_schema "bigint_defaults"
+ assert_match %r{create_table "bigint_defaults", id: :bigint, default: nil}, schema
+ end
+ end
end
if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter, :Mysql2Adapter)
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 88d2dd55ab..d0f60a84b5 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -22,13 +22,17 @@ module ActiveRecord
def sanitize_sql(sql)
sql
end
+
+ def sanitize_sql_for_order(sql)
+ sql
+ end
end
def relation
@relation ||= Relation.new FakeKlass.new('posts'), Post.arel_table, Post.predicate_builder
end
- (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method|
+ (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select, :left_joins]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal [:foo], relation.public_send("#{method}_values")
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 675149556f..f46d414b95 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -20,6 +20,10 @@ module ActiveRecord
def self.table_name
'fake_table'
end
+
+ def self.sanitize_sql_for_order(sql)
+ sql
+ end
end
def test_construction
@@ -231,6 +235,13 @@ module ActiveRecord
assert_equal 3, relation.where(id: post.id).pluck(:id).size
end
+ def test_merge_raises_with_invalid_argument
+ assert_raises ArgumentError do
+ relation = Relation.new(FakeKlass, :b, nil)
+ relation.merge(true)
+ end
+ end
+
def test_respond_to_for_non_selected_element
post = Post.select(:title).first
assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception"
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 7521f0573a..cd23c1b3e1 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -297,6 +297,11 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 3, tags.length
end
+ def test_finding_with_sanitized_order
+ query = Tag.order(["field(id, ?)", [1,3,2]]).to_sql
+ assert_match(/field\(id, 1,3,2\)/, query)
+ end
+
def test_finding_with_order_limit_and_offset
entrants = Entrant.order("id ASC").limit(2).offset(1)
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 2a2c2bc8d0..43f133b12d 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -353,6 +353,38 @@ class SchemaDumperTest < ActiveRecord::TestCase
ActiveRecord::Base.table_name_suffix = ActiveRecord::Base.table_name_prefix = ''
$stdout = original
end
+
+ def test_schema_dump_with_table_name_prefix_and_ignoring_tables
+ original, $stdout = $stdout, StringIO.new
+
+ create_cat_migration = Class.new(ActiveRecord::Migration) do
+ def change
+ create_table("cats") do |t|
+ end
+ create_table("omg_cats") do |t|
+ end
+ end
+ end
+
+ original_table_name_prefix = ActiveRecord::Base.table_name_prefix
+ original_schema_dumper_ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
+ ActiveRecord::Base.table_name_prefix = 'omg_'
+ ActiveRecord::SchemaDumper.ignore_tables = ["cats"]
+ migration = create_cat_migration.new
+ migration.migrate(:up)
+
+ stream = StringIO.new
+ output = ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream).string
+
+ assert_match %r{create_table "omg_cats"}, output
+ refute_match %r{create_table "cats"}, output
+ ensure
+ migration.migrate(:down)
+ ActiveRecord::Base.table_name_prefix = original_table_name_prefix
+ ActiveRecord::SchemaDumper.ignore_tables = original_schema_dumper_ignore_tables
+
+ $stdout = original
+ end
end
class SchemaDumperDefaultsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index a93fa57257..723a7618ba 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -21,28 +21,21 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
- def test_creates_database_with_default_encoding_and_collation
+ def test_creates_database_with_no_default_options
@connection.expects(:create_database).
- with('my-app-db', charset: 'utf8', collation: 'utf8_unicode_ci')
+ with('my-app-db', {})
ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
- def test_creates_database_with_given_encoding_and_default_collation
- @connection.expects(:create_database).
- with('my-app-db', charset: 'utf8', collation: 'utf8_unicode_ci')
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge('encoding' => 'utf8')
- end
-
- def test_creates_database_with_given_encoding_and_no_collation
+ def test_creates_database_with_given_encoding
@connection.expects(:create_database).
with('my-app-db', charset: 'latin1')
ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge('encoding' => 'latin1')
end
- def test_creates_database_with_given_collation_and_no_encoding
+ def test_creates_database_with_given_collation
@connection.expects(:create_database).
with('my-app-db', collation: 'latin1_swedish_ci')
@@ -111,7 +104,7 @@ module ActiveRecord
def test_database_created_by_root
assert_permissions_granted_for "pat"
@connection.expects(:create_database).
- with('my-app-db', :charset => 'utf8', :collation => 'utf8_unicode_ci')
+ with('my-app-db', {})
ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
@@ -203,9 +196,9 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.purge @configuration
end
- def test_recreates_database_with_the_default_options
+ def test_recreates_database_with_no_default_options
@connection.expects(:recreate_database).
- with('test-db', charset: 'utf8', collation: 'utf8_unicode_ci')
+ with('test-db', {})
ActiveRecord::Tasks::DatabaseTasks.purge @configuration
end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index c3fd0b2383..c31f94b2f2 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -60,7 +60,7 @@ module ActiveRecord
$stderr.expects(:puts).
with("Couldn't create database for #{@configuration.inspect}")
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration }
end
def test_create_when_database_exists_outputs_info_to_stderr
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index 750d5e42dc..0aea0c3b38 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -53,7 +53,7 @@ module ActiveRecord
$stderr.expects(:puts).
with("Couldn't create database for #{@configuration.inspect}")
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' }
end
end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index bff5ffa65e..584a3dc0d8 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -45,6 +45,18 @@ class AssociationValidationTest < ActiveRecord::TestCase
assert t.valid?
end
+ def test_validates_associated_without_marked_for_destruction
+ reply = Class.new do
+ def valid?
+ true
+ end
+ end
+ Topic.validates_associated(:replies)
+ t = Topic.new
+ t.define_singleton_method(:replies) { [reply.new] }
+ assert t.valid?
+ end
+
def test_validates_associated_with_custom_message_using_quotes
Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
Topic.validates_presence_of :content
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index e80d8bd584..d50ae74e35 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -45,7 +45,7 @@ module ViewBehavior
def test_table_exists
view_name = Ebook.table_name
# TODO: switch this assertion around once we changed #tables to not return views.
- assert @connection.table_exists?(view_name), "'#{view_name}' table should exist"
+ ActiveSupport::Deprecation.silence { assert @connection.table_exists?(view_name), "'#{view_name}' table should exist" }
end
def test_views_ara_valid_data_sources
@@ -87,7 +87,7 @@ class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
end
def drop_view(name)
- @connection.execute "DROP VIEW #{name}" if @connection.table_exists? name
+ @connection.execute "DROP VIEW #{name}" if @connection.view_exists? name
end
end
@@ -106,7 +106,7 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute "DROP VIEW paperbacks" if @connection.table_exists? "paperbacks"
+ @connection.execute "DROP VIEW paperbacks" if @connection.view_exists? "paperbacks"
end
def test_reading
@@ -125,7 +125,8 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
def test_table_exists
view_name = Paperback.table_name
- assert @connection.table_exists?(view_name), "'#{view_name}' table should exist"
+ # TODO: switch this assertion around once we changed #tables to not return views.
+ ActiveSupport::Deprecation.silence { assert @connection.table_exists?(view_name), "'#{view_name}' table should exist" }
end
def test_column_definitions
@@ -167,7 +168,7 @@ class UpdateableViewTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute "DROP VIEW printed_books" if @connection.table_exists? "printed_books"
+ @connection.execute "DROP VIEW printed_books" if @connection.view_exists? "printed_books"
end
def test_update_record
@@ -209,8 +210,7 @@ class MaterializedViewTest < ActiveRecord::PostgreSQLTestCase
end
def drop_view(name)
- @connection.execute "DROP MATERIALIZED VIEW #{name}" if @connection.table_exists? name
-
+ @connection.execute "DROP MATERIALIZED VIEW #{name}" if @connection.view_exists? name
end
end
end
diff --git a/activerecord/test/fixtures/content.yml b/activerecord/test/fixtures/content.yml
new file mode 100644
index 0000000000..0d12ee03dc
--- /dev/null
+++ b/activerecord/test/fixtures/content.yml
@@ -0,0 +1,3 @@
+content:
+ id: 1
+ title: How to use Rails
diff --git a/activerecord/test/fixtures/content_positions.yml b/activerecord/test/fixtures/content_positions.yml
new file mode 100644
index 0000000000..9e85773f8e
--- /dev/null
+++ b/activerecord/test/fixtures/content_positions.yml
@@ -0,0 +1,3 @@
+content_positions:
+ id: 1
+ content_id: 1
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index a6e83fe353..c1e491e5c5 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -1,6 +1,7 @@
class Bulb < ActiveRecord::Base
default_scope { where(:name => 'defaulty') }
belongs_to :car, :touch => true
+ scope :awesome, -> { where(frickinawesome: true) }
attr_reader :scope_after_initialize, :attributes_after_initialize
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index 81263b79d1..778c22b1f6 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -4,6 +4,7 @@ class Car < ActiveRecord::Base
has_many :funky_bulbs, class_name: 'FunkyBulb', dependent: :destroy
has_many :failed_bulbs, class_name: 'FailedBulb', dependent: :destroy
has_many :foo_bulbs, -> { where(:name => 'foo') }, :class_name => "Bulb"
+ has_many :awesome_bulbs, -> { awesome }, class_name: "Bulb"
has_one :bulb
diff --git a/activerecord/test/models/content.rb b/activerecord/test/models/content.rb
new file mode 100644
index 0000000000..140e1dfc78
--- /dev/null
+++ b/activerecord/test/models/content.rb
@@ -0,0 +1,40 @@
+class Content < ActiveRecord::Base
+ self.table_name = 'content'
+ has_one :content_position, dependent: :destroy
+
+ def self.destroyed_ids
+ @destroyed_ids ||= []
+ end
+
+ before_destroy do |object|
+ Content.destroyed_ids << object.id
+ end
+end
+
+class ContentWhichRequiresTwoDestroyCalls < ActiveRecord::Base
+ self.table_name = 'content'
+ has_one :content_position, foreign_key: 'content_id', dependent: :destroy
+
+ after_initialize do
+ @destroy_count = 0
+ end
+
+ before_destroy do
+ @destroy_count += 1
+ if @destroy_count == 1
+ throw :abort
+ end
+ end
+end
+
+class ContentPosition < ActiveRecord::Base
+ belongs_to :content, dependent: :destroy
+
+ def self.destroyed_ids
+ @destroyed_ids ||= []
+ end
+
+ before_destroy do |object|
+ ContentPosition.destroyed_ids << object.id
+ end
+end
diff --git a/activerecord/test/models/guitar.rb b/activerecord/test/models/guitar.rb
new file mode 100644
index 0000000000..cd068ff53d
--- /dev/null
+++ b/activerecord/test/models/guitar.rb
@@ -0,0 +1,4 @@
+class Guitar < ActiveRecord::Base
+ has_many :tuning_pegs, index_errors: true
+ accepts_nested_attributes_for :tuning_pegs
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 81a18188d4..23cebe2602 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -185,6 +185,7 @@ class SubStiPost < StiPost
end
class FirstPost < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
default_scope { where(:id => 1) }
@@ -193,6 +194,7 @@ class FirstPost < ActiveRecord::Base
end
class PostWithDefaultInclude < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
default_scope { includes(:comments) }
has_many :comments, :foreign_key => :post_id
@@ -204,6 +206,7 @@ class PostWithSpecialCategorization < Post
end
class PostWithDefaultScope < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
default_scope { order(:title) }
end
@@ -225,11 +228,13 @@ class PostWithIncludesDefaultScope < ActiveRecord::Base
end
class SpecialPostWithDefaultScope < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
default_scope { where(:id => [1, 5,6]) }
end
class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
has_many :comments, class_name: "CommentThatAutomaticallyAltersPostBody", foreign_key: :post_id
@@ -239,6 +244,7 @@ class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base
end
class PostWithAfterCreateCallback < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
has_many :comments, foreign_key: :post_id
@@ -248,6 +254,7 @@ class PostWithAfterCreateCallback < ActiveRecord::Base
end
class PostWithCommentWithDefaultScopeReferencesAssociation < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
has_many :comment_with_default_scope_references_associations, foreign_key: :post_id
has_one :first_comment, class_name: "CommentWithDefaultScopeReferencesAssociation", foreign_key: :post_id
diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb
index b034e0e267..efa8246f1e 100644
--- a/activerecord/test/models/project.rb
+++ b/activerecord/test/models/project.rb
@@ -15,6 +15,14 @@ class Project < ActiveRecord::Base
belongs_to :firm
has_one :lead_developer, through: :firm, inverse_of: :contracted_projects
+ begin
+ previous_value, ActiveRecord::Base.belongs_to_required_by_default =
+ ActiveRecord::Base.belongs_to_required_by_default, true
+ has_and_belongs_to_many :developers_required_by_default, class_name: "Developer"
+ ensure
+ ActiveRecord::Base.belongs_to_required_by_default = previous_value
+ end
+
attr_accessor :developers_log
after_initialize :set_developers_log
diff --git a/activerecord/test/models/tuning_peg.rb b/activerecord/test/models/tuning_peg.rb
new file mode 100644
index 0000000000..1252d6dc1d
--- /dev/null
+++ b/activerecord/test/models/tuning_peg.rb
@@ -0,0 +1,4 @@
+class TuningPeg < ActiveRecord::Base
+ belongs_to :guitar
+ validates_numericality_of :pitch
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 690ab1af4b..99098017d7 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -114,7 +114,7 @@ ActiveRecord::Schema.define do
create_table :bulbs, force: true do |t|
t.integer :car_id
t.string :name
- t.boolean :frickinawesome
+ t.boolean :frickinawesome, default: false
t.string :color
end
@@ -207,6 +207,14 @@ ActiveRecord::Schema.define do
add_index :companies, [:firm_id, :type], name: "company_partial_index", where: "rating > 10"
add_index :companies, :name, name: 'company_name_index', using: :btree
+ create_table :content, force: true do |t|
+ t.string :title
+ end
+
+ create_table :content_positions, force: true do |t|
+ t.integer :content_id
+ end
+
create_table :vegetables, force: true do |t|
t.string :name
t.integer :seller_id
@@ -348,6 +356,10 @@ ActiveRecord::Schema.define do
t.column :key, :string
end
+ create_table :guitar, force: true do |t|
+ t.string :color
+ end
+
create_table :inept_wizards, force: true do |t|
t.column :name, :string, null: false
t.column :city, :string, null: false
@@ -851,6 +863,11 @@ ActiveRecord::Schema.define do
t.belongs_to :ship
end
+ create_table :tuning_pegs, force: true do |t|
+ t.integer :guitar_id
+ t.float :pitch
+ end
+
create_table :tyres, force: true do |t|
t.integer :car_id
end
diff --git a/activerecord/test/support/schema_dumping_helper.rb b/activerecord/test/support/schema_dumping_helper.rb
index 2d1651454d..666c1b6a14 100644
--- a/activerecord/test/support/schema_dumping_helper.rb
+++ b/activerecord/test/support/schema_dumping_helper.rb
@@ -1,7 +1,7 @@
module SchemaDumpingHelper
def dump_table_schema(table, connection = ActiveRecord::Base.connection)
old_ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
- ActiveRecord::SchemaDumper.ignore_tables = connection.tables - [table]
+ ActiveRecord::SchemaDumper.ignore_tables = connection.data_sources - [table]
stream = StringIO.new
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
stream.string