aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorKasper Timm Hansen <kaspth@gmail.com>2017-07-15 21:17:27 +0200
committerKasper Timm Hansen <kaspth@gmail.com>2017-07-15 21:17:27 +0200
commitaad42dce10a9cc110d67fa03a72ce5b41cbb394a (patch)
tree7c22623e9f34f2ff7bfa142385010b6a4e5aea85 /activerecord
parent92fb8621a623afb33a145b1f3736010a3c870086 (diff)
parentb6300f3ecc79bff29cf9bb804a30fd92403feac1 (diff)
downloadrails-aad42dce10a9cc110d67fa03a72ce5b41cbb394a.tar.gz
rails-aad42dce10a9cc110d67fa03a72ce5b41cbb394a.tar.bz2
rails-aad42dce10a9cc110d67fa03a72ce5b41cbb394a.zip
Merge branch 'master' into unlock-minitest
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md91
-rw-r--r--activerecord/Rakefile4
-rw-r--r--activerecord/activerecord.gemspec5
-rwxr-xr-xactiverecord/bin/test2
-rw-r--r--activerecord/lib/active_record.rb4
-rw-r--r--activerecord/lib/active_record/associations.rb14
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb19
-rw-r--r--activerecord/lib/active_record/associations/association.rb12
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb21
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb23
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb14
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb64
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_base.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb17
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb68
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb19
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb9
-rw-r--r--activerecord/lib/active_record/attribute.rb2
-rw-r--r--activerecord/lib/active_record/attribute/user_provided_default.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb19
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb33
-rw-r--r--activerecord/lib/active_record/attribute_mutation_tracker.rb5
-rw-r--r--activerecord/lib/active_record/attribute_set.rb4
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb2
-rw-r--r--activerecord/lib/active_record/attributes.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb3
-rw-r--r--activerecord/lib/active_record/base.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb86
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb40
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb50
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb72
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb43
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb50
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb46
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb28
-rw-r--r--activerecord/lib/active_record/core.rb19
-rw-r--r--activerecord/lib/active_record/errors.rb2
-rw-r--r--activerecord/lib/active_record/explain.rb4
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb11
-rw-r--r--activerecord/lib/active_record/inheritance.rb2
-rw-r--r--activerecord/lib/active_record/internal_metadata.rb4
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb9
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb7
-rw-r--r--activerecord/lib/active_record/migration.rb16
-rw-r--r--activerecord/lib/active_record/model_schema.rb8
-rw-r--r--activerecord/lib/active_record/persistence.rb4
-rw-r--r--activerecord/lib/active_record/query_cache.rb12
-rw-r--r--activerecord/lib/active_record/railtie.rb10
-rw-r--r--activerecord/lib/active_record/railties/controller_runtime.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake6
-rw-r--r--activerecord/lib/active_record/reflection.rb55
-rw-r--r--activerecord/lib/active_record/relation.rb42
-rw-r--r--activerecord/lib/active_record/relation/batches.rb3
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb10
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb14
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb35
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb33
-rw-r--r--activerecord/lib/active_record/relation/query_attribute.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb17
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb3
-rw-r--r--activerecord/lib/active_record/schema_migration.rb4
-rw-r--r--activerecord/lib/active_record/scoping.rb7
-rw-r--r--activerecord/lib/active_record/scoping/named.rb5
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb8
-rw-r--r--activerecord/lib/active_record/timestamp.rb6
-rw-r--r--activerecord/lib/active_record/transactions.rb2
-rw-r--r--activerecord/lib/active_record/type.rb24
-rw-r--r--activerecord/lib/active_record/type_caster.rb4
-rw-r--r--activerecord/lib/active_record/validations.rb10
-rw-r--r--activerecord/test/cases/adapter_test.rb22
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb151
-rw-r--r--activerecord/test/cases/adapters/mysql2/virtual_column_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb16
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb13
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb4
-rw-r--r--activerecord/test/cases/associations/association_scope_test.rb3
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb1
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb6
-rw-r--r--activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb27
-rw-r--r--activerecord/test/cases/associations/eager_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb59
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb11
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb9
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb34
-rw-r--r--activerecord/test/cases/attribute_test.rb4
-rw-r--r--activerecord/test/cases/batches_test.rb65
-rw-r--r--activerecord/test/cases/binary_test.rb2
-rw-r--r--activerecord/test/cases/calculations_test.rb49
-rw-r--r--activerecord/test/cases/connection_pool_test.rb13
-rw-r--r--activerecord/test/cases/core_test.rb8
-rw-r--r--activerecord/test/cases/date_time_test.rb13
-rw-r--r--activerecord/test/cases/enum_test.rb2
-rw-r--r--activerecord/test/cases/errors_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb38
-rw-r--r--activerecord/test/cases/fixtures_test.rb32
-rw-r--r--activerecord/test/cases/json_shared_test_cases.rb19
-rw-r--r--activerecord/test/cases/locking_test.rb71
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb15
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb10
-rw-r--r--activerecord/test/cases/migration_test.rb14
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb2
-rw-r--r--activerecord/test/cases/primary_keys_test.rb23
-rw-r--r--activerecord/test/cases/query_cache_test.rb76
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb15
-rw-r--r--activerecord/test/cases/relation/merging_test.rb2
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb7
-rw-r--r--activerecord/test/cases/relation/or_test.rb25
-rw-r--r--activerecord/test/cases/relation/where_test.rb6
-rw-r--r--activerecord/test/cases/relation_test.rb23
-rw-r--r--activerecord/test/cases/relations_test.rb64
-rw-r--r--activerecord/test/cases/reserved_word_test.rb132
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb8
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb9
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb4
-rw-r--r--activerecord/test/cases/transactions_test.rb154
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb8
-rw-r--r--activerecord/test/fixtures/naked/yml/parrots.yml1
-rw-r--r--activerecord/test/migrations/magic/1_currencies_have_symbols.rb1
-rw-r--r--activerecord/test/models/club.rb2
-rw-r--r--activerecord/test/models/comment.rb1
-rw-r--r--activerecord/test/models/member.rb1
-rw-r--r--activerecord/test/models/membership.rb1
-rw-r--r--activerecord/test/models/post.rb1
-rw-r--r--activerecord/test/models/subject.rb14
-rw-r--r--activerecord/test/schema/schema.rb21
152 files changed, 1789 insertions, 1011 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 1f8163db12..786bef7359 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,94 @@
+* Skip query caching when working with batches of records (`find_each`, `find_in_batches`,
+ `in_batches`).
+
+ Previously, records would be fetched in batches, but all records would be retained in memory
+ until the end of the request or job.
+
+ *Eugene Kenny*
+
+* Prevent errors raised by `sql.active_record` notification subscribers from being converted into
+ `ActiveRecord::StatementInvalid` exceptions.
+
+ *Dennis Taylor*
+
+* Fix eager loading/preloading association with scope including joins.
+
+ Fixes #28324.
+
+ *Ryuta Kamizono*
+
+* Fix transactions to apply state to child transactions
+
+ Previously if you had a nested transaction and the outer transaction was rolledback the record from the
+ inner transaction would still be marked as persisted.
+
+ This change fixes that by applying the state of the parent transaction to the child transaction when the
+ parent transaction is rolledback. This will correctly mark records from the inner transaction as not persisted.
+
+ *Eileen M. Uchitelle*, *Aaron Patterson*
+
+* Deprecate `set_state` method in `TransactionState`
+
+ Deprecated the `set_state` method in favor of setting the state via specific methods. If you need to mark the
+ state of the transaction you can now use `rollback!`, `commit!` or `nullify!` instead of
+ `set_state(:rolledback)`, `set_state(:committed)`, or `set_state(nil)`.
+
+ *Eileen M. Uchitelle*, *Aaron Patterson*
+
+* Deprecate delegating to `arel` in `Relation`.
+
+ *Ryuta Kamizono*
+
+* Fix eager loading to respect `store_full_sti_class` setting.
+
+ *Ryuta Kamizono*
+
+* Query cache was unavailable when entering the `ActiveRecord::Base.cache` block
+ without being connected.
+
+ *Tsukasa Oishi*
+
+* Previously, when building records using a `has_many :through` association,
+ if the child records were deleted before the parent was saved, they would
+ still be persisted. Now, if child records are deleted before the parent is saved
+ on a `has_many :through` association, the child records will not be persisted.
+
+ *Tobias Kraze*
+
+* Merging two relations representing nested joins no longer transforms the joins of
+ the merged relation into LEFT OUTER JOIN. Example to clarify:
+
+ ```
+ Author.joins(:posts).merge(Post.joins(:comments))
+ # Before the change:
+ #=> SELECT ... FROM authors INNER JOIN posts ON ... LEFT OUTER JOIN comments ON...
+
+ # After the change:
+ #=> SELECT ... FROM authors INNER JOIN posts ON ... INNER JOIN comments ON...
+ ```
+
+ TODO: Add to the Rails 5.2 upgrade guide
+
+ *Maxime Handfield Lapointe*
+
+* `ActiveRecord::Persistence#touch` does not work well when optimistic locking enabled and
+ `locking_column`, without default value, is null in the database.
+
+ *bogdanvlviv*
+
+* Fix destroying existing object does not work well when optimistic locking enabled and
+ `locking_column` is null in the database.
+
+ *bogdanvlviv*
+
+* Use bulk INSERT to insert fixtures for better performance.
+
+ *Kir Shatrov*
+
+* Prevent making bind param if casted value is nil.
+
+ *Ryuta Kamizono*
+
* Deprecate passing arguments and block at the same time to `count` and `sum` in `ActiveRecord::Calculations`.
*Ryuta Kamizono*
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 2d0d5bd657..fe5f9d1071 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,7 +1,7 @@
require "rake/testtask"
-require File.expand_path("test/config", __dir__)
-require File.expand_path("test/support/config", __dir__)
+require_relative "test/config"
+require_relative "test/support/config"
def run_without_aborting(*tasks)
errors = []
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 450ec0bba9..a626a1f21b 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -21,6 +21,11 @@ Gem::Specification.new do |s|
s.extra_rdoc_files = %w(README.rdoc)
s.rdoc_options.concat ["--main", "README.rdoc"]
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/activerecord",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/activerecord/CHANGELOG.md"
+ }
+
s.add_dependency "activesupport", version
s.add_dependency "activemodel", version
diff --git a/activerecord/bin/test b/activerecord/bin/test
index 3a9547e5c1..ab69f4f603 100755
--- a/activerecord/bin/test
+++ b/activerecord/bin/test
@@ -1,7 +1,7 @@
#!/usr/bin/env ruby
COMPONENT_ROOT = File.expand_path("..", __dir__)
-require File.expand_path("../tools/test", COMPONENT_ROOT)
+require_relative "../../tools/test"
module Minitest
def self.plugin_active_record_options(opts, options)
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 29f49c6195..25d5e87317 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -26,8 +26,8 @@ require "active_support/rails"
require "active_model"
require "arel"
-require "active_record/version"
-require "active_record/attribute_set"
+require_relative "active_record/version"
+require_relative "active_record/attribute_set"
module ActiveRecord
extend ActiveSupport::Autoload
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 7c37132d3a..e782196ce6 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1,7 +1,7 @@
require "active_support/core_ext/enumerable"
require "active_support/core_ext/string/conversions"
require "active_support/core_ext/module/remove_method"
-require "active_record/errors"
+require_relative "errors"
module ActiveRecord
class AssociationNotFoundError < ConfigurationError #:nodoc:
@@ -342,7 +342,7 @@ module ActiveRecord
# | | belongs_to |
# generated methods | belongs_to | :polymorphic | has_one
# ----------------------------------+------------+--------------+---------
- # other(force_reload=false) | X | X | X
+ # other | X | X | X
# other=(other) | X | X | X
# build_other(attributes={}) | X | | X
# create_other(attributes={}) | X | | X
@@ -352,7 +352,7 @@ module ActiveRecord
# | | | has_many
# generated methods | habtm | has_many | :through
# ----------------------------------+-------+----------+----------
- # others(force_reload=false) | X | X | X
+ # others | X | X | X
# others=(other,other,...) | X | X | X
# other_ids | X | X | X
# other_ids=(id,id,...) | X | X | X
@@ -1187,7 +1187,7 @@ module ActiveRecord
# +collection+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
#
- # [collection(force_reload = false)]
+ # [collection]
# Returns an array of all the associated objects.
# An empty array is returned if none are found.
# [collection<<(object, ...)]
@@ -1407,7 +1407,7 @@ module ActiveRecord
# +association+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
#
- # [association(force_reload = false)]
+ # [association]
# Returns the associated object. +nil+ is returned if none is found.
# [association=(associate)]
# Assigns the associate object, extracts the primary key, sets it as the foreign key,
@@ -1539,7 +1539,7 @@ module ActiveRecord
# +association+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
#
- # [association(force_reload = false)]
+ # [association]
# Returns the associated object. +nil+ is returned if none is found.
# [association=(associate)]
# Assigns the associate object, extracts the primary key, and sets it as the foreign key.
@@ -1701,7 +1701,7 @@ module ActiveRecord
# +collection+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
#
- # [collection(force_reload = false)]
+ # [collection]
# Returns an array of all the associated objects.
# An empty array is returned if none are found.
# [collection<<(object, ...)]
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 4a5c821607..104de4f69d 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -4,21 +4,21 @@ module ActiveRecord
module Associations
# Keeps track of table aliases for ActiveRecord::Associations::JoinDependency
class AliasTracker # :nodoc:
- def self.create(connection, initial_table, type_caster)
+ def self.create(connection, initial_table)
aliases = Hash.new(0)
aliases[initial_table] = 1
- new connection, aliases, type_caster
+ new(connection, aliases)
end
- def self.create_with_joins(connection, initial_table, joins, type_caster)
+ def self.create_with_joins(connection, initial_table, joins)
if joins.empty?
- create(connection, initial_table, type_caster)
+ create(connection, initial_table)
else
aliases = Hash.new { |h, k|
h[k] = initial_count_for(connection, k, joins)
}
aliases[initial_table] = 1
- new connection, aliases, type_caster
+ new(connection, aliases)
end
end
@@ -51,17 +51,16 @@ module ActiveRecord
end
# table_joins is an array of arel joins which might conflict with the aliases we assign here
- def initialize(connection, aliases, type_caster)
+ def initialize(connection, aliases)
@aliases = aliases
@connection = connection
- @type_caster = type_caster
end
- def aliased_table_for(table_name, aliased_name)
+ def aliased_table_for(table_name, aliased_name, type_caster)
if aliases[table_name].zero?
# If it's zero, we can have our table_name
aliases[table_name] = 1
- Arel::Table.new(table_name, type_caster: @type_caster)
+ Arel::Table.new(table_name, type_caster: type_caster)
else
# Otherwise, we need to use an alias
aliased_name = @connection.table_alias_for(aliased_name)
@@ -74,7 +73,7 @@ module ActiveRecord
else
aliased_name
end
- Arel::Table.new(table_name, type_caster: @type_caster).alias(table_alias)
+ Arel::Table.new(table_name, type_caster: type_caster).alias(table_alias)
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index ee2e79889f..1138ae3462 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -30,14 +30,6 @@ module ActiveRecord
reset_scope
end
- # Returns the name of the table of the associated class:
- #
- # post.comments.aliased_table_name # => "comments"
- #
- def aliased_table_name
- klass.table_name
- end
-
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
def reset
@loaded = false
@@ -94,7 +86,7 @@ module ActiveRecord
# actually gets built.
def association_scope
if klass
- @association_scope ||= AssociationScope.scope(self, klass.connection)
+ @association_scope ||= AssociationScope.scope(self)
end
end
@@ -276,7 +268,7 @@ module ActiveRecord
end
# Returns true if statement cache should be skipped on the association reader.
- def skip_statement_cache?
+ def skip_statement_cache?(scope)
reflection.has_scope? ||
scope.eager_loading? ||
klass.scope_attributes? ||
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 1593b94f0c..6ef225b725 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -1,8 +1,8 @@
module ActiveRecord
module Associations
class AssociationScope #:nodoc:
- def self.scope(association, connection)
- INSTANCE.scope(association, connection)
+ def self.scope(association)
+ INSTANCE.scope(association)
end
def self.create(&block)
@@ -16,12 +16,12 @@ module ActiveRecord
INSTANCE = create
- def scope(association, connection)
+ def scope(association)
klass = association.klass
reflection = association.reflection
scope = klass.unscoped
owner = association.owner
- alias_tracker = AliasTracker.create connection, association.klass.table_name, klass.type_caster
+ alias_tracker = AliasTracker.create(klass.connection, klass.table_name)
chain_head, chain_tail = get_chain(reflection, association, alias_tracker)
scope.extending! reflection.extensions
@@ -112,7 +112,11 @@ module ActiveRecord
runtime_reflection = Reflection::RuntimeReflection.new(reflection, association)
previous_reflection = runtime_reflection
reflection.chain.drop(1).each do |refl|
- alias_name = tracker.aliased_table_for(refl.table_name, refl.alias_candidate(name))
+ alias_name = tracker.aliased_table_for(
+ refl.table_name,
+ refl.alias_candidate(name),
+ refl.klass.type_caster
+ )
proxy = ReflectionProxy.new(refl, alias_name)
previous_reflection.next = proxy
previous_reflection = proxy
@@ -138,7 +142,7 @@ module ActiveRecord
# Exclude the scope of the association itself, because that
# was already merged in the #scope method.
reflection.constraints.each do |scope_chain_item|
- item = eval_scope(reflection.klass, table, scope_chain_item, owner)
+ item = eval_scope(reflection, table, scope_chain_item, owner)
if scope_chain_item == refl.scope
scope.merge! item.except(:where, :includes)
@@ -159,9 +163,8 @@ module ActiveRecord
scope
end
- def eval_scope(klass, table, scope, owner)
- predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table))
- ActiveRecord::Relation.create(klass, table, predicate_builder).instance_exec(owner, &scope)
+ def eval_scope(reflection, table, scope, owner)
+ reflection.build_scope(table).instance_exec(owner, &scope)
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index edeb6491bd..c58b7d8160 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -1,6 +1,4 @@
-# This class is inherited by the has_many and has_many_and_belongs_to_many association classes
-
-require "active_record/associations"
+require_relative "../../associations"
module ActiveRecord::Associations::Builder # :nodoc:
class CollectionAssociation < Association #:nodoc:
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index edc53e2517..a49fb155ee 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -44,10 +44,7 @@ module ActiveRecord
if loaded?
target.pluck(reflection.association_primary_key)
else
- @association_ids ||= (
- column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
- scope.pluck(column)
- )
+ @association_ids ||= scope.pluck(reflection.association_primary_key)
end
end
@@ -69,6 +66,7 @@ module ActiveRecord
def reset
super
@target = []
+ @association_ids = nil
end
def find(*args)
@@ -300,13 +298,14 @@ module ActiveRecord
private
def find_target
- return scope.to_a if skip_statement_cache?
+ scope = self.scope
+ return scope.to_a if skip_statement_cache?(scope)
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
StatementCache.create(conn) { |params|
as = AssociationScope.create { params.bind }
- target_scope.merge as.scope(self, conn)
+ target_scope.merge!(as.scope(self))
}
end
@@ -357,7 +356,10 @@ module ActiveRecord
transaction do
add_to_target(build_record(attributes)) do |record|
yield(record) if block_given?
- insert_record(record, true, raise) { @_was_loaded = loaded? }
+ insert_record(record, true, raise) {
+ @_was_loaded = loaded?
+ @association_ids = nil
+ }
end
end
end
@@ -430,7 +432,12 @@ module ActiveRecord
records.each do |record|
raise_on_type_mismatch!(record)
add_to_target(record) do
- result &&= insert_record(record, true, raise) { @_was_loaded = loaded? } unless owner.new_record?
+ unless owner.new_record?
+ result &&= insert_record(record, true, raise) {
+ @_was_loaded = loaded?
+ @association_ids = nil
+ }
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 8cdb508c43..d77fcaf668 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -1114,6 +1114,7 @@ module ActiveRecord
end
def reset_scope # :nodoc:
+ @offsets = {}
@scope = nil
self
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 53ffb3b68d..2fd20b4368 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -109,6 +109,11 @@ module ActiveRecord
record
end
+ def remove_records(existing_records, records, method)
+ super
+ delete_through_records(records)
+ end
+
def target_reflection_has_associated_record?
!(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
end
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 643226267c..04cdcb6a7f 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -93,7 +93,7 @@ module ActiveRecord
# joins # => []
#
def initialize(base, associations, joins, eager_loading: true)
- @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins, base.type_caster)
+ @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins)
@eager_loading = eager_loading
tree = self.class.make_tree associations
@join_root = JoinBase.new base, build(tree, base)
@@ -104,17 +104,17 @@ module ActiveRecord
join_root.drop(1).map!(&:reflection)
end
- def join_constraints(outer_joins, join_type)
+ def join_constraints(joins_to_add, join_type)
joins = join_root.children.flat_map { |child|
make_join_constraints(join_root, child, join_type)
}
- joins.concat outer_joins.flat_map { |oj|
+ joins.concat joins_to_add.flat_map { |oj|
if join_root.match? oj.join_root
walk join_root, oj.join_root
else
oj.join_root.children.flat_map { |child|
- make_outer_joins oj.join_root, child
+ make_join_constraints(oj.join_root, child, join_type)
}
end
}
@@ -185,7 +185,8 @@ module ActiveRecord
node.reflection.chain.map { |reflection|
alias_tracker.aliased_table_for(
reflection.table_name,
- table_alias_for(reflection, parent, reflection != node.reflection)
+ table_alias_for(reflection, parent, reflection != node.reflection),
+ reflection.klass.type_caster
)
}
end
@@ -197,8 +198,7 @@ module ActiveRecord
def table_alias_for(reflection, parent, join)
name = "#{reflection.plural_name}_#{parent.table_name}"
- name << "_join" if join
- name
+ join ? "#{name}_join" : name
end
def walk(left, right)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 97cfec0302..b14ddfeeeb 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -1,4 +1,4 @@
-require "active_record/associations/join_dependency/join_part"
+require_relative "join_part"
module ActiveRecord
module Associations
@@ -34,37 +34,19 @@ module ActiveRecord
table = tables.shift
klass = reflection.klass
- join_keys = reflection.join_keys
- key = join_keys.key
- foreign_key = join_keys.foreign_key
+ constraint = reflection.build_join_constraint(table, foreign_table)
- constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
-
- predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table))
- scope_chain_items = reflection.join_scopes(table, predicate_builder)
- klass_scope = reflection.klass_join_scope(table, predicate_builder)
-
- scope_chain_items.concat [klass_scope].compact
-
- rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
- left.merge right
- end
-
- if rel && !rel.arel.constraints.empty?
- binds += rel.bound_attributes
- constraint = constraint.and rel.arel.constraints
- end
+ joins << table.create_join(table, table.create_on(constraint), join_type)
- if reflection.type
- value = foreign_klass.base_class.name
- column = klass.columns_hash[reflection.type.to_s]
+ join_scope = reflection.join_scope(table, foreign_klass)
- binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name))
- constraint = constraint.and klass.arel_attribute(reflection.type, table).eq(Arel::Nodes::BindParam.new)
+ if join_scope.arel.constraints.any?
+ binds.concat join_scope.bound_attributes
+ joins.concat join_scope.arel.join_sources
+ right = joins.last.right
+ right.expr = right.expr.and(join_scope.arel.constraints)
end
- joins << table.create_join(table, table.create_on(constraint), join_type)
-
# The current table in this iteration becomes the foreign table in the next
foreign_table, foreign_klass = table, klass
end
@@ -72,34 +54,6 @@ module ActiveRecord
JoinInformation.new joins, binds
end
- # Builds equality condition.
- #
- # Example:
- #
- # class Physician < ActiveRecord::Base
- # has_many :appointments
- # end
- #
- # If I execute `Physician.joins(:appointments).to_a` then
- # klass # => Physician
- # table # => #<Arel::Table @name="appointments" ...>
- # key # => physician_id
- # foreign_table # => #<Arel::Table @name="physicians" ...>
- # foreign_key # => id
- #
- def build_constraint(klass, table, key, foreign_table, foreign_key)
- constraint = table[key].eq(foreign_table[foreign_key])
-
- if klass.finder_needs_type_condition?
- constraint = table.create_and([
- constraint,
- klass.send(:type_condition, table)
- ])
- end
-
- constraint
- end
-
def table
tables.first
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
index fca20514d1..6e0963425d 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
@@ -1,4 +1,4 @@
-require "active_record/associations/join_dependency/join_part"
+require_relative "join_part"
module ActiveRecord
module Associations
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 9f77f38b35..a18994cec4 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -54,8 +54,6 @@ 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
@@ -93,7 +91,6 @@ module ActiveRecord
def preload(records, associations, preload_scope = nil)
records = Array.wrap(records).compact.uniq
associations = Array.wrap(associations)
- preload_scope = preload_scope || NULL_RELATION
if records.empty?
[]
@@ -147,7 +144,7 @@ module ActiveRecord
def preloaders_for_one(association, records, scope)
grouped_records(association, records).flat_map do |reflection, klasses|
klasses.map do |rhs_klass, rs|
- loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
+ loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
loader.run self
loader
end
@@ -159,6 +156,7 @@ module ActiveRecord
records.each do |record|
next unless record
assoc = record.association(association)
+ next unless assoc.klass
klasses = h[assoc.reflection] ||= {}
(klasses[assoc.klass] ||= []) << record
end
@@ -180,20 +178,11 @@ module ActiveRecord
end
end
- class NullPreloader # :nodoc:
- def self.new(klass, owners, reflection, preload_scope); self; end
- def self.run(preloader); end
- def self.preloaded_records; []; end
- def self.owners; []; 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
-
+ def preloader_for(reflection, owners)
if owners.first.association(reflection.name).loaded?
return AlreadyLoaded
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 63ef3f2d8c..85343040db 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -11,7 +11,6 @@ module ActiveRecord
@reflection = reflection
@preload_scope = preload_scope
@model = owners.first && owners.first.class
- @scope = nil
@preloaded_records = []
end
@@ -23,29 +22,11 @@ module ActiveRecord
raise NotImplementedError
end
- def scope
- @scope ||= build_scope
- end
-
- def records_for(ids)
- scope.where(association_key_name => ids)
- end
-
- def table
- klass.arel_table
- end
-
# The name of the key on the associated records
def association_key_name
raise NotImplementedError
end
- # This is overridden by HABTM as the condition should be on the foreign_key column in
- # the join table
- def association_key
- klass.arel_attribute(association_key_name, table)
- end
-
# The name of the key on the model which declares the association
def owner_key_name
raise NotImplementedError
@@ -114,54 +95,35 @@ module ActiveRecord
# Make several smaller queries if necessary or make one query if the adapter supports it
slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
@preloaded_records = slices.flat_map do |slice|
- records_for(slice).load(&block)
+ records_for(slice, &block)
end
@preloaded_records.group_by do |record|
convert_key(record[association_key_name])
end
end
+ def records_for(ids, &block)
+ scope.where(association_key_name => ids).load(&block)
+ end
+
+ def scope
+ @scope ||= build_scope
+ end
+
def reflection_scope
@reflection_scope ||= reflection.scope_for(klass)
end
def build_scope
- scope = klass.unscoped
-
- values = reflection_scope.values
- preload_values = preload_scope.values
-
- scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause
- scope.references_values = Array(values[:references]) + Array(preload_values[:references])
-
- if preload_values[:select] || values[:select]
- scope._select!(preload_values[:select] || values[:select])
- end
- scope.includes! preload_values[:includes] || values[:includes]
- if preload_scope.joins_values.any?
- scope.joins!(preload_scope.joins_values)
- else
- scope.joins!(reflection_scope.joins_values)
- end
-
- 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!
- end
+ scope = klass.default_scoped
- if options[:as]
- scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
+ if reflection.type
+ scope.where!(reflection.type => model.base_class.sti_name)
end
- scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope])
- klass.default_scoped.merge(scope)
+ scope.merge!(reflection_scope)
+ scope.merge!(preload_scope) if preload_scope
+ scope
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 8b954138cd..0999746cd5 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -11,6 +11,8 @@ module ActiveRecord
end
def associated_records_by_owner(preloader)
+ through_scope = through_scope()
+
preloader.preload(owners,
through_reflection.name,
through_scope)
@@ -20,7 +22,7 @@ module ActiveRecord
[owner, Array(center)]
end
- reset_association owners, through_reflection.name
+ reset_association(owners, through_reflection.name, through_scope)
middle_records = through_records.flat_map(&:last)
@@ -63,7 +65,7 @@ module ActiveRecord
id_map
end
- def reset_association(owners, association_name)
+ def reset_association(owners, association_name, through_scope)
should_reset = (through_scope != through_reflection.klass.unscoped) ||
(options[:source_type] && through_reflection.collection?)
@@ -77,17 +79,24 @@ module ActiveRecord
def through_scope
scope = through_reflection.klass.unscoped
+ values = reflection_scope.values
if options[:source_type]
scope.where! reflection.foreign_type => options[:source_type]
else
unless reflection_scope.where_clause.empty?
- scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
+ scope.includes_values = Array(values[:includes] || options[:source])
scope.where_clause = reflection_scope.where_clause
+ if joins = values[:joins]
+ scope.joins!(source_reflection.name => joins)
+ end
+ if left_outer_joins = values[:left_outer_joins]
+ scope.left_outer_joins!(source_reflection.name => left_outer_joins)
+ end
end
- scope.references! reflection_scope.values[:references]
- if scope.eager_loading? && order_values = reflection_scope.values[:order]
+ scope.references! values[:references]
+ if scope.eager_loading? && order_values = values[:order]
scope = scope.order(order_values)
end
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 91580a28d0..f8bbe4c2ed 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -36,13 +36,14 @@ module ActiveRecord
end
def find_target
- return scope.take if skip_statement_cache?
+ scope = self.scope
+ return scope.take if skip_statement_cache?(scope)
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
StatementCache.create(conn) { |params|
as = AssociationScope.create { params.bind }
- target_scope.merge(as.scope(self, conn)).limit(1)
+ target_scope.merge!(as.scope(self)).limit(1)
}
end
@@ -63,6 +64,10 @@ module ActiveRecord
end
def _create_record(attributes, raise_error = false)
+ unless owner.persisted?
+ raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
+ end
+
record = build_record(attributes)
yield(record) if block_given?
saved = record.save
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 38281158d8..78662433eb 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -122,7 +122,7 @@ module ActiveRecord
def encode_with(coder)
coder["name"] = name
- coder["value_before_type_cast"] = value_before_type_cast if value_before_type_cast
+ coder["value_before_type_cast"] = value_before_type_cast unless value_before_type_cast.nil?
coder["type"] = type if type
coder["original_attribute"] = original_attribute if original_attribute
coder["value"] = value if defined?(@value)
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb
index 57f8bbed76..c4e731fb28 100644
--- a/activerecord/lib/active_record/attribute/user_provided_default.rb
+++ b/activerecord/lib/active_record/attribute/user_provided_default.rb
@@ -1,4 +1,4 @@
-require "active_record/attribute"
+require_relative "../attribute"
module ActiveRecord
class Attribute # :nodoc:
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 76987fb8f4..8c58e63931 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
require "active_support/core_ext/module/attribute_accessors"
-require "active_record/attribute_mutation_tracker"
+require_relative "../attribute_mutation_tracker"
module ActiveRecord
module AttributeMethods
@@ -80,7 +80,7 @@ module ActiveRecord
clear_mutation_trackers
end
- def raw_write_attribute(attr_name, *)
+ def write_attribute_without_type_cast(attr_name, *)
result = super
clear_attribute_change(attr_name)
result
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 2f32caa257..081aad434d 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -8,23 +8,20 @@ module ActiveRecord
# Returns this record's primary key value wrapped in an array if one is
# available.
def to_key
- sync_with_transaction_state
key = id
[key] if key
end
# Returns the primary key value.
def id
- if pk = self.class.primary_key
- sync_with_transaction_state
- _read_attribute(pk)
- end
+ sync_with_transaction_state
+ _read_attribute(self.class.primary_key) if self.class.primary_key
end
# Sets the primary key value.
def id=(value)
sync_with_transaction_state
- write_attribute(self.class.primary_key, value) if self.class.primary_key
+ _write_attribute(self.class.primary_key, value) if self.class.primary_key
end
# Queries the primary key value.
@@ -57,16 +54,12 @@ module ActiveRecord
end
module ClassMethods
- def define_method_attribute(attr_name)
- super
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set
- if attr_name == primary_key && attr_name != "id"
- generated_attribute_methods.send(:alias_method, :id, primary_key)
- end
+ def instance_method_already_implemented?(method_name)
+ super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
end
- ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set
-
def dangerous_attribute_method?(method_name)
super && !ID_ATTRIBUTE_METHODS.include?(method_name)
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 4a8d231503..1f1efe8812 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -54,8 +54,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- mattr_accessor :time_zone_aware_attributes, instance_writer: false
- self.time_zone_aware_attributes = false
+ mattr_accessor :time_zone_aware_attributes, instance_writer: false, default: false
class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: []
class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ]
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index fe0e01db28..54b673c72e 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -17,7 +17,7 @@ module ActiveRecord
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
def __temp__#{safe_name}=(value)
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
- write_attribute(name, value)
+ _write_attribute(name, value)
end
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
undef_method :__temp__#{safe_name}=
@@ -35,30 +35,27 @@ module ActiveRecord
attr_name.to_s
end
- write_attribute_with_type_cast(name, value, true)
+ name = self.class.primary_key if name == "id".freeze && self.class.primary_key
+ _write_attribute(name, value)
end
- def raw_write_attribute(attr_name, value) # :nodoc:
- write_attribute_with_type_cast(attr_name, value, false)
+ # This method exists to avoid the expensive primary_key check internally, without
+ # breaking compatibility with the write_attribute API
+ def _write_attribute(attr_name, value) # :nodoc:
+ @attributes.write_from_user(attr_name.to_s, value)
+ value
end
private
- # Handle *= for method_missing.
- def attribute=(attribute_name, value)
- write_attribute(attribute_name, value)
+ def write_attribute_without_type_cast(attr_name, value)
+ name = attr_name.to_s
+ @attributes.write_cast_value(name, value)
+ value
end
- def write_attribute_with_type_cast(attr_name, value, should_type_cast)
- attr_name = attr_name.to_s
- attr_name = self.class.primary_key if attr_name == "id" && self.class.primary_key
-
- if should_type_cast
- @attributes.write_from_user(attr_name, value)
- else
- @attributes.write_cast_value(attr_name, value)
- end
-
- value
+ # Handle *= for method_missing.
+ def attribute=(attribute_name, value)
+ _write_attribute(attribute_name, value)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb
index 4de993e169..a01a58f8a5 100644
--- a/activerecord/lib/active_record/attribute_mutation_tracker.rb
+++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb
@@ -26,6 +26,7 @@ module ActiveRecord
end
def change_to_attribute(attr_name)
+ attr_name = attr_name.to_s
if changed?(attr_name)
[attributes[attr_name].original_value, attributes.fetch_value(attr_name)]
end
@@ -44,7 +45,7 @@ module ActiveRecord
end
def changed_in_place?(attr_name)
- attributes[attr_name].changed_in_place?
+ attributes[attr_name.to_s].changed_in_place?
end
def forget_change(attr_name)
@@ -54,7 +55,7 @@ module ActiveRecord
end
def original_value(attr_name)
- attributes[attr_name].original_value
+ attributes[attr_name.to_s].original_value
end
def force_change(attr_name)
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index 01f9d815d5..6399e3de70 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -1,5 +1,5 @@
-require "active_record/attribute_set/builder"
-require "active_record/attribute_set/yaml_encoder"
+require_relative "attribute_set/builder"
+require_relative "attribute_set/yaml_encoder"
module ActiveRecord
class AttributeSet # :nodoc:
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb
index 2f624d32af..abe22b9ae4 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activerecord/lib/active_record/attribute_set/builder.rb
@@ -1,4 +1,4 @@
-require "active_record/attribute"
+require_relative "../attribute"
module ActiveRecord
class AttributeSet # :nodoc:
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 475b9beec4..dde22bcdaa 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -1,4 +1,4 @@
-require "active_record/attribute/user_provided_default"
+require_relative "attribute/user_provided_default"
module ActiveRecord
# See ActiveRecord::Attributes::ClassMethods for documentation
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 829a4f6e86..70f0e2af8e 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -140,8 +140,7 @@ module ActiveRecord
included do
Associations::Builder::Association.extensions << AssociationBuilderExtension
- mattr_accessor :index_nested_attribute_errors, instance_writer: false
- self.index_nested_attribute_errors = false
+ mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false
end
module ClassMethods # :nodoc:
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index ac1aa2df45..f0e455478a 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -13,14 +13,14 @@ require "active_support/core_ext/kernel/singleton_class"
require "active_support/core_ext/module/introspection"
require "active_support/core_ext/object/duplicable"
require "active_support/core_ext/class/subclasses"
-require "active_record/attribute_decorators"
-require "active_record/define_callbacks"
-require "active_record/errors"
-require "active_record/log_subscriber"
-require "active_record/explain_subscriber"
-require "active_record/relation/delegation"
-require "active_record/attributes"
-require "active_record/type_caster"
+require_relative "attribute_decorators"
+require_relative "define_callbacks"
+require_relative "errors"
+require_relative "log_subscriber"
+require_relative "explain_subscriber"
+require_relative "relation/delegation"
+require_relative "attributes"
+require_relative "type_caster"
module ActiveRecord #:nodoc:
# = Active Record
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 61bf5477aa..627b753f01 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -679,7 +679,7 @@ module ActiveRecord
# this block can't be easily moved into attempt_to_checkout_all_existing_connections's
# rescue block, because doing so would put it outside of synchronize section, without
# being in a critical section thread_report might become inaccurate
- msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds"
+ msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds".dup
thread_report = []
@connections.each do |conn|
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index c6811a4802..879626b72a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -51,9 +51,7 @@ module ActiveRecord
# Returns a single value from a record
def select_value(arel, name = nil, binds = [])
- if result = select_rows(arel, name, binds).first
- result.first
- end
+ single_value_from_rows(select_rows(arel, name, binds))
end
# Returns an array of the values of the first column in a select:
@@ -68,6 +66,18 @@ module ActiveRecord
select_all(arel, name, binds).rows
end
+ def query_value(sql, name = nil) # :nodoc:
+ single_value_from_rows(query(sql, name))
+ end
+
+ def query_values(sql, name = nil) # :nodoc:
+ query(sql, name).map(&:first)
+ end
+
+ def query(sql, name = nil) # :nodoc:
+ exec_query(sql, name).rows
+ end
+
# Executes the SQL statement in the context of this connection and returns
# the raw result from the connection adapter.
# Note: depending on your database connector, the result returned by this
@@ -296,6 +306,9 @@ module ActiveRecord
# Inserts the given fixture into the table. Overridden in adapters that require
# something beyond a simple insert (eg. Oracle).
+ # Most of adapters should implement `insert_fixtures` that leverages bulk SQL insert.
+ # We keep this method to provide fallback
+ # for databases like sqlite that do not support bulk inserts.
def insert_fixture(fixture, table_name)
fixture = fixture.stringify_keys
@@ -308,16 +321,52 @@ module ActiveRecord
raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
end
end
- key_list = fixture.keys.map { |name| quote_column_name(name) }
- value_list = binds.map(&:value_for_database).map do |value|
- begin
- quote(value)
- rescue TypeError
- quote(YAML.dump(value))
+
+ table = Arel::Table.new(table_name)
+
+ values = binds.map do |bind|
+ value = with_yaml_fallback(bind.value_for_database)
+ [table[bind.name], value]
+ end
+
+ manager = Arel::InsertManager.new
+ manager.into(table)
+ manager.insert(values)
+ execute manager.to_sql, "Fixture Insert"
+ end
+
+ # Inserts a set of fixtures into the table. Overridden in adapters that require
+ # something beyond a simple insert (eg. Oracle).
+ def insert_fixtures(fixtures, table_name)
+ return if fixtures.empty?
+
+ columns = schema_cache.columns_hash(table_name)
+
+ values = fixtures.map do |fixture|
+ fixture = fixture.stringify_keys
+
+ unknown_columns = fixture.keys - columns.keys
+ if unknown_columns.any?
+ raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.)
+ end
+
+ columns.map do |name, column|
+ if fixture.key?(name)
+ type = lookup_cast_type_from_column(column)
+ bind = Relation::QueryAttribute.new(name, fixture[name], type)
+ with_yaml_fallback(bind.value_for_database)
+ else
+ Arel.sql("DEFAULT")
+ end
end
end
- execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", "Fixture Insert"
+ table = Arel::Table.new(table_name)
+ manager = Arel::InsertManager.new
+ manager.into(table)
+ columns.each_key { |column| manager.columns << table[column] }
+ manager.values = manager.create_values_list(values)
+ execute manager.to_sql, "Fixtures Insert"
end
def empty_insert_statement_value
@@ -371,7 +420,11 @@ module ActiveRecord
end
def last_inserted_id(result)
- row = result.rows.first
+ single_value_from_rows(result.rows)
+ end
+
+ def single_value_from_rows(rows)
+ row = rows.first
row && row.first
end
@@ -381,6 +434,17 @@ module ActiveRecord
end
[relation, binds]
end
+
+ # Fixture value is quoted by Arel, however scalar values
+ # are not quotable. In this case we want to convert
+ # the column value to YAML.
+ def with_yaml_fallback(value)
+ if value.is_a?(Hash) || value.is_a?(Array)
+ YAML.dump(value)
+ else
+ value
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index e53ba4e666..c352ddfc11 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -108,6 +108,7 @@ module ActiveRecord
"sql.active_record",
sql: sql,
binds: binds,
+ type_casted_binds: -> { type_casted_binds(binds) },
name: name,
connection_id: object_id,
cached: true,
@@ -123,6 +124,7 @@ module ActiveRecord
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such
# queries should not be cached.
def locked?(arel)
+ arel = arel.arel if arel.is_a?(Relation)
arel.respond_to?(:locked) && arel.locked
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index a4fecc4a8e..8865e7c703 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -22,7 +22,7 @@ module ActiveRecord
private
def visit_AlterTable(o)
- sql = "ALTER TABLE #{quote_table_name(o.name)} "
+ sql = "ALTER TABLE #{quote_table_name(o.name)} ".dup
sql << o.adds.map { |col| accept col }.join(" ")
sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(" ")
sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ")
@@ -30,17 +30,17 @@ module ActiveRecord
def visit_ColumnDefinition(o)
o.sql_type = type_to_sql(o.type, o.options)
- column_sql = "#{quote_column_name(o.name)} #{o.sql_type}"
+ column_sql = "#{quote_column_name(o.name)} #{o.sql_type}".dup
add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key
column_sql
end
def visit_AddColumnDefinition(o)
- "ADD #{accept(o.column)}"
+ "ADD #{accept(o.column)}".dup
end
def visit_TableDefinition(o)
- create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} "
+ create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} ".dup
statements = o.columns.map { |c| accept c }
statements << accept(o.primary_keys) if o.primary_keys
@@ -55,7 +55,7 @@ module ActiveRecord
create_sql << "(#{statements.join(', ')})" if statements.present?
add_table_options!(create_sql, table_options(o))
- create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
+ create_sql << " AS #{to_sql(o.as)}" if o.as
create_sql
end
@@ -114,6 +114,11 @@ module ActiveRecord
sql
end
+ def to_sql(sql)
+ sql = sql.to_sql if sql.respond_to?(:to_sql)
+ sql
+ end
+
def foreign_key_in_create(from_table, to_table, options)
options = foreign_key_options(from_table, to_table, options)
accept ForeignKeyDefinition.new(from_table, to_table, options)
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 16a398f631..475463c4fd 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1,4 +1,4 @@
-require "active_record/migration/join_table"
+require_relative "../../migration/join_table"
require "active_support/core_ext/string/access"
require "digest"
@@ -31,7 +31,7 @@ module ActiveRecord
# Returns the relation names useable to back Active Record models.
# For most adapters this means all #tables and #views.
def data_sources
- select_values(data_source_sql, "SCHEMA")
+ query_values(data_source_sql, "SCHEMA")
rescue NotImplementedError
tables | views
end
@@ -41,14 +41,14 @@ module ActiveRecord
# data_source_exists?(:ebooks)
#
def data_source_exists?(name)
- select_values(data_source_sql(name), "SCHEMA").any? if name.present?
+ query_values(data_source_sql(name), "SCHEMA").any? if name.present?
rescue NotImplementedError
data_sources.include?(name.to_s)
end
# Returns an array of table names defined in the database.
def tables
- select_values(data_source_sql(type: "BASE TABLE"), "SCHEMA")
+ query_values(data_source_sql(type: "BASE TABLE"), "SCHEMA")
end
# Checks to see if the table +table_name+ exists on the database.
@@ -56,14 +56,14 @@ module ActiveRecord
# table_exists?(:developers)
#
def table_exists?(table_name)
- select_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present?
+ query_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present?
rescue NotImplementedError
tables.include?(table_name.to_s)
end
# Returns an array of view names defined in the database.
def views
- select_values(data_source_sql(type: "VIEW"), "SCHEMA")
+ query_values(data_source_sql(type: "VIEW"), "SCHEMA")
end
# Checks to see if the view +view_name+ exists on the database.
@@ -71,7 +71,7 @@ module ActiveRecord
# view_exists?(:ebooks)
#
def view_exists?(view_name)
- select_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present?
+ query_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present?
rescue NotImplementedError
views.include?(view_name.to_s)
end
@@ -188,6 +188,8 @@ module ActiveRecord
# The name of the primary key, if one is to be added automatically.
# Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
#
+ # If an array is passed, a composite primary key will be created.
+ #
# Note that Active Record models will automatically detect their
# primary key. This can be avoided by using
# {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model
@@ -241,6 +243,23 @@ module ActiveRecord
# label varchar
# )
#
+ # ====== Create a composite primary key
+ #
+ # create_table(:orders, primary_key: [:product_id, :client_id]) do |t|
+ # t.belongs_to :product
+ # t.belongs_to :client
+ # end
+ #
+ # generates:
+ #
+ # CREATE TABLE order (
+ # product_id integer NOT NULL,
+ # client_id integer NOT NULL
+ # );
+ #
+ # ALTER TABLE ONLY "orders"
+ # ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id);
+ #
# ====== Do not add a primary key column
#
# create_table(:categories_suppliers, id: false) do |t|
@@ -493,8 +512,7 @@ module ActiveRecord
# * <tt>:default</tt> -
# The column's default value. Use +nil+ for +NULL+.
# * <tt>:null</tt> -
- # Allows or disallows +NULL+ values in the column. This option could
- # have been named <tt>:null_allowed</tt>.
+ # Allows or disallows +NULL+ values in the column.
# * <tt>:precision</tt> -
# Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
# * <tt>:scale</tt> -
@@ -992,7 +1010,7 @@ module ActiveRecord
def dump_schema_information #:nodoc:
versions = ActiveRecord::SchemaMigration.all_versions
- insert_versions_sql(versions)
+ insert_versions_sql(versions) if versions.any?
end
def initialize_schema_migrations_table # :nodoc:
@@ -1330,7 +1348,7 @@ module ActiveRecord
sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
if versions.is_a?(Array)
- sql = "INSERT INTO #{sm_table} (version) VALUES\n"
+ sql = "INSERT INTO #{sm_table} (version) VALUES\n".dup
sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
sql << ";\n\n"
sql
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 19b7821494..f63d09039f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -1,10 +1,13 @@
module ActiveRecord
module ConnectionAdapters
class TransactionState
- VALID_STATES = Set.new([:committed, :rolledback, nil])
-
def initialize(state = nil)
@state = state
+ @children = []
+ end
+
+ def add_child(state)
+ @children << state
end
def finalized?
@@ -19,15 +22,43 @@ module ActiveRecord
@state == :rolledback
end
+ def fully_completed?
+ completed?
+ end
+
def completed?
committed? || rolledback?
end
def set_state(state)
- unless VALID_STATES.include?(state)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ The set_state method is deprecated and will be removed in
+ Rails 6.0. Please use rollback! or commit! to set transaction
+ state directly.
+ MSG
+ case state
+ when :rolledback
+ rollback!
+ when :committed
+ commit!
+ when nil
+ nullify!
+ else
raise ArgumentError, "Invalid transaction state: #{state}"
end
- @state = state
+ end
+
+ def rollback!
+ @children.each { |c| c.rollback! }
+ @state = :rolledback
+ end
+
+ def commit!
+ @state = :committed
+ end
+
+ def nullify!
+ @state = nil
end
end
@@ -57,7 +88,7 @@ module ActiveRecord
end
def rollback
- @state.set_state(:rolledback)
+ @state.rollback!
end
def rollback_records
@@ -72,7 +103,7 @@ module ActiveRecord
end
def commit
- @state.set_state(:committed)
+ @state.commit!
end
def before_commit_records
@@ -100,8 +131,11 @@ module ActiveRecord
end
class SavepointTransaction < Transaction
- def initialize(connection, savepoint_name, options, *args)
+ def initialize(connection, savepoint_name, parent_transaction, options, *args)
super(connection, options, *args)
+
+ parent_transaction.state.add_child(@state)
+
if options[:isolation]
raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
end
@@ -155,7 +189,7 @@ module ActiveRecord
if @stack.empty?
RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
else
- SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options,
+ SavepointTransaction.new(@connection, "active_record_#{@stack.size}", @stack.last, options,
run_commit_callbacks: run_commit_callbacks)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 85d6fbe8b3..30b29e7007 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,9 +1,9 @@
-require "active_record/type"
-require "active_record/connection_adapters/determine_if_preparable_visitor"
-require "active_record/connection_adapters/schema_cache"
-require "active_record/connection_adapters/sql_type_metadata"
-require "active_record/connection_adapters/abstract/schema_dumper"
-require "active_record/connection_adapters/abstract/schema_creation"
+require_relative "../type"
+require_relative "determine_if_preparable_visitor"
+require_relative "schema_cache"
+require_relative "sql_type_metadata"
+require_relative "abstract/schema_dumper"
+require_relative "abstract/schema_creation"
require "arel/collectors/bind"
require "arel/collectors/sql_string"
@@ -147,7 +147,7 @@ module ActiveRecord
# this method must only be called while holding connection pool's mutex
def lease
if in_use?
- msg = "Cannot lease connection, "
+ msg = "Cannot lease connection, ".dup
if @owner == Thread.current
msg << "it is already leased by the current thread."
else
@@ -576,12 +576,14 @@ module ActiveRecord
type_casted_binds: type_casted_binds,
statement_name: statement_name,
connection_id: object_id) do
+ begin
@lock.synchronize do
yield
end
+ rescue => e
+ raise translate_exception_class(e, sql)
end
- rescue => e
- raise translate_exception_class(e, sql)
+ end
end
def translate_exception(exception, message)
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 648c869915..06976aa769 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,13 +1,13 @@
-require "active_record/connection_adapters/abstract_adapter"
-require "active_record/connection_adapters/statement_pool"
-require "active_record/connection_adapters/mysql/column"
-require "active_record/connection_adapters/mysql/explain_pretty_printer"
-require "active_record/connection_adapters/mysql/quoting"
-require "active_record/connection_adapters/mysql/schema_creation"
-require "active_record/connection_adapters/mysql/schema_definitions"
-require "active_record/connection_adapters/mysql/schema_dumper"
-require "active_record/connection_adapters/mysql/schema_statements"
-require "active_record/connection_adapters/mysql/type_metadata"
+require_relative "abstract_adapter"
+require_relative "statement_pool"
+require_relative "mysql/column"
+require_relative "mysql/explain_pretty_printer"
+require_relative "mysql/quoting"
+require_relative "mysql/schema_creation"
+require_relative "mysql/schema_definitions"
+require_relative "mysql/schema_dumper"
+require_relative "mysql/schema_statements"
+require_relative "mysql/type_metadata"
require "active_support/core_ext/string/strip"
@@ -59,12 +59,12 @@ module ActiveRecord
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
if version < "5.1.10"
- raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.1.10."
+ raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.1.10."
end
end
def version #:nodoc:
- @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
+ @version ||= Version.new(version_string)
end
def mariadb? # :nodoc:
@@ -120,11 +120,11 @@ module ActiveRecord
end
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
- select_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1
+ query_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1
end
def release_advisory_lock(lock_name) # :nodoc:
- select_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1
end
def native_database_types
@@ -132,7 +132,7 @@ module ActiveRecord
end
def index_algorithms
- { default: "ALGORITHM = DEFAULT", copy: "ALGORITHM = COPY", inplace: "ALGORITHM = INPLACE" }
+ { default: "ALGORITHM = DEFAULT".dup, copy: "ALGORITHM = COPY".dup, inplace: "ALGORITHM = INPLACE".dup }
end
# HELPER METHODS ===========================================
@@ -152,7 +152,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity #:nodoc:
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
begin
update("SET FOREIGN_KEY_CHECKS = 0")
@@ -267,7 +267,7 @@ module ActiveRecord
end
def current_database
- select_value "SELECT DATABASE() as db"
+ query_value("SELECT database()", "SCHEMA")
end
# Returns the database character set.
@@ -287,7 +287,7 @@ module ActiveRecord
def table_comment(table_name) # :nodoc:
scope = quoted_scope(table_name)
- select_value(<<-SQL.strip_heredoc, "SCHEMA")
+ query_value(<<-SQL.strip_heredoc, "SCHEMA")
SELECT table_comment
FROM information_schema.tables
WHERE table_schema = #{scope[:schema]}
@@ -379,7 +379,7 @@ module ActiveRecord
def add_index(table_name, column_name, options = {}) #:nodoc:
index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
- sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}".dup
execute add_sql_comment!(sql, comment)
end
@@ -393,7 +393,7 @@ module ActiveRecord
scope = quoted_scope(table_name)
- fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA")
+ fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
SELECT fk.referenced_table_name AS 'to_table',
fk.referenced_column_name AS 'primary_key',
fk.column_name AS 'column',
@@ -464,13 +464,13 @@ module ActiveRecord
super
end
- sql << " unsigned" if unsigned && type != :primary_key
+ sql = "#{sql} unsigned" if unsigned && type != :primary_key
sql
end
# SHOW VARIABLES LIKE 'name'
def show_variable(name)
- select_value("SELECT @@#{name}", "SCHEMA")
+ query_value("SELECT @@#{name}", "SCHEMA")
rescue ActiveRecord::StatementInvalid
nil
end
@@ -480,7 +480,7 @@ module ActiveRecord
scope = quoted_scope(table_name)
- select_values(<<-SQL.strip_heredoc, "SCHEMA")
+ query_values(<<-SQL.strip_heredoc, "SCHEMA")
SELECT column_name
FROM information_schema.key_column_usage
WHERE constraint_name = 'PRIMARY'
@@ -526,8 +526,25 @@ module ActiveRecord
index.using == :btree || super
end
+ def insert_fixtures(*)
+ without_sql_mode("NO_AUTO_VALUE_ON_ZERO") { super }
+ end
+
private
+ def without_sql_mode(mode)
+ result = execute("SELECT @@SESSION.sql_mode")
+ current_mode = result.first[0]
+ return yield unless current_mode.include?(mode)
+
+ sql_mode = "REPLACE(@@sql_mode, '#{mode}', '')"
+ execute("SET @@SESSION.sql_mode = #{sql_mode}")
+ yield
+ ensure
+ sql_mode = "CONCAT(@@sql_mode, ',#{mode}')"
+ execute("SET @@SESSION.sql_mode = #{sql_mode}")
+ end
+
def initialize_type_map(m)
super
@@ -677,7 +694,7 @@ module ActiveRecord
auto_increment: column.auto_increment?
}
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", "SCHEMA")["Type"]
+ current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
td = create_table_definition(table_name)
cd = td.new_column_definition(new_column_name, current_type, options)
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
@@ -761,7 +778,7 @@ module ActiveRecord
# http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
# (trailing comma because variable_assignments will always have content)
if @config[:encoding]
- encoding = "NAMES #{@config[:encoding]}"
+ encoding = "NAMES #{@config[:encoding]}".dup
encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
encoding << ", "
end
@@ -787,7 +804,7 @@ module ActiveRecord
end
def create_table_info(table_name) # :nodoc:
- select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
+ exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
end
def arel_visitor
@@ -837,7 +854,8 @@ module ActiveRecord
end
end
- class MysqlJson < Type::Json # :nodoc:
+ def version_string
+ full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
end
class MysqlString < Type::String # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
index 8c67a7a80b..bda482a00f 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -13,13 +13,8 @@ module ActiveRecord
result
end
- # Returns an array of arrays containing the field values.
- # Order is the same as that returned by +columns+.
- def select_rows(arel, name = nil, binds = []) # :nodoc:
- select_result(arel, name, binds) do |result|
- @connection.next_result while @connection.more_results?
- result.to_a
- end
+ def query(sql, name = nil) # :nodoc:
+ execute(sql, name).to_a
end
# Executes the SQL statement in the context of this connection.
@@ -58,16 +53,6 @@ module ActiveRecord
@connection.last_id
end
- def select_result(arel, name, binds)
- arel, binds = binds_from_relation(arel, binds)
- sql = to_sql(arel, binds)
- if without_prepared_statement?(binds)
- execute_and_free(sql, name) { |result| yield result }
- else
- exec_stmt_and_free(sql, name, binds, cache_stmt: true) { |_, result| yield result }
- end
- end
-
def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
# made since we established the connection
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
index 083cd6340f..eea4984680 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
@@ -16,7 +16,7 @@ module ActiveRecord
end
def visit_ChangeColumnDefinition(o)
- change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
+ change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}".dup
add_column_position!(change_column_sql, column_options(o.column))
end
@@ -63,7 +63,7 @@ module ActiveRecord
def index_in_create(table_name, column_name, options)
index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options)
- add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})", comment)
+ add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})".dup, comment)
end
end
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 e2ba0ba1a0..a46d9f8cbb 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -47,13 +47,13 @@ module ActiveRecord
def schema_collation(column)
if column.collation && table_name = column.table_name
@table_collation_cache ||= {}
- @table_collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"]
+ @table_collation_cache[table_name] ||= exec_query("SHOW TABLE STATUS LIKE #{quote(table_name)}", "SCHEMA").first["Collation"]
column.collation.inspect if column.collation != @table_collation_cache[table_name]
end
end
def extract_expression_for_virtual_column(column)
- if mariadb?
+ if mariadb? && version < "10.2.5"
create_table_info = create_table_info(column.table_name)
if %r/#{quote_column_name(column.name)} #{Regexp.quote(column.sql_type)}(?: COLLATE \w+)? AS \((?<expression>.+?)\) #{column.extra}/ =~ create_table_info
$~[:expression].inspect
@@ -64,7 +64,7 @@ module ActiveRecord
" WHERE table_schema = #{scope[:schema]}" \
" AND table_name = #{scope[:name]}" \
" AND column_name = #{quote(column.name)}"
- select_value(sql, "SCHEMA").inspect
+ query_value(sql, "SCHEMA").inspect
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
index f9e1e046ea..24f8ff6367 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -45,6 +45,13 @@ module ActiveRecord
indexes
end
+ def remove_column(table_name, column_name, type = nil, options = {})
+ if foreign_key_exists?(table_name, column: column_name)
+ remove_foreign_key(table_name, column: column_name)
+ end
+ super
+ end
+
def internal_string_options_for_primary_key
super.tap do |options|
if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) && (mariadb? || version < "8.0.0")
@@ -66,8 +73,8 @@ module ActiveRecord
def new_column_from_field(table_name, field)
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
- default, default_function = nil, field[:Default]
+ if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\(\))?\z/i.match?(field[:Default])
+ default, default_function = nil, "CURRENT_TIMESTAMP"
else
default, default_function = field[:Default], nil
end
@@ -95,7 +102,7 @@ module ActiveRecord
def data_source_sql(name = nil, type: nil)
scope = quoted_scope(name, type: type)
- sql = "SELECT table_name FROM information_schema.tables"
+ sql = "SELECT table_name FROM information_schema.tables".dup
sql << " WHERE table_schema = #{scope[:schema]}"
sql << " AND table_name = #{scope[:name]}" if scope[:name]
sql << " AND table_type = #{scope[:type]}" if scope[:type]
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index af55cfe2f6..c5c0a071e7 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,5 +1,5 @@
-require "active_record/connection_adapters/abstract_mysql_adapter"
-require "active_record/connection_adapters/mysql/database_statements"
+require_relative "abstract_mysql_adapter"
+require_relative "mysql/database_statements"
gem "mysql2", ">= 0.3.18", "< 0.5"
require "mysql2"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index 705e6063dc..ebf1715ed0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -7,30 +7,6 @@ module ActiveRecord
PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds))
end
- def select_value(arel, name = nil, binds = []) # :nodoc:
- select_result(arel, name, binds) do |result|
- result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0
- end
- end
-
- def select_values(arel, name = nil, binds = []) # :nodoc:
- select_result(arel, name, binds) do |result|
- if result.nfields > 0
- result.column_values(0)
- else
- []
- end
- end
- end
-
- # Executes a SELECT query and returns an array of rows. Each row is an
- # array of field values.
- def select_rows(arel, name = nil, binds = []) # :nodoc:
- select_result(arel, name, binds) do |result|
- result.values
- end
- end
-
# The internal PostgreSQL identifier of the money data type.
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
# The internal PostgreSQL identifier of the BYTEA data type.
@@ -171,18 +147,14 @@ module ActiveRecord
end
private
+ # Returns the current ID of a table's sequence.
+ def last_insert_id_result(sequence_name)
+ exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
+ end
def suppress_composite_primary_key(pk)
pk unless pk.is_a?(Array)
end
-
- def select_result(arel, name, binds)
- arel, binds = binds_from_relation(arel, binds)
- sql = to_sql(arel, binds)
- execute_and_clear(sql, name, binds) do |result|
- yield result
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 4098250f3e..6666622c08 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -1,26 +1,25 @@
-require "active_record/connection_adapters/postgresql/oid/array"
-require "active_record/connection_adapters/postgresql/oid/bit"
-require "active_record/connection_adapters/postgresql/oid/bit_varying"
-require "active_record/connection_adapters/postgresql/oid/bytea"
-require "active_record/connection_adapters/postgresql/oid/cidr"
-require "active_record/connection_adapters/postgresql/oid/date_time"
-require "active_record/connection_adapters/postgresql/oid/decimal"
-require "active_record/connection_adapters/postgresql/oid/enum"
-require "active_record/connection_adapters/postgresql/oid/hstore"
-require "active_record/connection_adapters/postgresql/oid/inet"
-require "active_record/connection_adapters/postgresql/oid/json"
-require "active_record/connection_adapters/postgresql/oid/jsonb"
-require "active_record/connection_adapters/postgresql/oid/money"
-require "active_record/connection_adapters/postgresql/oid/oid"
-require "active_record/connection_adapters/postgresql/oid/point"
-require "active_record/connection_adapters/postgresql/oid/legacy_point"
-require "active_record/connection_adapters/postgresql/oid/range"
-require "active_record/connection_adapters/postgresql/oid/specialized_string"
-require "active_record/connection_adapters/postgresql/oid/uuid"
-require "active_record/connection_adapters/postgresql/oid/vector"
-require "active_record/connection_adapters/postgresql/oid/xml"
+require_relative "oid/array"
+require_relative "oid/bit"
+require_relative "oid/bit_varying"
+require_relative "oid/bytea"
+require_relative "oid/cidr"
+require_relative "oid/date_time"
+require_relative "oid/decimal"
+require_relative "oid/enum"
+require_relative "oid/hstore"
+require_relative "oid/inet"
+require_relative "oid/jsonb"
+require_relative "oid/money"
+require_relative "oid/oid"
+require_relative "oid/point"
+require_relative "oid/legacy_point"
+require_relative "oid/range"
+require_relative "oid/specialized_string"
+require_relative "oid/uuid"
+require_relative "oid/vector"
+require_relative "oid/xml"
-require "active_record/connection_adapters/postgresql/oid/type_map_initializer"
+require_relative "oid/type_map_initializer"
module ActiveRecord
module ConnectionAdapters
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
deleted file mode 100644
index 3c706c27c4..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module OID # :nodoc:
- class Json < Type::Json # :nodoc:
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
index 7a91272d1c..cd7442e751 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
@@ -22,7 +23,7 @@ module ActiveRecord
# (3) -$2.55
# (4) ($2.55)
- value.sub!(/^\((.+)\)$/, '-\1') # (4)
+ value = value.sub(/^\((.+)\)$/, '-\1') # (4)
case value
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
value.gsub!(/[^-\d.]/, "")
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
index 5e839228e9..db92333ef7 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Uuid < Type::Value # :nodoc:
- ACCEPTABLE_UUID = %r{\A\{?([a-fA-F0-9]{4}-?){8}\}?\z}x
+ ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z}
alias_method :serialize, :deserialize
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 44eb666965..ee4230c6f2 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -78,7 +78,7 @@ module ActiveRecord
private
def lookup_cast_type(sql_type)
- super(select_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
+ super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
end
def _quote(value)
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 cb45d7ba6b..a710ea6cc9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -60,7 +60,7 @@ module ActiveRecord
# Returns true if schema exists.
def schema_exists?(name)
- select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
+ query_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
end
# Verifies existence of an index with a given name.
@@ -73,7 +73,7 @@ module ActiveRecord
table = quoted_scope(table_name)
index = quoted_scope(index_name)
- select_value(<<-SQL, "SCHEMA").to_i > 0
+ query_value(<<-SQL, "SCHEMA").to_i > 0
SELECT COUNT(*)
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
@@ -163,7 +163,7 @@ module ActiveRecord
def table_comment(table_name) # :nodoc:
scope = quoted_scope(table_name, type: "BASE TABLE")
if scope[:name]
- select_value(<<-SQL.strip_heredoc, "SCHEMA")
+ query_value(<<-SQL.strip_heredoc, "SCHEMA")
SELECT pg_catalog.obj_description(c.oid, 'pg_class')
FROM pg_catalog.pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
@@ -176,32 +176,32 @@ module ActiveRecord
# Returns the current database name.
def current_database
- select_value("SELECT current_database()", "SCHEMA")
+ query_value("SELECT current_database()", "SCHEMA")
end
# Returns the current schema name.
def current_schema
- select_value("SELECT current_schema", "SCHEMA")
+ query_value("SELECT current_schema", "SCHEMA")
end
# Returns the current database encoding format.
def encoding
- select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
+ query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
end
# Returns the current database collation.
def collation
- select_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA")
+ query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA")
end
# Returns the current database ctype.
def ctype
- select_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA")
+ query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA")
end
# Returns an array of schema names.
def schema_names
- select_values(<<-SQL, "SCHEMA")
+ query_values(<<-SQL, "SCHEMA")
SELECT nspname
FROM pg_namespace
WHERE nspname !~ '^pg_.*'
@@ -234,12 +234,12 @@ module ActiveRecord
# Returns the active schema search path.
def schema_search_path
- @schema_search_path ||= select_value("SHOW search_path", "SCHEMA")
+ @schema_search_path ||= query_value("SHOW search_path", "SCHEMA")
end
# Returns the current client message level.
def client_min_messages
- select_value("SHOW client_min_messages", "SCHEMA")
+ query_value("SHOW client_min_messages", "SCHEMA")
end
# Set the client message level.
@@ -257,7 +257,7 @@ module ActiveRecord
end
def serial_sequence(table, column)
- select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", "SCHEMA")
+ query_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", "SCHEMA")
end
# Sets the sequence of a table's primary key to the specified value.
@@ -268,7 +268,7 @@ module ActiveRecord
if sequence
quoted_sequence = quote_table_name(sequence)
- select_value("SELECT setval('#{quoted_sequence}', #{value})", "SCHEMA")
+ query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
else
@logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
end
@@ -290,18 +290,16 @@ module ActiveRecord
if pk && sequence
quoted_sequence = quote_table_name(sequence)
- max_pk = select_value("select MAX(#{quote_column_name pk}) from #{quote_table_name(table)}")
+ max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
if max_pk.nil?
if postgresql_version >= 100000
- minvalue = select_value("SELECT seqmin from pg_sequence where seqrelid = '#{quoted_sequence}'::regclass")
+ minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
else
- minvalue = select_value("SELECT min_value FROM #{quoted_sequence}")
+ minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
end
end
- select_value(<<-end_sql, "SCHEMA")
- SELECT setval('#{quoted_sequence}', #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})
- end_sql
+ query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
end
end
@@ -325,7 +323,7 @@ module ActiveRecord
AND seq.relnamespace = nsp.oid
AND cons.contype = 'p'
AND dep.classid = 'pg_class'::regclass
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
+ AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
end_sql
if result.nil? || result.empty?
@@ -343,7 +341,7 @@ module ActiveRecord
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
- WHERE t.oid = '#{quote_table_name(table)}'::regclass
+ WHERE t.oid = #{quote(quote_table_name(table))}::regclass
AND cons.contype = 'p'
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
end_sql
@@ -360,7 +358,7 @@ module ActiveRecord
end
def primary_keys(table_name) # :nodoc:
- select_values(<<-SQL.strip_heredoc, "SCHEMA")
+ query_values(<<-SQL.strip_heredoc, "SCHEMA")
SELECT a.attname
FROM (
SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
@@ -408,7 +406,7 @@ module ActiveRecord
quoted_table_name = quote_table_name(table_name)
quoted_column_name = quote_column_name(column_name)
sql_type = type_to_sql(type, options)
- sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
+ sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
end
@@ -511,7 +509,7 @@ module ActiveRecord
def foreign_keys(table_name)
scope = quoted_scope(table_name)
- fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA")
+ fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
FROM pg_constraint c
JOIN pg_class t1 ON c.conrelid = t1.oid
@@ -568,7 +566,7 @@ module ActiveRecord
super
end
- sql << "[]" if array && type != :primary_key
+ sql = "#{sql}[]" if array && type != :primary_key
sql
end
@@ -637,7 +635,7 @@ module ActiveRecord
scope = quoted_scope(name, type: type)
scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
- sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
+ sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup
sql << " WHERE n.nspname = #{scope[:schema]}"
sql << " AND c.relname = #{scope[:name]}" if scope[:name]
sql << " AND c.relkind IN (#{scope[:type]})"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 5287dd6a51..8baef19030 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -2,20 +2,20 @@
gem "pg", "~> 0.18"
require "pg"
-require "active_record/connection_adapters/abstract_adapter"
-require "active_record/connection_adapters/statement_pool"
-require "active_record/connection_adapters/postgresql/column"
-require "active_record/connection_adapters/postgresql/database_statements"
-require "active_record/connection_adapters/postgresql/explain_pretty_printer"
-require "active_record/connection_adapters/postgresql/oid"
-require "active_record/connection_adapters/postgresql/quoting"
-require "active_record/connection_adapters/postgresql/referential_integrity"
-require "active_record/connection_adapters/postgresql/schema_creation"
-require "active_record/connection_adapters/postgresql/schema_definitions"
-require "active_record/connection_adapters/postgresql/schema_dumper"
-require "active_record/connection_adapters/postgresql/schema_statements"
-require "active_record/connection_adapters/postgresql/type_metadata"
-require "active_record/connection_adapters/postgresql/utils"
+require_relative "abstract_adapter"
+require_relative "statement_pool"
+require_relative "postgresql/column"
+require_relative "postgresql/database_statements"
+require_relative "postgresql/explain_pretty_printer"
+require_relative "postgresql/oid"
+require_relative "postgresql/quoting"
+require_relative "postgresql/referential_integrity"
+require_relative "postgresql/schema_creation"
+require_relative "postgresql/schema_definitions"
+require_relative "postgresql/schema_dumper"
+require_relative "postgresql/schema_statements"
+require_relative "postgresql/type_metadata"
+require_relative "postgresql/utils"
module ActiveRecord
module ConnectionHandling # :nodoc:
@@ -312,14 +312,14 @@ module ActiveRecord
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});")
+ query_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})")
+ query_value("SELECT pg_advisory_unlock(#{lock_id})")
end
def enable_extension(name)
@@ -336,15 +336,14 @@ module ActiveRecord
def extension_enabled?(name)
if supports_extensions?
- res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled",
- "SCHEMA"
+ res = exec_query("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", "SCHEMA")
res.cast_values.first
end
end
def extensions
if supports_extensions?
- exec_query("SELECT extname from pg_extension", "SCHEMA").cast_values
+ exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
else
super
end
@@ -352,14 +351,14 @@ module ActiveRecord
# Returns the configured supported identifier length supported by PostgreSQL
def table_alias_length
- @max_identifier_length ||= select_value("SHOW max_identifier_length", "SCHEMA").to_i
+ @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
end
alias index_name_length table_alias_length
# Set the authorized user for this session
def session_auth=(user)
clear_cache!
- exec_query "SET SESSION AUTHORIZATION #{user}"
+ execute("SET SESSION AUTHORIZATION #{user}")
end
def use_insert_returning?
@@ -723,11 +722,6 @@ module ActiveRecord
end
end
- # Returns the current ID of a table's sequence.
- def last_insert_id_result(sequence_name)
- exec_query("SELECT currval('#{sequence_name}')", "SQL")
- end
-
# Returns the list of a table's column names, data types, and default values.
#
# The underlying query is roughly:
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
index e02491edb6..31e83f9260 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
@@ -11,7 +11,7 @@ module ActiveRecord
end
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
- index_sql = select_value(<<-SQL, "SCHEMA")
+ index_sql = query_value(<<-SQL, "SCHEMA")
SELECT sql
FROM sqlite_master
WHERE name = #{quote(row['name'])} AND type = 'index'
@@ -67,7 +67,7 @@ module ActiveRecord
scope = quoted_scope(name, type: type)
scope[:type] ||= "'table','view'"
- sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
+ sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'".dup
sql << " AND name = #{scope[:name]}" if scope[:name]
sql << " AND type IN (#{scope[:type]})"
sql
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 7233325d5a..04129841e4 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,11 +1,11 @@
-require "active_record/connection_adapters/abstract_adapter"
-require "active_record/connection_adapters/statement_pool"
-require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
-require "active_record/connection_adapters/sqlite3/quoting"
-require "active_record/connection_adapters/sqlite3/schema_creation"
-require "active_record/connection_adapters/sqlite3/schema_definitions"
-require "active_record/connection_adapters/sqlite3/schema_dumper"
-require "active_record/connection_adapters/sqlite3/schema_statements"
+require_relative "abstract_adapter"
+require_relative "statement_pool"
+require_relative "sqlite3/explain_pretty_printer"
+require_relative "sqlite3/quoting"
+require_relative "sqlite3/schema_creation"
+require_relative "sqlite3/schema_definitions"
+require_relative "sqlite3/schema_dumper"
+require_relative "sqlite3/schema_statements"
gem "sqlite3", "~> 1.3.6"
require "sqlite3"
@@ -169,7 +169,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity # :nodoc:
- old = select_value("PRAGMA foreign_keys")
+ old = query_value("PRAGMA foreign_keys")
begin
execute("PRAGMA foreign_keys = OFF")
@@ -337,7 +337,7 @@ module ActiveRecord
alias :add_belongs_to :add_reference
def foreign_keys(table_name)
- fk_info = select_all("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
+ fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
fk_info.map do |row|
options = {
column: row["from"],
@@ -349,6 +349,12 @@ module ActiveRecord
end
end
+ def insert_fixtures(rows, table_name)
+ rows.each do |row|
+ insert_fixture(row, table_name)
+ end
+ end
+
private
def table_structure(table_name)
@@ -437,7 +443,7 @@ module ActiveRecord
end
def sqlite_version
- @sqlite_version ||= SQLite3Adapter::Version.new(select_value("SELECT sqlite_version(*)"))
+ @sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
end
def translate_exception(exception, message)
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 8f78330d4a..198c712abc 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -56,8 +56,7 @@ module ActiveRecord
# :singleton-method:
# Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
# dates and times from the database. This is set to :utc by default.
- mattr_accessor :default_timezone, instance_writer: false
- self.default_timezone = :utc
+ mattr_accessor :default_timezone, instance_writer: false, default: :utc
##
# :singleton-method:
@@ -67,16 +66,14 @@ module ActiveRecord
# ActiveRecord::Schema file which can be loaded into any database that
# supports migrations. Use :ruby if you want to have different database
# adapters for, e.g., your development and test environments.
- mattr_accessor :schema_format, instance_writer: false
- self.schema_format = :ruby
+ mattr_accessor :schema_format, instance_writer: false, default: :ruby
##
# :singleton-method:
# Specifies if an error should be raised if the query has an order being
# ignored when doing batch queries. Useful in applications where the
# scope being ignored is error-worthy, rather than a warning.
- mattr_accessor :error_on_ignored_order, instance_writer: false
- self.error_on_ignored_order = false
+ mattr_accessor :error_on_ignored_order, instance_writer: false, default: false
def self.error_on_ignored_order_or_limit
ActiveSupport::Deprecation.warn(<<-MSG.squish)
@@ -101,8 +98,7 @@ module ActiveRecord
##
# :singleton-method:
# Specify whether or not to use timestamps for migration versions
- mattr_accessor :timestamped_migrations, instance_writer: false
- self.timestamped_migrations = true
+ mattr_accessor :timestamped_migrations, instance_writer: false, default: true
##
# :singleton-method:
@@ -110,8 +106,7 @@ module ActiveRecord
# db:migrate rake task. This is true by default, which is useful for the
# development environment. This should ideally be false in the production
# environment where dumping schema is rarely needed.
- mattr_accessor :dump_schema_after_migration, instance_writer: false
- self.dump_schema_after_migration = true
+ mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
##
# :singleton-method:
@@ -120,8 +115,7 @@ module ActiveRecord
# schema_search_path are dumped. Use :all to dump all schemas regardless
# of schema_search_path, or a string of comma separated schemas for a
# custom list.
- mattr_accessor :dump_schemas, instance_writer: false
- self.dump_schemas = :schema_search_path
+ mattr_accessor :dump_schemas, instance_writer: false, default: :schema_search_path
##
# :singleton-method:
@@ -130,7 +124,6 @@ module ActiveRecord
# be used to identify queries which load thousands of records and
# potentially cause memory bloat.
mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false
- self.warn_on_records_fetched_greater_than = nil
mattr_accessor :maintain_test_schema, instance_accessor: false
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 18fac5af1b..60d4fb70e0 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -105,7 +105,7 @@ module ActiveRecord
class WrappedDatabaseException < StatementInvalid
end
- # Raised when a record cannot be inserted because it would violate a uniqueness constraint.
+ # Raised when a record cannot be inserted or updated because it would violate a uniqueness constraint.
class RecordNotUnique < WrappedDatabaseException
end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 8f7ae2c33c..eff5990f3a 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -1,4 +1,4 @@
-require "active_record/explain_registry"
+require_relative "explain_registry"
module ActiveRecord
module Explain
@@ -16,7 +16,7 @@ module ActiveRecord
# Returns a formatted string ready to be logged.
def exec_explain(queries) # :nodoc:
str = queries.map do |sql, binds|
- msg = "EXPLAIN for: #{sql}"
+ msg = "EXPLAIN for: #{sql}".dup
unless binds.empty?
msg << " "
msg << binds.map { |attr| render_bind(attr) }.inspect
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index abd8cfc8f2..928720d011 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -1,5 +1,5 @@
require "active_support/notifications"
-require "active_record/explain_registry"
+require_relative "explain_registry"
module ActiveRecord
class ExplainSubscriber # :nodoc:
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index bad6542be2..c9e97d9d2b 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -4,8 +4,8 @@ require "zlib"
require "set"
require "active_support/dependencies"
require "active_support/core_ext/digest/uuid"
-require "active_record/fixture_set/file"
-require "active_record/errors"
+require_relative "fixture_set/file"
+require_relative "errors"
module ActiveRecord
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
@@ -492,8 +492,7 @@ module ActiveRecord
end
end
- cattr_accessor :all_loaded_fixtures
- self.all_loaded_fixtures = {}
+ cattr_accessor :all_loaded_fixtures, default: {}
class ClassCache
def initialize(class_names, config)
@@ -568,9 +567,7 @@ module ActiveRecord
end
table_rows.each do |fixture_set_name, rows|
- rows.each do |row|
- conn.insert_fixture(row, fixture_set_name)
- end
+ conn.insert_fixtures(rows, fixture_set_name)
end
# Cap primary key sequences to max(pk).
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 5776807507..1753322274 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -245,7 +245,7 @@ module ActiveRecord
def ensure_proper_type
klass = self.class
if klass.finder_needs_type_condition?
- write_attribute(klass.inheritance_column, klass.sti_name)
+ _write_attribute(klass.inheritance_column, klass.sti_name)
end
end
end
diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb
index 25ee9d6bfe..89e5d153b8 100644
--- a/activerecord/lib/active_record/internal_metadata.rb
+++ b/activerecord/lib/active_record/internal_metadata.rb
@@ -1,5 +1,5 @@
-require "active_record/scoping/default"
-require "active_record/scoping/named"
+require_relative "scoping/default"
+require_relative "scoping/named"
module ActiveRecord
# This class is used to create a table that keeps track of values and keys such
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 3c7110369b..522da6a571 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -62,8 +62,8 @@ module ActiveRecord
def increment_lock
lock_col = self.class.locking_column
- previous_lock_value = send(lock_col).to_i
- send(lock_col + "=", previous_lock_value + 1)
+ previous_lock_value = send(lock_col)
+ send("#{lock_col}=", previous_lock_value + 1)
end
def _create_record(attribute_names = self.attribute_names, *)
@@ -107,7 +107,8 @@ module ActiveRecord
# If something went wrong, revert the locking_column value.
rescue Exception
- send(lock_col + "=", previous_lock_value.to_i)
+ send("#{lock_col}=", previous_lock_value.to_i)
+
raise
end
end
@@ -127,7 +128,7 @@ module ActiveRecord
if locking_enabled?
locking_column = self.class.locking_column
- relation = relation.where(locking_column => _read_attribute(locking_column))
+ relation = relation.where(locking_column => read_attribute_before_type_cast(locking_column))
end
relation
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 2297c77835..e39ca5f6dc 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -29,7 +29,7 @@ module ActiveRecord
binds = nil
unless (payload[:binds] || []).empty?
- casted_params = type_casted_binds(payload[:binds], payload[:type_casted_binds])
+ casted_params = type_casted_binds(payload[:type_casted_binds])
binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
render_bind(attr, value)
}.inspect
@@ -42,9 +42,8 @@ module ActiveRecord
end
private
-
- def type_casted_binds(binds, casted_binds)
- casted_binds || ActiveRecord::Base.connection.type_casted_binds(binds)
+ def type_casted_binds(casted_binds)
+ casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
end
def render_bind(attr, value)
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 51c82f4ced..42220b9a5e 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -157,7 +157,7 @@ module ActiveRecord
class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
def initialize(env = "production")
- msg = "You are attempting to run a destructive action against your '#{env}' database.\n"
+ msg = "You are attempting to run a destructive action against your '#{env}' database.\n".dup
msg << "If you are sure you want to continue, run the same command with the environment variable:\n"
msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
super(msg)
@@ -166,7 +166,7 @@ module ActiveRecord
class EnvironmentMismatchError < ActiveRecordError
def initialize(current: nil, stored: nil)
- msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
+ msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n".dup
msg << "You are running in `#{ current }` environment. "
msg << "If you are sure you want to continue, first set the environment using:\n\n"
msg << " bin/rails db:environment:set"
@@ -863,15 +863,17 @@ module ActiveRecord
source_migrations.each do |migration|
source = File.binread(migration.filename)
inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n"
- if /\A#.*\b(?:en)?coding:\s*\S+/ =~ source
+ magic_comments = "".dup
+ loop do
# If we have a magic comment in the original migration,
# insert our comment after the first newline(end of the magic comment line)
# so the magic keep working.
# Note that magic comments must be at the first line(except sh-bang).
- source[/\n/] = "\n#{inserted_comment}"
- else
- source = "#{inserted_comment}#{source}"
+ source.sub!(/\A(?:#.*\b(?:en)?coding:\s*\S+|#\s*frozen_string_literal:\s*(?:true|false)).*\n/) do |magic_comment|
+ magic_comments << magic_comment; ""
+ end || break
end
+ source = "#{magic_comments}#{inserted_comment}#{source}"
if duplicate = destination_migrations.detect { |m| m.name == migration.name }
if options[:on_skip] && duplicate.scope != scope.to_s
@@ -1239,7 +1241,7 @@ module ActiveRecord
record_version_state_after_migrating(migration.version)
end
rescue => e
- msg = "An error has occurred, "
+ msg = "An error has occurred, ".dup
msg << "this and " if use_transaction?(migration)
msg << "all later migrations canceled:\n\n#{e}"
raise StandardError, msg, e.backtrace
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 1179a60e9b..14e0f5bff7 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -446,7 +446,11 @@ module ActiveRecord
def load_schema
return if schema_loaded?
@load_schema_monitor.synchronize do
- load_schema! unless defined?(@columns_hash) && @columns_hash
+ return if defined?(@columns_hash) && @columns_hash
+
+ load_schema!
+
+ @schema_loaded = true
end
end
@@ -460,8 +464,6 @@ module ActiveRecord
user_provided_default: false
)
end
-
- @schema_loaded = true
end
def reload_schema_from_cache
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index f652c7c3a1..a9509e562a 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -333,7 +333,7 @@ module ActiveRecord
updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes)
attributes.each do |k, v|
- raw_write_attribute(k, v)
+ write_attribute_without_type_cast(k, v)
end
updated_count == 1
@@ -526,7 +526,7 @@ module ActiveRecord
if locking_enabled?
locking_column = self.class.locking_column
- scope = scope.where(locking_column => _read_attribute(locking_column))
+ scope = scope.where(locking_column => read_attribute_before_type_cast(locking_column))
changes[locking_column] = increment_lock
end
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index ec246e97bc..e4c2e1f86f 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -5,20 +5,20 @@ module ActiveRecord
# Enable the query cache within the block if Active Record is configured.
# If it's not, it will execute the given block.
def cache(&block)
- if connected?
- connection.cache(&block)
- else
+ if configurations.empty?
yield
+ else
+ connection.cache(&block)
end
end
# Disable the query cache within the block if Active Record is configured.
# If it's not, it will execute the given block.
def uncached(&block)
- if connected?
- connection.uncached(&block)
- else
+ if configurations.empty?
yield
+ else
+ connection.uncached(&block)
end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 73518ca144..9cca103a18 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -48,8 +48,8 @@ module ActiveRecord
# to avoid cross references when loading a constant for the
# first time. Also, make it output to STDERR.
console do |app|
- require "active_record/railties/console_sandbox" if app.sandbox?
- require "active_record/base"
+ require_relative "railties/console_sandbox" if app.sandbox?
+ require_relative "base"
unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
console = ActiveSupport::Logger.new(STDERR)
Rails.logger.extend ActiveSupport::Logger.broadcast console
@@ -57,7 +57,7 @@ module ActiveRecord
end
runner do
- require "active_record/base"
+ require_relative "base"
end
initializer "active_record.initialize_timezone" do
@@ -101,7 +101,7 @@ module ActiveRecord
initializer "active_record.warn_on_records_fetched_greater_than" do
if config.active_record.warn_on_records_fetched_greater_than
ActiveSupport.on_load(:active_record) do
- require "active_record/relation/record_fetch_warning"
+ require_relative "relation/record_fetch_warning"
end
end
end
@@ -139,7 +139,7 @@ end_warning
# Expose database runtime to controller for logging.
initializer "active_record.log_runtime" do
- require "active_record/railties/controller_runtime"
+ require_relative "railties/controller_runtime"
ActiveSupport.on_load(:action_controller) do
include ActiveRecord::Railties::ControllerRuntime
end
diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb
index 8658188623..4030cdc158 100644
--- a/activerecord/lib/active_record/railties/controller_runtime.rb
+++ b/activerecord/lib/active_record/railties/controller_runtime.rb
@@ -1,5 +1,5 @@
require "active_support/core_ext/module/attr_internal"
-require "active_record/log_subscriber"
+require_relative "../log_subscriber"
module ActiveRecord
module Railties # :nodoc:
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 711099e9e1..abc7323341 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -187,7 +187,7 @@ db_namespace = namespace :db do
namespace :fixtures do
desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task load: [:environment, :load_config] do
- require "active_record/fixtures"
+ require_relative "../fixtures"
base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path
@@ -209,7 +209,7 @@ db_namespace = namespace :db do
# desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task identify: [:environment, :load_config] do
- require "active_record/fixtures"
+ require_relative "../fixtures"
label, id = ENV["LABEL"], ENV["ID"]
raise "LABEL or ID required" if label.blank? && id.blank?
@@ -235,7 +235,7 @@ db_namespace = namespace :db do
namespace :schema do
desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record"
task dump: [:environment, :load_config] do
- require "active_record/schema_dumper"
+ require_relative "../schema_dumper"
filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema.rb")
File.open(filename, "w:utf-8") do |file|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index e8ee8279fd..a453ca55c7 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -171,7 +171,7 @@ module ActiveRecord
JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
def join_keys
- get_join_keys klass
+ @join_keys ||= get_join_keys(klass)
end
# Returns a list of scopes that should be applied for this Reflection
@@ -185,10 +185,34 @@ module ActiveRecord
end
deprecate :scope_chain
+ def build_join_constraint(table, foreign_table)
+ key = join_keys.key
+ foreign_key = join_keys.foreign_key
+
+ constraint = table[key].eq(foreign_table[foreign_key])
+
+ if klass.finder_needs_type_condition?
+ table.create_and([constraint, klass.send(:type_condition, table)])
+ else
+ constraint
+ end
+ end
+
+ def join_scope(table, foreign_klass)
+ predicate_builder = predicate_builder(table)
+ scope_chain_items = join_scopes(table, predicate_builder)
+ klass_scope = klass_join_scope(table, predicate_builder)
+
+ if type
+ klass_scope.where!(type => foreign_klass.base_class.sti_name)
+ end
+
+ scope_chain_items.inject(klass_scope, &:merge!)
+ end
+
def join_scopes(table, predicate_builder) # :nodoc:
if scope
- [ActiveRecord::Relation.create(klass, table, predicate_builder)
- .instance_exec(&scope)]
+ [build_scope(table, predicate_builder).instance_exec(&scope)]
else
[]
end
@@ -200,12 +224,7 @@ module ActiveRecord
scope.joins_values = scope.left_outer_joins_values = [].freeze
}
else
- relation = ActiveRecord::Relation.create(
- klass,
- table,
- predicate_builder,
- )
- klass.send(:build_default_scope, relation)
+ klass.default_scoped(build_scope(table, predicate_builder))
end
end
@@ -287,12 +306,19 @@ module ActiveRecord
JoinKeys.new(join_pk(association_klass), join_fk)
end
+ def build_scope(table, predicate_builder = predicate_builder(table))
+ Relation.create(klass, table, predicate_builder)
+ end
+
protected
def actual_source_reflection # FIXME: this is a horrible name
self
end
private
+ def predicate_builder(table)
+ PredicateBuilder.new(TableMetadata.new(klass, table))
+ end
def join_pk(_)
foreign_key
@@ -570,7 +596,7 @@ module ActiveRecord
end
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
- INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
+ INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :foreign_key]
def add_as_source(seed)
seed
@@ -641,9 +667,8 @@ module ActiveRecord
# us from being able to guess the inverse automatically. First, the
# <tt>inverse_of</tt> option cannot be set to false. Second, we must
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
- # Third, we must not have options such as <tt>:polymorphic</tt> or
- # <tt>:foreign_key</tt> which prevent us from correctly guessing the
- # inverse association.
+ # Third, we must not have options such as <tt>:foreign_key</tt>
+ # which prevent us from correctly guessing the inverse association.
#
# Anything with a scope can additionally ruin our attempt at finding an
# inverse, so we exclude reflections with scopes.
@@ -746,10 +771,6 @@ module ActiveRecord
end
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
- def initialize(name, scope, options, active_record)
- super
- end
-
def macro; :has_and_belongs_to_many; end
def collection?
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 7a8f9abb36..76cf47a3ed 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -6,7 +6,7 @@ module ActiveRecord
:extending, :unscope]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
- :reverse_order, :distinct, :create_with]
+ :reverse_order, :distinct, :create_with, :skip_query_cache]
CLAUSE_METHODS = [:where, :having, :from]
INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having]
@@ -18,7 +18,7 @@ module ActiveRecord
attr_reader :table, :klass, :loaded, :predicate_builder
alias :model :klass
alias :loaded? :loaded
- alias :locked? :locked
+ alias :locked? :lock_value
def initialize(klass, table, predicate_builder, values = {})
@klass = klass
@@ -333,7 +333,7 @@ module ActiveRecord
# Please check unscoped if you want to remove all previous scopes (including
# the default_scope) during the execution of a block.
def scoping
- previous, klass.current_scope = klass.current_scope, self
+ previous, klass.current_scope = klass.current_scope(true), self
yield
ensure
klass.current_scope = previous
@@ -657,20 +657,32 @@ module ActiveRecord
end
def exec_queries(&block)
- @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes, &block).freeze
-
- preload = preload_values
- preload += includes_values unless eager_loading?
- preloader = nil
- preload.each do |associations|
- preloader ||= build_preloader
- preloader.preload @records, associations
- end
+ skip_query_cache_if_necessary do
+ @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes, &block).freeze
+
+ preload = preload_values
+ preload += includes_values unless eager_loading?
+ preloader = nil
+ preload.each do |associations|
+ preloader ||= build_preloader
+ preloader.preload @records, associations
+ end
- @records.each(&:readonly!) if readonly_value
+ @records.each(&:readonly!) if readonly_value
- @loaded = true
- @records
+ @loaded = true
+ @records
+ end
+ end
+
+ def skip_query_cache_if_necessary
+ if skip_query_cache_value
+ uncached do
+ yield
+ end
+ else
+ yield
+ end
end
def build_preloader
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 13a2c3f511..c7e4f8a88a 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -1,4 +1,4 @@
-require "active_record/relation/batches/batch_enumerator"
+require_relative "batches/batch_enumerator"
module ActiveRecord
module Batches
@@ -209,6 +209,7 @@ module ActiveRecord
relation = relation.reorder(batch_order).limit(batch_limit)
relation = apply_limits(relation, start, finish)
+ relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
batch_relation = relation
loop do
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index c562f214c9..aaba6c71f2 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -41,7 +41,7 @@ module ActiveRecord
unless column_name.nil?
ActiveSupport::Deprecation.warn \
"When `count' is called with a block, it ignores other arguments. " \
- "This behavior is now deprecated and will result in an ArgumentError in Rails 5.3."
+ "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0."
end
return super()
@@ -86,7 +86,7 @@ module ActiveRecord
unless column_name.nil?
ActiveSupport::Deprecation.warn \
"When `sum' is called with a block, it ignores other arguments. " \
- "This behavior is now deprecated and will result in an ArgumentError in Rails 5.3."
+ "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0."
end
return super()
@@ -184,7 +184,7 @@ module ActiveRecord
relation.select_values = column_names.map { |cn|
@klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
}
- result = klass.connection.select_all(relation.arel, nil, bound_attributes)
+ result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil, bound_attributes) }
result.cast_values(klass.attribute_types)
end
end
@@ -260,7 +260,7 @@ module ActiveRecord
query_builder = relation.arel
end
- result = @klass.connection.select_all(query_builder, nil, bound_attributes)
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, nil, bound_attributes) }
row = result.first
value = row && row.values.first
type = result.column_types.fetch(column_alias) do
@@ -311,7 +311,7 @@ module ActiveRecord
relation.group_values = group_fields
relation.select_values = select_values
- calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
+ calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil, relation.bound_attributes) }
if association
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index dada0fddf8..48c4dcdef4 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -44,8 +44,6 @@ module ActiveRecord
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, to: :klass
- delegate :ast, :locked, to: :arel
-
module ClassSpecificRelation # :nodoc:
extend ActiveSupport::Concern
@@ -91,6 +89,8 @@ module ActiveRecord
self.class.delegate_to_scoped_klass(method)
scoping { @klass.public_send(method, *args, &block) }
elsif arel.respond_to?(method)
+ ActiveSupport::Deprecation.warn \
+ "Delegating #{method} to arel is deprecated and will be removed in Rails 6.0."
self.class.delegate method, to: :arel
arel.public_send(method, *args, &block)
else
@@ -115,15 +115,5 @@ module ActiveRecord
def respond_to_missing?(method, _)
super || @klass.respond_to?(method) || arel.respond_to?(method)
end
-
- def method_missing(method, *args, &block)
- if @klass.respond_to?(method)
- scoping { @klass.public_send(method, *args, &block) }
- elsif arel.respond_to?(method)
- arel.public_send(method, *args, &block)
- else
- super
- end
- end
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 1d661fa8ed..ac0b4f597e 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -147,8 +147,7 @@ module ActiveRecord
def last(limit = nil)
return find_last(limit) if loaded? || limit_value
- result = limit(limit)
- result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
+ result = ordered_relation.limit(limit)
result = result.reverse_order!
limit ? result.reverse : result.first
@@ -316,7 +315,7 @@ module ActiveRecord
relation = construct_relation_for_exists(relation, conditions)
- connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
+ skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists", relation.bound_attributes) } ? true : false
rescue ::RangeError
false
end
@@ -335,14 +334,14 @@ module ActiveRecord
name = @klass.name
if ids.nil?
- error = "Couldn't find #{name}"
+ error = "Couldn't find #{name}".dup
error << " with#{conditions}" if conditions
raise RecordNotFound.new(error, name)
elsif Array(ids).size == 1
error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
raise RecordNotFound.new(error, name, key, ids)
else
- error = "Couldn't find all #{name.pluralize} with '#{key}': "
+ error = "Couldn't find all #{name.pluralize} with '#{key}': ".dup
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
raise RecordNotFound.new(error, name, primary_key, ids)
@@ -377,8 +376,7 @@ module ActiveRecord
if ActiveRecord::NullRelation === relation
[]
else
- arel = relation.arel
- rows = connection.select_all(arel, "SQL", relation.bound_attributes)
+ rows = skip_query_cache_if_necessary { connection.select_all(relation.arel, "SQL", relation.bound_attributes) }
join_dependency.instantiate(rows, aliases)
end
end
@@ -425,9 +423,8 @@ module ActiveRecord
"#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
relation = relation.except(:select).select(values).distinct!
- arel = relation.arel
- id_rows = @klass.connection.select_all(arel, "SQL", relation.bound_attributes)
+ id_rows = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "SQL", relation.bound_attributes) }
id_rows.map { |row| row[primary_key] }
end
@@ -535,11 +532,7 @@ module ActiveRecord
if loaded?
records[index, limit] || []
else
- relation = if order_values.empty? && primary_key
- order(arel_attribute(primary_key).asc)
- else
- self
- end
+ relation = ordered_relation
if limit_value.nil? || index < limit_value
relation = relation.offset(offset_index + index) unless index.zero?
@@ -554,11 +547,7 @@ module ActiveRecord
if loaded?
records[-index]
else
- relation = if order_values.empty? && primary_key
- order(arel_attribute(primary_key).asc)
- else
- self
- end
+ relation = ordered_relation
relation.to_a[-index]
# TODO: can be made more performant on large result sets by
@@ -572,5 +561,13 @@ module ActiveRecord
def find_last(limit)
limit ? records.last(limit) : records.last
end
+
+ def ordered_relation
+ if order_values.empty? && primary_key
+ order(arel_attribute(primary_key).asc)
+ else
+ self
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index a6309e0b5c..eb80c9a00d 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -107,21 +107,26 @@ module ActiveRecord
first = value.begin
last = value.end
unless first.respond_to?(:infinite?) && first.infinite?
- binds << build_bind_param(column_name, first)
+ binds << build_bind_attribute(column_name, first)
first = Arel::Nodes::BindParam.new
end
unless last.respond_to?(:infinite?) && last.infinite?
- binds << build_bind_param(column_name, last)
+ binds << build_bind_attribute(column_name, last)
last = Arel::Nodes::BindParam.new
end
result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?)
+ when value.is_a?(Relation)
+ binds.concat(value.bound_attributes)
else
if can_be_bound?(column_name, value)
- result[column_name] = Arel::Nodes::BindParam.new
- binds << build_bind_param(column_name, value)
- elsif value.is_a?(Relation)
- binds.concat(value.bound_attributes)
+ bind_attribute = build_bind_attribute(column_name, value)
+ if value.is_a?(StatementCache::Substitute) || !bind_attribute.value_for_database.nil?
+ result[column_name] = Arel::Nodes::BindParam.new
+ binds << bind_attribute
+ else
+ result[column_name] = nil
+ end
end
end
end
@@ -164,17 +169,17 @@ module ActiveRecord
end
end
- def build_bind_param(column_name, value)
+ def build_bind_attribute(column_name, value)
Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
end
end
end
-require "active_record/relation/predicate_builder/array_handler"
-require "active_record/relation/predicate_builder/base_handler"
-require "active_record/relation/predicate_builder/basic_object_handler"
-require "active_record/relation/predicate_builder/range_handler"
-require "active_record/relation/predicate_builder/relation_handler"
+require_relative "predicate_builder/array_handler"
+require_relative "predicate_builder/base_handler"
+require_relative "predicate_builder/basic_object_handler"
+require_relative "predicate_builder/range_handler"
+require_relative "predicate_builder/relation_handler"
-require "active_record/relation/predicate_builder/association_query_value"
-require "active_record/relation/predicate_builder/polymorphic_array_value"
+require_relative "predicate_builder/association_query_value"
+require_relative "predicate_builder/polymorphic_array_value"
diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb
index a68e508fcc..0e1f64775d 100644
--- a/activerecord/lib/active_record/relation/query_attribute.rb
+++ b/activerecord/lib/active_record/relation/query_attribute.rb
@@ -1,4 +1,4 @@
-require "active_record/attribute"
+require_relative "../attribute"
module ActiveRecord
class Relation
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 79e65baae5..79495ead91 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1,7 +1,7 @@
-require "active_record/relation/from_clause"
-require "active_record/relation/query_attribute"
-require "active_record/relation/where_clause"
-require "active_record/relation/where_clause_factory"
+require_relative "from_clause"
+require_relative "query_attribute"
+require_relative "where_clause"
+require_relative "where_clause_factory"
require "active_model/forbidden_attributes_protection"
module ActiveRecord
@@ -913,6 +913,11 @@ module ActiveRecord
self
end
+ def skip_query_cache! # :nodoc:
+ self.skip_query_cache_value = true
+ self
+ end
+
# Returns the Arel object associated with the relation.
def arel # :nodoc:
@arel ||= build_arel
@@ -1122,7 +1127,7 @@ module ActiveRecord
validate_order_args(order_args)
references = order_args.grep(String)
- references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
+ references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
references!(references) if references.any?
# if a symbol is given we prepend the quoted table name
@@ -1167,7 +1172,7 @@ module ActiveRecord
end
end
- STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having]
+ STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope]
def structurally_incompatible_values_for_or(other)
STRUCTURAL_OR_METHODS.reject do |method|
get_value(method) == other.get_value(method)
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index ada89b5ec3..ddf7f825c1 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -1,6 +1,6 @@
require "active_support/core_ext/hash/except"
require "active_support/core_ext/hash/slice"
-require "active_record/relation/merger"
+require_relative "merger"
module ActiveRecord
module SpawnMethods
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
index 04bee73e8f..b862dd56a5 100644
--- a/activerecord/lib/active_record/relation/where_clause_factory.rb
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -57,7 +57,7 @@ module ActiveRecord
else
column = klass.column_for_attribute(attribute)
- binds << predicate_builder.send(:build_bind_param, attribute, value)
+ binds << predicate_builder.send(:build_bind_attribute, attribute, value)
value = Arel::Nodes::BindParam.new
predicate = if options[:case_sensitive]
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 24b81aabc8..66a2846f3a 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -13,8 +13,7 @@ module ActiveRecord
# A list of tables which should not be dumped to the schema.
# Acceptable values are strings as well as regexp if ActiveRecord::Base.schema_format == :ruby.
# Only strings are accepted if ActiveRecord::Base.schema_format == :sql.
- cattr_accessor :ignore_tables
- @@ignore_tables = []
+ cattr_accessor :ignore_tables, default: []
class << self
def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base)
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index f59737afb0..6dbabd69a1 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -1,5 +1,5 @@
-require "active_record/scoping/default"
-require "active_record/scoping/named"
+require_relative "scoping/default"
+require_relative "scoping/named"
module ActiveRecord
# This class is used to create a table that keeps track of which migrations
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index 7c00e7e4ed..94e0ef6724 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -10,8 +10,8 @@ module ActiveRecord
end
module ClassMethods
- def current_scope #:nodoc:
- ScopeRegistry.value_for(:current_scope, self)
+ def current_scope(skip_inherited_scope = false) # :nodoc:
+ ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
end
def current_scope=(scope) #:nodoc:
@@ -75,8 +75,9 @@ module ActiveRecord
end
# Obtains the value for a given +scope_type+ and +model+.
- def value_for(scope_type, model)
+ def value_for(scope_type, model, skip_inherited_scope = false)
raise_invalid_scope_type!(scope_type)
+ return @registry[scope_type][model.name] if skip_inherited_scope
klass = model
base = model.base_class
while klass <= base
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index a61fdd6454..b4026fabb2 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -29,8 +29,7 @@ module ActiveRecord
end
end
- def default_scoped # :nodoc:
- scope = relation
+ def default_scoped(scope = relation) # :nodoc:
build_default_scope(scope) || scope
end
@@ -160,7 +159,7 @@ module ActiveRecord
if body.respond_to?(:to_proc)
singleton_class.send(:define_method, name) do |*args|
scope = all
- scope = scope.scoping { instance_exec(*args, &body) || scope }
+ scope = scope.instance_exec(*args, &body) || scope
scope = scope.extending(extension) if extension
scope
end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 541165b3d1..ff6745f7b5 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -59,7 +59,6 @@ module ActiveRecord
args.concat(["--no-data"])
args.concat(["--routines"])
args.concat(["--skip-comments"])
- args.concat(Array(extra_flags)) if extra_flags
ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
if ignore_tables.any?
@@ -67,6 +66,7 @@ module ActiveRecord
end
args.concat(["#{configuration['database']}"])
+ args.unshift(*extra_flags) if extra_flags
run_cmd("mysqldump", args, "dumping")
end
@@ -75,7 +75,7 @@ module ActiveRecord
args = prepare_command_options
args.concat(["--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}])
args.concat(["--database", "#{configuration['database']}"])
- args.concat(Array(extra_flags)) if extra_flags
+ args.unshift(*extra_flags) if extra_flags
run_cmd("mysql", args, "loading")
end
@@ -99,7 +99,7 @@ module ActiveRecord
def error_class
if configuration["adapter"].include?("jdbc")
- require "active_record/railties/jdbcmysql_error"
+ require_relative "../railties/jdbcmysql_error"
ArJdbcMySQL::Error
elsif defined?(Mysql2)
Mysql2::Error
@@ -151,7 +151,7 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
end
def run_cmd_error(cmd, args, action)
- msg = "failed to execute: `#{cmd}`\n"
+ msg = "failed to execute: `#{cmd}`\n".dup
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
msg
end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 55f3a194a9..26eea0bc24 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -86,7 +86,7 @@ module ActiveRecord
all_timestamp_attributes_in_model.each do |column|
if !attribute_present?(column)
- write_attribute(column, current_time)
+ _write_attribute(column, current_time)
end
end
end
@@ -100,7 +100,7 @@ module ActiveRecord
timestamp_attributes_for_update_in_model.each do |column|
next if will_save_change_to_attribute?(column)
- write_attribute(column, current_time)
+ _write_attribute(column, current_time)
end
end
super(*args)
@@ -126,7 +126,7 @@ module ActiveRecord
self.class.send(:current_time_from_proper_timezone)
end
- def max_updated_column_timestamp(timestamp_names = self.class.send(:timestamp_attributes_for_update))
+ def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update_in_model)
timestamp_names
.map { |attr| self[attr] }
.compact
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 45795fa287..463bb1f314 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -490,7 +490,7 @@ module ActiveRecord
def update_attributes_from_transaction_state(transaction_state)
if transaction_state && transaction_state.finalized?
restore_transaction_record_state if transaction_state.rolledback?
- clear_transaction_record_state
+ clear_transaction_record_state if transaction_state.fully_completed?
end
end
end
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index 9ed6c95bf9..6f4e35b159 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -1,20 +1,20 @@
require "active_model/type"
-require "active_record/type/internal/timezone"
+require_relative "type/internal/timezone"
-require "active_record/type/date"
-require "active_record/type/date_time"
-require "active_record/type/decimal_without_scale"
-require "active_record/type/json"
-require "active_record/type/time"
-require "active_record/type/text"
-require "active_record/type/unsigned_integer"
+require_relative "type/date"
+require_relative "type/date_time"
+require_relative "type/decimal_without_scale"
+require_relative "type/json"
+require_relative "type/time"
+require_relative "type/text"
+require_relative "type/unsigned_integer"
-require "active_record/type/serialized"
-require "active_record/type/adapter_specific_registry"
+require_relative "type/serialized"
+require_relative "type/adapter_specific_registry"
-require "active_record/type/type_map"
-require "active_record/type/hash_lookup_type_map"
+require_relative "type/type_map"
+require_relative "type/hash_lookup_type_map"
module ActiveRecord
module Type
diff --git a/activerecord/lib/active_record/type_caster.rb b/activerecord/lib/active_record/type_caster.rb
index f1686e4913..8b12a30c6a 100644
--- a/activerecord/lib/active_record/type_caster.rb
+++ b/activerecord/lib/active_record/type_caster.rb
@@ -1,5 +1,5 @@
-require "active_record/type_caster/map"
-require "active_record/type_caster/connection"
+require_relative "type_caster/map"
+require_relative "type_caster/connection"
module ActiveRecord
module TypeCaster # :nodoc:
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 9633f226f0..046b69bee2 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -84,8 +84,8 @@ module ActiveRecord
end
end
-require "active_record/validations/associated"
-require "active_record/validations/uniqueness"
-require "active_record/validations/presence"
-require "active_record/validations/absence"
-require "active_record/validations/length"
+require_relative "validations/associated"
+require_relative "validations/uniqueness"
+require_relative "validations/presence"
+require_relative "validations/absence"
+require_relative "validations/length"
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index a1fb6427f9..827bcba121 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -211,6 +211,28 @@ module ActiveRecord
end
end
+ def test_exceptions_from_notifications_are_not_translated
+ original_error = StandardError.new("This StandardError shouldn't get translated")
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") { raise original_error }
+ actual_error = assert_raises(StandardError) do
+ @connection.execute("SELECT * FROM posts")
+ end
+
+ assert_equal original_error, actual_error
+
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+
+ def test_database_related_exceptions_are_translated_to_statement_invalid
+ error = assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.execute("This is a syntax error")
+ end
+
+ assert_instance_of ActiveRecord::StatementInvalid, error
+ assert_kind_of Exception, error.cause
+ end
+
def test_select_all_always_return_activerecord_result
result = @connection.select_all "SELECT * FROM posts"
assert result.is_a?(ActiveRecord::Result)
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
deleted file mode 100644
index 2c778b1150..0000000000
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ /dev/null
@@ -1,151 +0,0 @@
-require "cases/helper"
-
-# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
-# reserved word names (ie: group, order, values, etc...)
-class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase
- class Group < ActiveRecord::Base
- Group.table_name = "group"
- belongs_to :select
- has_one :values
- end
-
- class Select < ActiveRecord::Base
- Select.table_name = "select"
- has_many :groups
- end
-
- class Values < ActiveRecord::Base
- Values.table_name = "values"
- end
-
- class Distinct < ActiveRecord::Base
- Distinct.table_name = "distinct"
- has_and_belongs_to_many :selects
- has_many :values, through: :groups
- end
-
- def setup
- @connection = ActiveRecord::Base.connection
-
- # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table()
- # will fail with these table names if these test cases fail
-
- create_tables_directly "group" => "id int auto_increment primary key, `order` varchar(255), select_id int",
- "select" => "id int auto_increment primary key",
- "values" => "id int auto_increment primary key, group_id int",
- "distinct" => "id int auto_increment primary key",
- "distinct_select" => "distinct_id int, select_id int"
- end
-
- teardown do
- drop_tables_directly ["group", "select", "values", "distinct", "distinct_select", "order"]
- end
-
- # create tables with reserved-word names and columns
- def test_create_tables
- assert_nothing_raised {
- @connection.create_table :order do |t|
- t.column :group, :string
- end
- }
- end
-
- # rename tables with reserved-word names
- def test_rename_tables
- assert_nothing_raised { @connection.rename_table(:group, :order) }
- end
-
- # alter column with a reserved-word name in a table with a reserved-word name
- def test_change_columns
- assert_nothing_raised { @connection.change_column_default(:group, :order, "whatever") }
- #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter
- assert_nothing_raised { @connection.change_column("group", "order", :Int, default: 0) }
- assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
- end
-
- # introspect table with reserved word name
- def test_introspect
- assert_nothing_raised { @connection.columns(:group) }
- assert_nothing_raised { @connection.indexes(:group) }
- end
-
- #fixtures
- self.use_instantiated_fixtures = true
- self.use_transactional_tests = false
-
- #activerecord model class with reserved-word table name
- def test_activerecord_model
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- x = nil
- assert_nothing_raised { x = Group.new }
- x.order = "x"
- assert_nothing_raised { x.save }
- x.order = "y"
- assert_nothing_raised { x.save }
- assert_nothing_raised { Group.find_by_order("y") }
- assert_nothing_raised { Group.find(1) }
- end
-
- # has_one association with reserved-word table name
- def test_has_one_associations
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- v = nil
- assert_nothing_raised { v = Group.find(1).values }
- assert_equal 2, v.id
- end
-
- # belongs_to association with reserved-word table name
- def test_belongs_to_associations
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- gs = nil
- assert_nothing_raised { gs = Select.find(2).groups }
- assert_equal gs.length, 2
- assert(gs.collect(&:id).sort == [2, 3])
- end
-
- # has_and_belongs_to_many with reserved-word table name
- def test_has_and_belongs_to_many
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- s = nil
- assert_nothing_raised { s = Distinct.find(1).selects }
- assert_equal s.length, 2
- assert(s.collect(&:id).sort == [1, 2])
- end
-
- # activerecord model introspection with reserved-word table and column names
- def test_activerecord_introspection
- assert_nothing_raised { Group.table_exists? }
- assert_nothing_raised { Group.columns }
- end
-
- # Calculations
- def test_calculations_work_with_reserved_words
- assert_nothing_raised { Group.count }
- end
-
- def test_associations_work_with_reserved_words
- assert_nothing_raised { Select.all.merge!(includes: [:groups]).to_a }
- end
-
- #the following functions were added to DRY test cases
-
- private
- # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path
- def create_test_fixtures(*fixture_names)
- ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
- end
-
- # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
- def drop_tables_directly(table_names, connection = @connection)
- table_names.each do |name|
- connection.drop_table name, if_exists: true
- end
- end
-
- # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns
- def create_tables_directly(tables, connection = @connection)
- tables.each do |table_name, column_properties|
- connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )")
- end
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb
index 442a4fb7b5..1c5ef2aa41 100644
--- a/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb
@@ -52,7 +52,7 @@ if ActiveRecord::Base.connection.supports_virtual_columns?
def test_schema_dumping
output = dump_table_schema("virtual_columns")
- assert_match(/t\.virtual\s+"upper_name",\s+type: :string,\s+as: "UPPER\(`name`\)"$/i, output)
+ assert_match(/t\.virtual\s+"upper_name",\s+type: :string,\s+as: "(?:UPPER|UCASE)\(`name`\)"$/i, output)
assert_match(/t\.virtual\s+"name_length",\s+type: :integer,\s+as: "LENGTH\(`name`\)",\s+stored: true$/i, output)
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index c78c6178ff..121c62dadf 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -191,6 +191,12 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
assert_equal(PgArray.last.tags, tag_values)
end
+ def test_insert_fixtures
+ tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"]
+ @connection.insert_fixtures([{ "tags" => tag_values }], "pg_arrays")
+ assert_equal(PgArray.last.tags, tag_values)
+ end
+
def test_attribute_for_inspect_for_array_field
record = PgArray.new { |a| a.ratings = (1..10).to_a }
assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]", record.attribute_for_inspect(:ratings))
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 539c90f0bc..65baed34e9 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -47,7 +47,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
end
def test_type_cast_binary_value
- data = "\u001F\x8B".force_encoding("BINARY")
+ data = "\u001F\x8B".dup.force_encoding("BINARY")
assert_equal(data, @type.deserialize(data))
end
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index c1f3a4ae2c..3b6840a1c9 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -93,8 +93,6 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
end
def test_empty_string_assignment
- assert_nothing_raised { PostgresqlPoint.new(x: "") }
-
p = PostgresqlPoint.new(x: "")
assert_nil p.x
end
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index 1b5d8362af..ea060345a7 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -47,10 +47,10 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
def test_money_type_cast
type = PostgresqlMoney.type_for_attribute("wealth")
- assert_equal(12345678.12, type.cast("$12,345,678.12"))
- assert_equal(12345678.12, type.cast("$12.345.678,12"))
- assert_equal(-1.15, type.cast("-$1.15"))
- assert_equal(-2.25, type.cast("($2.25)"))
+ assert_equal(12345678.12, type.cast("$12,345,678.12".dup))
+ assert_equal(12345678.12, type.cast("$12.345.678,12".dup))
+ assert_equal(-1.15, type.cast("-$1.15".dup))
+ assert_equal(-2.25, type.cast("($2.25)".dup))
end
def test_schema_dumping
@@ -60,7 +60,7 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
end
def test_create_and_update_money
- money = PostgresqlMoney.create(wealth: "987.65")
+ money = PostgresqlMoney.create(wealth: "987.65".dup)
assert_equal 987.65, money.wealth
new_value = BigDecimal.new("123.45")
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index b55b766d20..26bf4d2090 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -324,16 +324,16 @@ module ActiveRecord
reset_connection
end
- def test_only_reload_type_map_once_for_every_unknown_type
+ def test_only_reload_type_map_once_for_every_unrecognized_type
reset_connection
connection = ActiveRecord::Base.connection
silence_warnings do
assert_queries 2, ignore_none: true do
- connection.select_all "SELECT NULL::anyelement"
+ connection.select_all "select 'pg_catalog.pg_class'::regclass"
end
assert_queries 1, ignore_none: true do
- connection.select_all "SELECT NULL::anyelement"
+ connection.select_all "select 'pg_catalog.pg_class'::regclass"
end
assert_queries 2, ignore_none: true do
connection.select_all "SELECT NULL::anyarray"
@@ -343,16 +343,16 @@ module ActiveRecord
reset_connection
end
- def test_only_warn_on_first_encounter_of_unknown_oid
+ def test_only_warn_on_first_encounter_of_unrecognized_oid
reset_connection
connection = ActiveRecord::Base.connection
warning = capture(:stderr) {
- connection.select_all "SELECT NULL::anyelement"
- connection.select_all "SELECT NULL::anyelement"
- connection.select_all "SELECT NULL::anyelement"
+ connection.select_all "select 'pg_catalog.pg_class'::regclass"
+ connection.select_all "select 'pg_catalog.pg_class'::regclass"
+ connection.select_all "select 'pg_catalog.pg_class'::regclass"
}
- assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'\. It will be treated as String\.\n\z/, warning)
+ assert_match(/\Aunknown OID \d+: failed to recognize type of 'regclass'\. It will be treated as String\.\n\z/, warning)
ensure
reset_connection
end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 6ebe9d82a7..00de92cdfd 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -40,7 +40,8 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
drop_table "uuid_data_type"
end
- if ActiveRecord::Base.connection.supports_pgcrypto_uuid?
+ if ActiveRecord::Base.connection.respond_to?(:supports_pgcrypto_uuid?) &&
+ ActiveRecord::Base.connection.supports_pgcrypto_uuid?
def test_uuid_column_default
connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "gen_random_uuid()"
UUIDType.reset_column_information
@@ -64,11 +65,11 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
end
def test_add_column_with_null_true_and_default_nil
- assert_nothing_raised do
- connection.add_column :uuid_data_type, :thingy, :uuid, null: true, default: nil
- end
+ connection.add_column :uuid_data_type, :thingy, :uuid, null: true, default: nil
+
UUIDType.reset_column_information
column = UUIDType.columns_hash["thingy"]
+
assert column.null
assert_nil column.default
end
@@ -123,7 +124,9 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
"Z0000C99-9C0B-4EF8-BB6D-6BB9BD380A11",
"a0eebc999r0b4ef8ab6d6bb9bd380a11",
"a0ee-bc99------4ef8-bb6d-6bb9-bd38-0a11",
- "{a0eebc99-bb6d6bb9-bd380a11}"].each do |invalid_uuid|
+ "{a0eebc99-bb6d6bb9-bd380a11}",
+ "{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11",
+ "a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}"].each do |invalid_uuid|
uuid = UUIDType.new guid: invalid_uuid
assert_nil uuid.guid
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 9a812e325e..cf0c37f70c 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -165,7 +165,7 @@ module ActiveRecord
data binary
)
eosql
- str = "\x80".force_encoding("ASCII-8BIT")
+ str = "\x80".dup.force_encoding("ASCII-8BIT")
binary = DualEncoding.new name: "いただきます!", data: str
binary.save!
assert_equal str, binary.data
@@ -174,7 +174,7 @@ module ActiveRecord
end
def test_type_cast_should_not_mutate_encoding
- name = "hello".force_encoding(Encoding::ASCII_8BIT)
+ name = "hello".dup.force_encoding(Encoding::ASCII_8BIT)
Owner.create(name: name)
assert_equal Encoding::ASCII_8BIT, name.encoding
ensure
diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb
index c322333f6d..c54542ff7b 100644
--- a/activerecord/test/cases/associations/association_scope_test.rb
+++ b/activerecord/test/cases/associations/association_scope_test.rb
@@ -6,8 +6,7 @@ module ActiveRecord
module Associations
class AssociationScopeTest < ActiveRecord::TestCase
test "does not duplicate conditions" do
- scope = AssociationScope.scope(Author.new.association(:welcome_posts),
- Author.connection)
+ scope = AssociationScope.scope(Author.new.association(:welcome_posts))
binds = scope.where_clause.binds.map(&:value)
assert_equal binds.uniq, binds
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index c8b26232b6..a727cc6e60 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -380,7 +380,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
sponsor.sponsorable = Member.new name: "Bert"
assert_equal Member, sponsor.association(:sponsorable).send(:klass)
- assert_equal "members", sponsor.association(:sponsorable).aliased_table_name
end
def test_with_polymorphic_and_condition
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 3638c87968..7b0445025c 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -34,18 +34,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_hmt_does_not_table_name_collide_when_joining_associations
- assert_nothing_raised do
- Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a
- end
authors = Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a
assert_equal 1, assert_no_queries { authors.size }
assert_equal 10, assert_no_queries { authors[0].comments.size }
end
def test_eager_association_loading_grafts_stashed_associations_to_correct_parent
- assert_nothing_raised do
- Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").to_a
- end
assert_equal people(:michael), Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").first
end
diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
index 4f0fe3236e..61f39b4136 100644
--- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
+++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
@@ -11,25 +11,32 @@ end
class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase
def setup
- generate_test_objects
- end
-
- def generate_test_objects
post = Namespaced::Post.create(title: "Great stuff", body: "This is not", author_id: 1)
- Tagging.create(taggable: post)
+ @tagging = Tagging.create(taggable: post)
+ @old = ActiveRecord::Base.store_full_sti_class
end
- def test_class_names
- old = ActiveRecord::Base.store_full_sti_class
+ def teardown
+ ActiveRecord::Base.store_full_sti_class = @old
+ end
+ def test_class_names_with_includes
ActiveRecord::Base.store_full_sti_class = false
post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff")
assert_nil post.tagging
ActiveRecord::Base.store_full_sti_class = true
post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff")
- assert_instance_of Tagging, post.tagging
- ensure
- ActiveRecord::Base.store_full_sti_class = old
+ assert_equal @tagging, post.tagging
+ end
+
+ def test_class_names_with_eager_load
+ ActiveRecord::Base.store_full_sti_class = false
+ post = Namespaced::Post.eager_load(:tagging).find_by_title("Great stuff")
+ assert_nil post.tagging
+
+ ActiveRecord::Base.store_full_sti_class = true
+ post = Namespaced::Post.eager_load(:tagging).find_by_title("Great stuff")
+ assert_equal @tagging, post.tagging
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 4271a09c9b..c0bab19e82 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -68,6 +68,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
"expected to find only david's posts"
end
+ def test_loading_with_scope_including_joins
+ assert_equal clubs(:boring_club), Member.preload(:general_club).find(1).general_club
+ assert_equal clubs(:boring_club), Member.eager_load(:general_club).find(1).general_club
+ end
+
def test_with_ordering
list = Post.all.merge!(includes: :comments, order: "posts.id DESC").to_a
[:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other,
@@ -271,9 +276,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_loading_from_an_association_that_has_a_hash_of_conditions
- assert_nothing_raised do
- Author.all.merge!(includes: :hello_posts_with_hash_conditions).to_a
- end
assert !Author.all.merge!(includes: :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty?
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 6102f98682..eeba8fa74f 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -241,6 +241,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal "defaulty", bulb.name
end
+ def test_build_from_association_sets_inverse_instance
+ car = Car.new(name: "honda")
+
+ bulb = car.bulbs.build
+ assert_equal car, bulb.car
+ end
+
def test_do_not_call_callbacks_for_delete_all
car = Car.create(name: "honda")
car.funky_bulbs.create!
@@ -744,6 +751,41 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal client2, firm.clients.merge!(where: ["#{QUOTED_TYPE} = :type", { type: "Client" }], order: "id").first
end
+ def test_find_first_after_reset_scope
+ firm = Firm.all.merge!(order: "id").first
+ collection = firm.clients
+
+ original_object = collection.first
+ assert_same original_object, collection.first, "Expected second call to #first to cache the same object"
+
+ # It should return a different object, since the association has been reloaded
+ assert_not_same original_object, firm.clients.first, "Expected #first to return a new object"
+ end
+
+ def test_find_first_after_reset
+ firm = Firm.all.merge!(order: "id").first
+ collection = firm.clients
+
+ original_object = collection.first
+ assert_same original_object, collection.first, "Expected second call to #first to cache the same object"
+ collection.reset
+
+ # It should return a different object, since the association has been reloaded
+ assert_not_same original_object, collection.first, "Expected #first after #reset to return a new object"
+ end
+
+ def test_find_first_after_reload
+ firm = Firm.all.merge!(order: "id").first
+ collection = firm.clients
+
+ original_object = collection.first
+ assert_same original_object, collection.first, "Expected second call to #first to cache the same object"
+ collection.reload
+
+ # It should return a different object, since the association has been reloaded
+ assert_not_same original_object, collection.first, "Expected #first after #reload to return a new object"
+ end
+
def test_find_all_with_include_and_conditions
assert_nothing_raised do
Developer.all.merge!(joins: :audit_logs, where: { "audit_logs.message" => nil, :name => "Smith" }).to_a
@@ -2143,6 +2185,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal "Post", tagging.taggable_type
end
+ def test_build_from_polymorphic_association_sets_inverse_instance
+ post = Post.new
+ tagging = post.taggings.build
+
+ assert_equal post, tagging.taggable
+ end
+
def test_dont_call_save_callbacks_twice_on_has_many
firm = companies(:first_firm)
contract = firm.contracts.create!
@@ -2322,8 +2371,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
car = Car.create!
bulb = Bulb.create! name: "other", car: car
- assert_equal bulb, Car.find(car.id).all_bulbs.first
- assert_equal bulb, Car.includes(:all_bulbs).find(car.id).all_bulbs.first
+ assert_equal [bulb], Car.find(car.id).all_bulbs
+ assert_equal [bulb], Car.includes(:all_bulbs).find(car.id).all_bulbs
+ assert_equal [bulb], Car.eager_load(:all_bulbs).find(car.id).all_bulbs
end
test "raises RecordNotDestroyed when replaced child can't be destroyed" do
@@ -2511,6 +2561,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [bulb.id], car.bulb_ids
assert_no_queries { car.bulb_ids }
+
+ bulb2 = car.bulbs.create!
+
+ assert_equal [bulb.id, bulb2.id], car.bulb_ids
+ assert_no_queries { car.bulb_ids }
end
def test_loading_association_in_validate_callback_doesnt_affect_persistence
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 9156f6d57a..1c2138a3d0 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -319,6 +319,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_includes post.single_people, person
end
+ def test_build_then_remove_then_save
+ post = posts(:thinking)
+ post.people.build(first_name: "Bob")
+ ted = post.people.build(first_name: "Ted")
+ post.people.delete(ted)
+ post.save!
+ post.reload
+
+ assert_equal ["Bob"], post.people.collect(&:first_name)
+ end
+
def test_both_parent_ids_set_when_saving_new
post = Post.new(title: "Hello", body: "world")
person = Person.new(first_name: "Sean")
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 7c11d2e7fc..bf3b8dcd63 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -307,6 +307,15 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_create_when_parent_is_new_raises
+ firm = Firm.new
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
+ firm.create_account
+ end
+
+ assert_equal "You cannot call create unless the parent is saved", error.message
+ end
+
def test_reload_association
odegy = companies(:odegy)
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 467cc73ecd..9fbaa43274 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -24,11 +24,9 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
monkey_reflection = MixedCaseMonkey.reflect_on_association(:man)
man_reflection = Man.reflect_on_association(:mixed_case_monkey)
- assert_respond_to monkey_reflection, :has_inverse?
assert monkey_reflection.has_inverse?, "The monkey reflection should have an inverse"
assert_equal man_reflection, monkey_reflection.inverse_of, "The monkey reflection's inverse should be the man reflection"
- assert_respond_to man_reflection, :has_inverse?
assert man_reflection.has_inverse?, "The man reflection should have an inverse"
assert_equal monkey_reflection, man_reflection.inverse_of, "The man reflection's inverse should be the monkey reflection"
end
@@ -37,7 +35,6 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
account_reflection = Admin::Account.reflect_on_association(:users)
user_reflection = Admin::User.reflect_on_association(:account)
- assert_respond_to account_reflection, :has_inverse?
assert account_reflection.has_inverse?, "The Admin::Account reflection should have an inverse"
assert_equal user_reflection, account_reflection.inverse_of, "The Admin::Account reflection's inverse should be the Admin::User reflection"
end
@@ -46,11 +43,9 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
car_reflection = Car.reflect_on_association(:bulb)
bulb_reflection = Bulb.reflect_on_association(:car)
- assert_respond_to car_reflection, :has_inverse?
assert car_reflection.has_inverse?, "The Car reflection should have an inverse"
assert_equal bulb_reflection, car_reflection.inverse_of, "The Car reflection's inverse should be the Bulb reflection"
- assert_respond_to bulb_reflection, :has_inverse?
assert bulb_reflection.has_inverse?, "The Bulb reflection should have an inverse"
assert_equal car_reflection, bulb_reflection.inverse_of, "The Bulb reflection's inverse should be the Car reflection"
end
@@ -59,7 +54,6 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
comment_reflection = Comment.reflect_on_association(:ratings)
rating_reflection = Rating.reflect_on_association(:comment)
- assert_respond_to comment_reflection, :has_inverse?
assert comment_reflection.has_inverse?, "The Comment reflection should have an inverse"
assert_equal rating_reflection, comment_reflection.inverse_of, "The Comment reflection's inverse should be the Rating reflection"
end
@@ -107,24 +101,17 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
def test_polymorphic_and_has_many_through_relationships_should_not_have_inverses
sponsor_reflection = Sponsor.reflect_on_association(:sponsorable)
- assert_respond_to sponsor_reflection, :has_inverse?
assert !sponsor_reflection.has_inverse?, "A polymorphic association should not find an inverse automatically"
club_reflection = Club.reflect_on_association(:members)
- assert_respond_to club_reflection, :has_inverse?
assert !club_reflection.has_inverse?, "A has_many_through association should not find an inverse automatically"
end
- def test_polymorphic_relationships_should_still_not_have_inverses_when_non_polymorphic_relationship_has_the_same_name
+ def test_polymorphic_has_one_should_find_inverse_automatically
man_reflection = Man.reflect_on_association(:polymorphic_face_without_inverse)
- face_reflection = Face.reflect_on_association(:man)
- assert_respond_to face_reflection, :has_inverse?
- assert face_reflection.has_inverse?, "For this test, the non-polymorphic association must have an inverse"
-
- assert_respond_to man_reflection, :has_inverse?
- assert !man_reflection.has_inverse?, "The target of a polymorphic association should not find an inverse automatically"
+ assert man_reflection.has_inverse?
end
end
@@ -145,41 +132,24 @@ class InverseAssociationTests < ActiveRecord::TestCase
def test_should_be_able_to_ask_a_reflection_if_it_has_an_inverse
has_one_with_inverse_ref = Man.reflect_on_association(:face)
- assert_respond_to has_one_with_inverse_ref, :has_inverse?
assert has_one_with_inverse_ref.has_inverse?
has_many_with_inverse_ref = Man.reflect_on_association(:interests)
- assert_respond_to has_many_with_inverse_ref, :has_inverse?
assert has_many_with_inverse_ref.has_inverse?
belongs_to_with_inverse_ref = Face.reflect_on_association(:man)
- assert_respond_to belongs_to_with_inverse_ref, :has_inverse?
assert belongs_to_with_inverse_ref.has_inverse?
has_one_without_inverse_ref = Club.reflect_on_association(:sponsor)
- assert_respond_to has_one_without_inverse_ref, :has_inverse?
assert !has_one_without_inverse_ref.has_inverse?
has_many_without_inverse_ref = Club.reflect_on_association(:memberships)
- assert_respond_to has_many_without_inverse_ref, :has_inverse?
assert !has_many_without_inverse_ref.has_inverse?
belongs_to_without_inverse_ref = Sponsor.reflect_on_association(:sponsor_club)
- assert_respond_to belongs_to_without_inverse_ref, :has_inverse?
assert !belongs_to_without_inverse_ref.has_inverse?
end
- def test_should_be_able_to_ask_a_reflection_what_it_is_the_inverse_of
- has_one_ref = Man.reflect_on_association(:face)
- assert_respond_to has_one_ref, :inverse_of
-
- has_many_ref = Man.reflect_on_association(:interests)
- assert_respond_to has_many_ref, :inverse_of
-
- belongs_to_ref = Face.reflect_on_association(:man)
- assert_respond_to belongs_to_ref, :inverse_of
- end
-
def test_inverse_of_method_should_supply_the_actual_reflection_instance_it_is_the_inverse_of
has_one_ref = Man.reflect_on_association(:face)
assert_equal Face.reflect_on_association(:man), has_one_ref.inverse_of
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
index 7cf6b498c9..e856d551c0 100644
--- a/activerecord/test/cases/attribute_test.rb
+++ b/activerecord/test/cases/attribute_test.rb
@@ -76,7 +76,7 @@ module ActiveRecord
end
test "duping dups the value" do
- @type.expect(:deserialize, "type cast", ["a value"])
+ @type.expect(:deserialize, "type cast".dup, ["a value"])
attribute = Attribute.from_database(nil, "a value", @type)
value_from_orig = attribute.value
@@ -244,7 +244,7 @@ module ActiveRecord
end
test "with_type preserves mutations" do
- attribute = Attribute.from_database(:foo, "", Type::Value.new)
+ attribute = Attribute.from_database(:foo, "".dup, Type::Value.new)
attribute.value << "1"
assert_equal 1, attribute.with_type(Type::Integer.new).value
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index fbc3fbb44f..dcd3af487b 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "models/comment"
require "models/post"
require "models/subscriber"
@@ -152,7 +153,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified
- not_a_post = "not a post"
+ not_a_post = "not a post".dup
def not_a_post.id; end
not_a_post.stub(:id, -> { raise StandardError.new("not_a_post had #id called on it") }) do
assert_nothing_raised do
@@ -417,7 +418,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified
- not_a_post = "not a post"
+ not_a_post = "not a post".dup
def not_a_post.id
raise StandardError.new("not_a_post had #id called on it")
end
@@ -610,4 +611,64 @@ class EachTest < ActiveRecord::TestCase
end
assert_equal expected, actual
end
+
+ test ".find_each bypasses the query cache for its own queries" do
+ Post.cache do
+ assert_queries(2) do
+ Post.find_each {}
+ Post.find_each {}
+ end
+ end
+ end
+
+ test ".find_each does not disable the query cache inside the given block" do
+ Post.cache do
+ Post.find_each(start: 1, finish: 1) do |post|
+ assert_queries(1) do
+ post.comments.count
+ post.comments.count
+ end
+ end
+ end
+ end
+
+ test ".find_in_batches bypasses the query cache for its own queries" do
+ Post.cache do
+ assert_queries(2) do
+ Post.find_in_batches {}
+ Post.find_in_batches {}
+ end
+ end
+ end
+
+ test ".find_in_batches does not disable the query cache inside the given block" do
+ Post.cache do
+ Post.find_in_batches(start: 1, finish: 1) do |batch|
+ assert_queries(1) do
+ batch.first.comments.count
+ batch.first.comments.count
+ end
+ end
+ end
+ end
+
+ test ".in_batches bypasses the query cache for its own queries" do
+ Post.cache do
+ assert_queries(2) do
+ Post.in_batches {}
+ Post.in_batches {}
+ end
+ end
+ end
+
+ test ".in_batches does not disable the query cache inside the given block" do
+ Post.cache do
+ Post.in_batches(start: 1, finish: 1) do |relation|
+ assert_queries(1) do
+ relation.count
+ relation.count
+ end
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index 1fc30e24d2..f6ac7990d1 100644
--- a/activerecord/test/cases/binary_test.rb
+++ b/activerecord/test/cases/binary_test.rb
@@ -10,7 +10,7 @@ unless current_adapter?(:DB2Adapter)
FIXTURES = %w(flowers.jpg example.log test.txt)
def test_mixed_encoding
- str = "\x80"
+ str = "\x80".dup
str.force_encoding("ASCII-8BIT")
binary = Binary.new name: "いただきます!", data: str
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index adfca08289..9f44b583f6 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -580,8 +580,11 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_pluck_without_column_names
- assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, ""]],
- Company.order(:id).limit(1).pluck
+ if current_adapter?(:OracleAdapter)
+ assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, nil]], Company.order(:id).limit(1).pluck
+ else
+ assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, ""]], Company.order(:id).limit(1).pluck
+ end
end
def test_pluck_type_cast
@@ -820,4 +823,46 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 6, Account.sum(:firm_id) { 1 }
end
end
+
+ test "#skip_query_cache! for #pluck" do
+ Account.cache do
+ assert_queries(1) do
+ Account.pluck(:credit_limit)
+ Account.pluck(:credit_limit)
+ end
+
+ assert_queries(2) do
+ Account.all.skip_query_cache!.pluck(:credit_limit)
+ Account.all.skip_query_cache!.pluck(:credit_limit)
+ end
+ end
+ end
+
+ test "#skip_query_cache! for a simple calculation" do
+ Account.cache do
+ assert_queries(1) do
+ Account.calculate(:sum, :credit_limit)
+ Account.calculate(:sum, :credit_limit)
+ end
+
+ assert_queries(2) do
+ Account.all.skip_query_cache!.calculate(:sum, :credit_limit)
+ Account.all.skip_query_cache!.calculate(:sum, :credit_limit)
+ end
+ end
+ end
+
+ test "#skip_query_cache! for a grouped calculation" do
+ Account.cache do
+ assert_queries(1) do
+ Account.group(:firm_id).calculate(:sum, :credit_limit)
+ Account.group(:firm_id).calculate(:sum, :credit_limit)
+ end
+
+ assert_queries(2) do
+ Account.all.skip_query_cache!.group(:firm_id).calculate(:sum, :credit_limit)
+ Account.all.skip_query_cache!.group(:firm_id).calculate(:sum, :credit_limit)
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 7e88c9cf7a..00a0187b57 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -499,21 +499,8 @@ module ActiveRecord
if failed
second_thread_done.set
- puts
- puts ">>> test_disconnect_and_clear_reloadable_connections_are_able_to_preempt_other_waiting_threads / #{group_action_method}"
- p [first_thread, second_thread]
- p pool.stat
- p pool.connections.map(&:owner)
-
first_thread.join(2)
second_thread.join(2)
-
- puts "---"
- p [first_thread, second_thread]
- p pool.stat
- p pool.connections.map(&:owner)
- puts "<<<"
- puts
end
first_thread.join(10) || raise("first_thread got stuck")
diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb
index 3735572898..936f26ce04 100644
--- a/activerecord/test/cases/core_test.rb
+++ b/activerecord/test/cases/core_test.rb
@@ -35,7 +35,7 @@ class CoreTest < ActiveRecord::TestCase
def test_pretty_print_new
topic = Topic.new
- actual = ""
+ actual = "".dup
PP.pp(topic, StringIO.new(actual))
expected = <<-PRETTY.strip_heredoc
#<Topic:0xXXXXXX
@@ -64,7 +64,7 @@ class CoreTest < ActiveRecord::TestCase
def test_pretty_print_persisted
topic = topics(:first)
- actual = ""
+ actual = "".dup
PP.pp(topic, StringIO.new(actual))
expected = <<-PRETTY.strip_heredoc
#<Topic:0x\\w+
@@ -92,7 +92,7 @@ class CoreTest < ActiveRecord::TestCase
def test_pretty_print_uninitialized
topic = Topic.allocate
- actual = ""
+ actual = "".dup
PP.pp(topic, StringIO.new(actual))
expected = "#<Topic:XXXXXX not initialized>\n"
assert actual.start_with?(expected.split("XXXXXX").first)
@@ -105,7 +105,7 @@ class CoreTest < ActiveRecord::TestCase
"inspecting topic"
end
end
- actual = ""
+ actual = "".dup
PP.pp(subtopic.new, StringIO.new(actual))
assert_equal "inspecting topic\n", actual
end
diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb
index ad7da9de70..6cd98fe254 100644
--- a/activerecord/test/cases/date_time_test.rb
+++ b/activerecord/test/cases/date_time_test.rb
@@ -58,4 +58,17 @@ class DateTimeTest < ActiveRecord::TestCase
assert_equal now, task.starting
end
end
+
+ def test_date_time_with_string_value_with_subsecond_precision
+ skip unless subsecond_precision_supported?
+ string_value = "2017-07-04 14:19:00.5"
+ topic = Topic.create(written_on: string_value)
+ assert_equal topic, Topic.find_by(written_on: string_value)
+ end
+
+ def test_date_time_with_string_value_with_non_iso_format
+ string_value = "04/07/2017 2:19pm"
+ topic = Topic.create(written_on: string_value)
+ assert_equal topic, Topic.find_by(written_on: string_value)
+ end
end
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index db3da53487..4ef9a125e6 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -60,6 +60,7 @@ class EnumTest < ActiveRecord::TestCase
assert_not_equal @book, Book.where(status: [:written]).first
assert_not_equal @book, Book.where.not(status: :published).first
assert_equal @book, Book.where.not(status: :written).first
+ assert_equal books(:ddd), Book.where(read_status: :forgotten).first
end
test "find via where with strings" do
@@ -69,6 +70,7 @@ class EnumTest < ActiveRecord::TestCase
assert_not_equal @book, Book.where(status: ["written"]).first
assert_not_equal @book, Book.where.not(status: "published").first
assert_equal @book, Book.where.not(status: "written").first
+ assert_equal books(:ddd), Book.where(read_status: "forgotten").first
end
test "build from scope" do
diff --git a/activerecord/test/cases/errors_test.rb b/activerecord/test/cases/errors_test.rb
index 73feb831d0..e90669e0c7 100644
--- a/activerecord/test/cases/errors_test.rb
+++ b/activerecord/test/cases/errors_test.rb
@@ -1,4 +1,4 @@
-require_relative "../cases/helper"
+require "cases/helper"
class ErrorsTest < ActiveRecord::TestCase
def test_can_be_instantiated_with_no_args
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 4837a169fa..af21cd529f 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -1023,16 +1023,6 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
end
- def test_find_all_with_join
- developers_on_project_one = Developer.
- joins("LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id").
- where("project_id=1").to_a
- assert_equal 3, developers_on_project_one.length
- developer_names = developers_on_project_one.map(&:name)
- assert_includes developer_names, "David"
- assert_includes developer_names, "Jamis"
- end
-
def test_joins_dont_clobber_id
first = Firm.
joins("INNER JOIN companies clients ON clients.firm_id = companies.id").
@@ -1242,6 +1232,34 @@ class FinderTest < ActiveRecord::TestCase
assert_equal tyre2, zyke.tyres.custom_find_by(id: tyre2.id)
end
+ test "#skip_query_cache! for #exists?" do
+ Topic.cache do
+ assert_queries(1) do
+ Topic.exists?
+ Topic.exists?
+ end
+
+ assert_queries(2) do
+ Topic.all.skip_query_cache!.exists?
+ Topic.all.skip_query_cache!.exists?
+ end
+ end
+ end
+
+ test "#skip_query_cache! for #exists? with a limited eager load" do
+ Topic.cache do
+ assert_queries(2) do
+ Topic.eager_load(:replies).limit(1).exists?
+ Topic.eager_load(:replies).limit(1).exists?
+ end
+
+ assert_queries(4) do
+ Topic.eager_load(:replies).limit(1).skip_query_cache!.exists?
+ Topic.eager_load(:replies).limit(1).skip_query_cache!.exists?
+ end
+ end
+ end
+
private
def table_with_custom_primary_key
yield(Class.new(Toy) do
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index e651f63c7e..f2fe724095 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -54,6 +54,31 @@ class FixturesTest < ActiveRecord::TestCase
end
end
+ class InsertQuerySubscriber
+ attr_reader :events
+
+ def initialize
+ @events = []
+ end
+
+ def call(_, _, _, _, values)
+ @events << values[:sql] if values[:sql] =~ /INSERT/
+ end
+ end
+
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
+ def test_bulk_insert
+ begin
+ subscriber = InsertQuerySubscriber.new
+ subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber)
+ create_fixtures("bulbs")
+ assert_equal 1, subscriber.events.size, "It takes one INSERT query to insert two fixtures"
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscription)
+ end
+ end
+ end
+
def test_broken_yaml_exception
badyaml = Tempfile.new ["foo", ".yml"]
badyaml.write "a: : "
@@ -248,7 +273,12 @@ class FixturesTest < ActiveRecord::TestCase
e = assert_raise(ActiveRecord::Fixture::FixtureError) do
ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/naked/yml", "parrots")
end
- assert_equal(%(table "parrots" has no column named "arrr".), e.message)
+
+ if current_adapter?(:SQLite3Adapter)
+ assert_equal(%(table "parrots" has no column named "arrr".), e.message)
+ else
+ assert_equal(%(table "parrots" has no columns named "arrr", "foobar".), e.message)
+ end
end
def test_yaml_file_with_symbol_columns
diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb
index 5aa41b9d6d..9a1c1c3f3f 100644
--- a/activerecord/test/cases/json_shared_test_cases.rb
+++ b/activerecord/test/cases/json_shared_test_cases.rb
@@ -169,6 +169,25 @@ module JSONSharedTestCases
assert_not json.changed?
end
+ def test_changes_in_place_ignores_key_order
+ json = klass.new
+ assert_not json.changed?
+
+ json.payload = { "three" => "four", "one" => "two" }
+ json.save!
+ json.reload
+
+ json.payload = { "three" => "four", "one" => "two" }
+ assert_not json.changed?
+
+ json.payload = [{ "three" => "four", "one" => "two" }, { "seven" => "eight", "five" => "six" }]
+ json.save!
+ json.reload
+
+ json.payload = [{ "three" => "four", "one" => "two" }, { "seven" => "eight", "five" => "six" }]
+ assert_not json.changed?
+ end
+
def test_changes_in_place_with_ruby_object
time = Time.now.utc
json = klass.create!(payload: time)
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 3a3b8e51f9..2fc52393f2 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -167,6 +167,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 0, p1.lock_version
end
+ def test_lock_new_when_explicitly_passing_value
+ p1 = Person.new(first_name: "Douglas Adams", lock_version: 42)
+ p1.save!
+ assert_equal 42, p1.lock_version
+ end
+
def test_touch_existing_lock
p1 = Person.find(1)
assert_equal 0, p1.lock_version
@@ -186,6 +192,19 @@ class OptimisticLockingTest < ActiveRecord::TestCase
end
end
+ def test_explicit_update_lock_column_raise_error
+ person = Person.find(1)
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ person.first_name = "Douglas Adams"
+ person.lock_version = 42
+
+ assert person.lock_version_changed?
+
+ person.save
+ end
+ end
+
def test_lock_column_name_existing
t1 = LegacyThing.find(1)
t2 = LegacyThing.find(1)
@@ -225,10 +244,33 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 0, t1.lock_version_before_type_cast
end
+ def test_touch_existing_lock_without_default_should_work_with_null_in_the_database
+ ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults(title) VALUES('title1')")
+ t1 = LockWithoutDefault.last
+
+ assert_equal 0, t1.lock_version
+ assert_nil t1.lock_version_before_type_cast
+
+ t1.touch
+
+ assert_equal 1, t1.lock_version
+ end
+
+ def test_touch_stale_object_with_lock_without_default
+ t1 = LockWithoutDefault.create!(title: "title1")
+ stale_object = LockWithoutDefault.find(t1.id)
+
+ t1.update!(title: "title2")
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ stale_object.touch
+ end
+ end
+
def test_lock_without_default_should_work_with_null_in_the_database
ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults(title) VALUES('title1')")
t1 = LockWithoutDefault.last
- t2 = LockWithoutDefault.last
+ t2 = LockWithoutDefault.find(t1.id)
assert_equal 0, t1.lock_version
assert_nil t1.lock_version_before_type_cast
@@ -285,7 +327,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults_cust(title) VALUES('title1')")
t1 = LockWithCustomColumnWithoutDefault.last
- t2 = LockWithCustomColumnWithoutDefault.last
+ t2 = LockWithCustomColumnWithoutDefault.find(t1.id)
assert_equal 0, t1.custom_lock_version
assert_nil t1.custom_lock_version_before_type_cast
@@ -434,6 +476,31 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
PersonalLegacyThing.reset_column_information
end
+ def test_destroy_existing_object_with_locking_column_value_null_in_the_database
+ ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults(title) VALUES('title1')")
+ t1 = LockWithoutDefault.last
+
+ assert_equal 0, t1.lock_version
+ assert_nil t1.lock_version_before_type_cast
+
+ t1.destroy
+
+ assert t1.destroyed?
+ end
+
+ def test_destroy_stale_object
+ t1 = LockWithoutDefault.create!(title: "title1")
+ stale_object = LockWithoutDefault.find(t1.id)
+
+ t1.update!(title: "title2")
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ stale_object.destroy!
+ end
+
+ refute stale_object.destroyed?
+ end
+
private
def add_counter_column_to(model, col = "test_count")
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 7a80bfb899..596a21dcbc 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -90,6 +90,21 @@ module ActiveRecord
connection.drop_table :more_testings rescue nil
end
+ def test_timestamps_have_null_constraints_if_not_present_in_migration_of_change_table
+ migration = Class.new(ActiveRecord::Migration[4.2]) {
+ def migrate(x)
+ change_table :testings do |t|
+ t.timestamps
+ end
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert connection.columns(:testings).find { |c| c.name == "created_at" }.null
+ assert connection.columns(:testings).find { |c| c.name == "updated_at" }.null
+ end
+
def test_timestamps_have_null_constraints_if_not_present_in_migration_for_adding_timestamps_to_existing_table
migration = Class.new(ActiveRecord::Migration[4.2]) {
def migrate(x)
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index f1ddac1ee2..718b9a0613 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -139,6 +139,16 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
end
+ test "removing column removes foreign key" do
+ @connection.create_table :testings do |t|
+ t.references :testing_parent, index: true, foreign_key: true
+ end
+
+ assert_difference "@connection.foreign_keys('testings').size", -1 do
+ @connection.remove_column :testings, :testing_parent_id
+ end
+ end
+
test "foreign key methods respect pluralize_table_names" do
begin
original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 57f94950f9..eff6e09eb7 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -502,11 +502,10 @@ class MigrationTest < ActiveRecord::TestCase
unless mysql_enforcing_gtid_consistency?
def test_create_table_with_query
- Person.connection.create_table(:person, force: true)
-
- Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM person"
+ Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM people WHERE id = 1"
columns = Person.connection.columns(:table_from_query_testings)
+ assert_equal [1], Person.connection.select_values("SELECT * FROM table_from_query_testings")
assert_equal 1, columns.length
assert_equal "id", columns.first.name
ensure
@@ -514,11 +513,10 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_create_table_with_query_from_relation
- Person.connection.create_table(:person, force: true)
-
- Person.connection.create_table :table_from_query_testings, as: Person.select(:id)
+ Person.connection.create_table :table_from_query_testings, as: Person.select(:id).where(id: 1)
columns = Person.connection.columns(:table_from_query_testings)
+ assert_equal [1], Person.connection.select_values("SELECT * FROM table_from_query_testings")
assert_equal 1, columns.length
assert_equal "id", columns.first.name
ensure
@@ -1017,8 +1015,8 @@ class CopyMigrationsTest < ActiveRecord::TestCase
assert File.exist?(@migrations_path + "/4_currencies_have_symbols.bukkits.rb")
assert_equal [@migrations_path + "/4_currencies_have_symbols.bukkits.rb"], copied.map(&:filename)
- expected = "# coding: ISO-8859-15\n# This migration comes from bukkits (originally 1)"
- assert_equal expected, IO.readlines(@migrations_path + "/4_currencies_have_symbols.bukkits.rb")[0..1].join.chomp
+ expected = "# frozen_string_literal: true\n# coding: ISO-8859-15\n# This migration comes from bukkits (originally 1)"
+ assert_equal expected, IO.readlines(@migrations_path + "/4_currencies_have_symbols.bukkits.rb")[0..2].join.chomp
files_count = Dir[@migrations_path + "/*.rb"].length
copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/magic")
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 5a62cbd3a6..154faa56aa 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -117,7 +117,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
def test_reject_if_with_a_proc_which_returns_true_always_for_has_one
Pirate.accepts_nested_attributes_for :ship, reject_if: proc { |attributes| true }
- pirate = Pirate.new(catchphrase: "Stop wastin' me time")
+ pirate = Pirate.create(catchphrase: "Stop wastin' me time")
ship = pirate.create_ship(name: "s1")
pirate.update(ship_attributes: { name: "s2", id: ship.id })
assert_equal "s1", ship.reload.name
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 80cbfdba06..31892cfff0 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -46,7 +46,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
topic = Topic.new
topic.title = "New Topic"
assert_nil topic.id
- assert_nothing_raised { topic.save! }
+ topic.save!
id = topic.id
topicReloaded = Topic.find(id)
@@ -56,23 +56,36 @@ class PrimaryKeysTest < ActiveRecord::TestCase
def test_customized_primary_key_auto_assigns_on_save
Keyboard.delete_all
keyboard = Keyboard.new(name: "HHKB")
- assert_nothing_raised { keyboard.save! }
+ keyboard.save!
assert_equal keyboard.id, Keyboard.find_by_name("HHKB").id
end
def test_customized_primary_key_can_be_get_before_saving
keyboard = Keyboard.new
assert_nil keyboard.id
- assert_nothing_raised { assert_nil keyboard.key_number }
+ assert_nil keyboard.key_number
end
def test_customized_string_primary_key_settable_before_save
subscriber = Subscriber.new
- assert_nothing_raised { subscriber.id = "webster123" }
+ subscriber.id = "webster123"
assert_equal "webster123", subscriber.id
assert_equal "webster123", subscriber.nick
end
+ def test_update_with_non_primary_key_id_column
+ subscriber = Subscriber.first
+ subscriber.update(update_count: 1)
+ subscriber.reload
+ assert_equal 1, subscriber.update_count
+ end
+
+ def test_update_columns_with_non_primary_key_id_column
+ subscriber = Subscriber.first
+ subscriber.update_columns(id: 1)
+ assert_not_equal 1, subscriber.nick
+ end
+
def test_string_key
subscriber = Subscriber.find(subscribers(:first).nick)
assert_equal(subscribers(:first).name, subscriber.name)
@@ -83,7 +96,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
subscriber.id = "jdoe"
assert_equal("jdoe", subscriber.id)
subscriber.name = "John Doe"
- assert_nothing_raised { subscriber.save! }
+ subscriber.save!
assert_equal("jdoe", subscriber.id)
subscriberReloaded = Subscriber.find("jdoe")
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 17b5304cc4..54b65d1a86 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -42,7 +42,8 @@ class QueryCacheTest < ActiveRecord::TestCase
mw = middleware { |env|
Task.find 1
Task.find 1
- assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ query_cache = ActiveRecord::Base.connection.query_cache
+ assert_equal 1, query_cache.length, query_cache.keys
raise "lol borked"
}
assert_raises(RuntimeError) { mw.call({}) }
@@ -149,7 +150,8 @@ class QueryCacheTest < ActiveRecord::TestCase
mw = middleware { |env|
Task.find 1
Task.find 1
- assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ query_cache = ActiveRecord::Base.connection.query_cache
+ assert_equal 1, query_cache.length, query_cache.keys
[200, {}, nil]
}
mw.call({})
@@ -204,6 +206,52 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
+ def test_exists_queries_with_cache
+ Post.cache do
+ assert_queries(1) { Post.exists?; Post.exists? }
+ end
+ end
+
+ def test_select_all_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_all(Post.all) }
+ end
+ end
+ end
+
+ def test_select_one_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_one(Post.all) }
+ end
+ end
+ end
+
+ def test_select_value_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_value(Post.all) }
+ end
+ end
+ end
+
+ def test_select_values_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_values(Post.all) }
+ end
+ end
+ end
+
+ def test_select_rows_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_rows(Post.all) }
+ end
+ end
+ end
+
def test_query_cache_dups_results_correctly
Task.cache do
now = Time.now.utc
@@ -274,18 +322,8 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
- def test_cache_is_available_when_connection_is_connected
- conf = ActiveRecord::Base.configurations
-
- ActiveRecord::Base.configurations = {}
- Task.cache do
- assert_queries(1) { Task.find(1); Task.find(1) }
- end
- ensure
- ActiveRecord::Base.configurations = conf
- end
-
- def test_cache_is_not_available_when_using_a_not_connected_connection
+ def test_cache_is_available_when_using_a_not_connected_connection
+ skip "In-Memory DB can't test for using a not connected connection" if in_memory_db?
with_temporary_connection_pool do
spec_name = Task.connection_specification_name
conf = ActiveRecord::Base.configurations["arunit"].merge("name" => "test2")
@@ -295,15 +333,7 @@ class QueryCacheTest < ActiveRecord::TestCase
Task.cache do
begin
- if in_memory_db?
- Task.connection.create_table :tasks do |t|
- t.datetime :starting
- t.datetime :ending
- end
- ActiveRecord::FixtureSet.create_fixtures(self.class.fixture_path, ["tasks"], {}, ActiveRecord::Base)
- end
- Task.connection # warmup postgresql connection setup queries
- assert_queries(2) { Task.find(1); Task.find(1) }
+ assert_queries(1) { Task.find(1); Task.find(1) }
ensure
ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name)
Task.connection_specification_name = spec_name
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index cb6e4d76d3..3b15f051b2 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -21,8 +21,22 @@ module ActiveRecord
end
end
+ module DeprecatedArelDelegationTests
+ AREL_METHODS = [
+ :with, :orders, :froms, :project, :projections, :taken, :constraints, :exists, :locked, :where_sql,
+ :ast, :source, :join_sources, :to_dot, :bind_values, :create_insert, :create_true, :create_false
+ ]
+
+ def test_deprecate_arel_delegation
+ AREL_METHODS.each do |method|
+ assert_deprecated { target.public_send(method) }
+ end
+ end
+ end
+
class DelegationAssociationTest < ActiveRecord::TestCase
include DelegationWhitelistTests
+ include DeprecatedArelDelegationTests
fixtures :posts
@@ -33,6 +47,7 @@ module ActiveRecord
class DelegationRelationTest < ActiveRecord::TestCase
include DelegationWhitelistTests
+ include DeprecatedArelDelegationTests
fixtures :comments
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index c3b39a9295..3901824aac 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -143,7 +143,7 @@ class MergingDifferentRelationsTest < ActiveRecord::TestCase
assert_equal ["Mary", "Mary", "Mary", "David"], posts_by_author_name
end
- test "relation merging (using a proc argument)" do
+ test "relation merging (using a proc argument)" do
dev = Developer.where(name: "Jamis").first
comment_1 = dev.comments.create!(body: "I'm Jamis", post: Post.first)
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index dea787c07f..8e73baa70a 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -90,7 +90,7 @@ module ActiveRecord
assert_equal [], relation.extending_values
end
- (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with, :skip_query_cache]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal :foo, relation.public_send("#{method}_value")
@@ -162,5 +162,10 @@ module ActiveRecord
relation.distinct! :foo
assert_equal :foo, relation.distinct_value
end
+
+ test "skip_query_cache!" do
+ relation.skip_query_cache!
+ assert relation.skip_query_cache_value
+ end
end
end
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
index abb7ca72dd..61b6601580 100644
--- a/activerecord/test/cases/relation/or_test.rb
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -59,6 +59,31 @@ module ActiveRecord
assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message
end
+ def test_or_with_unscope_where
+ expected = Post.where("id = 1 or id = 2")
+ partial = Post.where("id = 1 and id != 2")
+ assert_equal expected, partial.or(partial.unscope(:where).where("id = 2")).to_a
+ end
+
+ def test_or_with_unscope_where_column
+ expected = Post.where("id = 1 or id = 2")
+ partial = Post.where(id: 1).where.not(id: 2)
+ assert_equal expected, partial.or(partial.unscope(where: :id).where("id = 2")).to_a
+ end
+
+ def test_or_with_unscope_order
+ expected = Post.where("id = 1 or id = 2")
+ assert_equal expected, Post.order("body asc").where("id = 1").unscope(:order).or(Post.where("id = 2")).to_a
+ end
+
+ def test_or_with_incompatible_unscope
+ error = assert_raises ArgumentError do
+ Post.order("body asc").where("id = 1").or(Post.order("body asc").where("id = 2").unscope(:order)).to_a
+ end
+
+ assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message
+ end
+
def test_or_when_grouping
groups = Post.where("id < 10").group("body").select("body, COUNT(*) AS c")
expected = groups.having("COUNT(*) > 1 OR body like 'Such%'").to_a.map { |o| [o.body, o.c] }
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index cbc466d6b8..42dae4d569 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -15,7 +15,7 @@ require "models/vertex"
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
- fixtures :posts, :edges, :authors, :author_addresses, :binaries, :essays, :cars, :treasures, :price_estimates
+ fixtures :posts, :edges, :authors, :author_addresses, :binaries, :essays, :cars, :treasures, :price_estimates, :topics
def test_where_copies_bind_params
author = authors(:david)
@@ -48,6 +48,10 @@ module ActiveRecord
assert_equal [chef], chefs.to_a
end
+ def test_where_with_casted_value_is_nil
+ assert_equal 4, Topic.where(last_read: "").count
+ end
+
def test_rewhere_on_root
assert_equal posts(:welcome), Post.rewhere(title: "Welcome to the weblog").first
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index e31e21e765..e4f68eef1b 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -7,7 +7,7 @@ require "models/rating"
module ActiveRecord
class RelationTest < ActiveRecord::TestCase
- fixtures :posts, :comments, :authors, :author_addresses
+ fixtures :posts, :comments, :authors, :author_addresses, :ratings
FakeKlass = Struct.new(:table_name, :name) do
extend ActiveRecord::Delegation::DelegateCache
@@ -225,7 +225,26 @@ module ActiveRecord
def test_relation_merging_with_merged_joins_as_symbols
special_comments_with_ratings = SpecialComment.joins(:ratings)
posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
- assert_equal({ 2 => 1, 4 => 3, 5 => 1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
+ assert_equal({ 4 => 2 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
+ end
+
+ def test_relation_merging_with_merged_symbol_joins_keeps_inner_joins
+ queries = capture_sql { Author.joins(:posts).merge(Post.joins(:comments)).to_a }
+
+ nb_inner_join = queries.sum { |sql| sql.scan(/INNER\s+JOIN/i).size }
+ assert_equal 2, nb_inner_join, "Wrong amount of INNER JOIN in query"
+ assert queries.none? { |sql| /LEFT\s+(OUTER)?\s+JOIN/i.match?(sql) }, "Shouldn't have any LEFT JOIN in query"
+ end
+
+ def test_relation_merging_with_merged_symbol_joins_has_correct_size_and_count
+ # Has one entry per comment
+ merged_authors_with_commented_posts_relation = Author.joins(:posts).merge(Post.joins(:comments))
+
+ post_ids_with_author = Post.joins(:author).pluck(:id)
+ manual_comments_on_post_that_have_author = Comment.where(post_id: post_ids_with_author).pluck(:id)
+
+ assert_equal manual_comments_on_post_that_have_author.size, merged_authors_with_commented_posts_relation.count
+ assert_equal manual_comments_on_post_that_have_author.size, merged_authors_with_commented_posts_relation.to_a.size
end
def test_relation_merging_with_joins_as_join_dependency_pick_proper_parent
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 81173945a3..eb3449b331 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -695,16 +695,6 @@ class RelationTest < ActiveRecord::TestCase
end
end
- def test_default_scope_with_conditions_string
- assert_equal Developer.where(name: "David").map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort
- assert_nil DeveloperCalledDavid.create!.name
- end
-
- def test_default_scope_with_conditions_hash
- assert_equal Developer.where(name: "Jamis").map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort
- assert_equal "Jamis", DeveloperCalledJamis.create!.name
- end
-
def test_default_scoping_finder_methods
developers = DeveloperCalledDavid.order("id").map(&:id).sort
assert_equal Developer.where(name: "David").map(&:id).sort, developers
@@ -1727,6 +1717,9 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.order("comments.body")
assert_equal ["comments"], scope.references_values
+ scope = Post.order("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")
+ assert_equal ["comments"], scope.references_values
+
scope = Post.order("comments.body", "yaks.body")
assert_equal ["comments", "yaks"], scope.references_values
@@ -1745,6 +1738,9 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.reorder("comments.body")
assert_equal %w(comments), scope.references_values
+ scope = Post.reorder("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")
+ assert_equal ["comments"], scope.references_values
+
scope = Post.reorder("comments.body", "yaks.body")
assert_equal %w(comments yaks), scope.references_values
@@ -2011,6 +2007,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal binds, merged.bound_attributes
end
+ def test_locked_should_not_build_arel
+ posts = Post.locked
+ assert posts.locked?
+ assert_nothing_raised { posts.lock!(false) }
+ end
+
def test_relation_join_method
assert_equal "Thank you for the welcome,Thank you again for the welcome", Post.first.comments.join(",")
end
@@ -2032,4 +2034,46 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 2, posts.to_a.length
end
+
+ test "#skip_query_cache!" do
+ Post.cache do
+ assert_queries(1) do
+ Post.all.load
+ Post.all.load
+ end
+
+ assert_queries(2) do
+ Post.all.skip_query_cache!.load
+ Post.all.skip_query_cache!.load
+ end
+ end
+ end
+
+ test "#skip_query_cache! with an eager load" do
+ Post.cache do
+ assert_queries(1) do
+ Post.eager_load(:comments).load
+ Post.eager_load(:comments).load
+ end
+
+ assert_queries(2) do
+ Post.eager_load(:comments).skip_query_cache!.load
+ Post.eager_load(:comments).skip_query_cache!.load
+ end
+ end
+ end
+
+ test "#skip_query_cache! with a preload" do
+ Post.cache do
+ assert_queries(2) do
+ Post.preload(:comments).load
+ Post.preload(:comments).load
+ end
+
+ assert_queries(4) do
+ Post.preload(:comments).skip_query_cache!.load
+ Post.preload(:comments).skip_query_cache!.load
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/reserved_word_test.rb b/activerecord/test/cases/reserved_word_test.rb
new file mode 100644
index 0000000000..f3019a5326
--- /dev/null
+++ b/activerecord/test/cases/reserved_word_test.rb
@@ -0,0 +1,132 @@
+require "cases/helper"
+
+class ReservedWordTest < ActiveRecord::TestCase
+ self.use_instantiated_fixtures = true
+ self.use_transactional_tests = false
+
+ class Group < ActiveRecord::Base
+ Group.table_name = "group"
+ belongs_to :select
+ has_one :values
+ end
+
+ class Select < ActiveRecord::Base
+ Select.table_name = "select"
+ has_many :groups
+ end
+
+ class Values < ActiveRecord::Base
+ Values.table_name = "values"
+ end
+
+ class Distinct < ActiveRecord::Base
+ Distinct.table_name = "distinct"
+ has_and_belongs_to_many :selects
+ has_many :values, through: :groups
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :select, force: true
+ @connection.create_table :distinct, force: true
+ @connection.create_table :distinct_select, id: false, force: true do |t|
+ t.belongs_to :distinct
+ t.belongs_to :select
+ end
+ @connection.create_table :group, force: true do |t|
+ t.string :order
+ t.belongs_to :select
+ end
+ @connection.create_table :values, force: true do |t|
+ t.belongs_to :group
+ end
+ end
+
+ def teardown
+ @connection.drop_table :select, if_exists: true
+ @connection.drop_table :distinct, if_exists: true
+ @connection.drop_table :distinct_select, if_exists: true
+ @connection.drop_table :group, if_exists: true
+ @connection.drop_table :values, if_exists: true
+ @connection.drop_table :order, if_exists: true
+ end
+
+ def test_create_tables
+ assert_not @connection.table_exists?(:order)
+
+ @connection.create_table :order do |t|
+ t.string :group
+ end
+
+ assert @connection.table_exists?(:order)
+ end
+
+ def test_rename_tables
+ assert_nothing_raised { @connection.rename_table(:group, :order) }
+ end
+
+ def test_change_columns
+ assert_nothing_raised { @connection.change_column_default(:group, :order, "whatever") }
+ assert_nothing_raised { @connection.change_column("group", "order", :text, default: nil) }
+ assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
+ end
+
+ def test_introspect
+ assert_equal ["id", "order", "select_id"], @connection.columns(:group).map(&:name).sort
+ assert_equal ["index_group_on_select_id"], @connection.indexes(:group).map(&:name).sort
+ end
+
+ def test_activerecord_model
+ x = Group.new
+ x.order = "x"
+ x.save!
+ x.order = "y"
+ x.save!
+ assert_equal x, Group.find_by_order("y")
+ assert_equal x, Group.find(x.id)
+ end
+
+ def test_has_one_associations
+ create_test_fixtures :group, :values
+ v = Group.find(1).values
+ assert_equal 2, v.id
+ end
+
+ def test_belongs_to_associations
+ create_test_fixtures :select, :group
+ gs = Select.find(2).groups
+ assert_equal 2, gs.length
+ assert_equal [2, 3], gs.collect(&:id).sort
+ end
+
+ def test_has_and_belongs_to_many
+ create_test_fixtures :select, :distinct, :distinct_select
+ s = Distinct.find(1).selects
+ assert_equal 2, s.length
+ assert_equal [1, 2], s.collect(&:id).sort
+ end
+
+ def test_activerecord_introspection
+ assert Group.table_exists?
+ assert_equal ["id", "order", "select_id"], Group.columns.map(&:name).sort
+ end
+
+ def test_calculations_work_with_reserved_words
+ create_test_fixtures :group
+ assert_equal 3, Group.count
+ end
+
+ def test_associations_work_with_reserved_words
+ create_test_fixtures :select, :group
+ selects = Select.all.merge!(includes: [:groups]).to_a
+ assert_no_queries do
+ selects.each { |select| select.groups }
+ end
+ end
+
+ private
+ # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path
+ def create_test_fixtures(*fixture_names)
+ ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
+ end
+end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 417c6f4832..4c81e825fa 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -17,6 +17,12 @@ class SchemaDumperTest < ActiveRecord::TestCase
dump_all_table_schema []
end
+ def test_dump_schema_information_with_empty_versions
+ ActiveRecord::SchemaMigration.delete_all
+ schema_info = ActiveRecord::Base.connection.dump_schema_information
+ assert_no_match(/INSERT INTO/, schema_info)
+ end
+
def test_dump_schema_information_outputs_lexically_ordered_versions
versions = %w{ 20100101010101 20100201010101 20100301010101 }
versions.reverse_each do |v|
@@ -320,7 +326,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added
output = standard_dump
- assert_match %r{create_table "subscribers", id: false}, output
+ assert_match %r{create_table "string_key_objects", id: false}, output
end
if ActiveRecord::Base.connection.supports_foreign_keys?
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 8535be8402..10553bf057 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -229,6 +229,15 @@ class RelationScopingTest < ActiveRecord::TestCase
end
end
+ def test_scoping_is_correctly_restored
+ Comment.unscoped do
+ SpecialComment.unscoped.created
+ end
+
+ assert_nil Comment.current_scope
+ assert_nil SpecialComment.current_scope
+ end
+
def test_circular_joins_with_scoping_does_not_crash
posts = Post.joins(comments: :post).scoping do
Post.first(10)
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index c22d974536..9c6fb14376 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -296,7 +296,7 @@ if current_adapter?(:Mysql2Adapter)
def test_structure_dump_with_extra_flags
filename = "awesome-file.sql"
- expected_command = ["mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "--noop", "test-db"]
+ expected_command = ["mysqldump", "--noop", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db"]
assert_called_with(Kernel, :system, expected_command, returns: true) do
with_structure_dump_flags(["--noop"]) do
@@ -364,7 +364,7 @@ if current_adapter?(:Mysql2Adapter)
def test_structure_load
filename = "awesome-file.sql"
- expected_command = ["mysql", "--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db", "--noop"]
+ expected_command = ["mysql", "--noop", "--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db"]
assert_called_with(Kernel, :system, expected_command, returns: true) do
with_structure_load_flags(["--noop"]) do
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 5c6d78b574..76a997ba8b 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -304,6 +304,76 @@ class TransactionTest < ActiveRecord::TestCase
assert !Topic.find(2).approved?, "Second should have been unapproved"
end
+ def test_nested_transaction_with_new_transaction_applies_parent_state_on_rollback
+ topic_one = Topic.new(title: "A new topic")
+ topic_two = Topic.new(title: "Another new topic")
+
+ Topic.transaction do
+ topic_one.save
+
+ Topic.transaction(requires_new: true) do
+ topic_two.save
+
+ assert_predicate topic_one, :persisted?
+ assert_predicate topic_two, :persisted?
+ end
+
+ raise ActiveRecord::Rollback
+ end
+
+ refute_predicate topic_one, :persisted?
+ refute_predicate topic_two, :persisted?
+ end
+
+ def test_nested_transaction_without_new_transaction_applies_parent_state_on_rollback
+ topic_one = Topic.new(title: "A new topic")
+ topic_two = Topic.new(title: "Another new topic")
+
+ Topic.transaction do
+ topic_one.save
+
+ Topic.transaction do
+ topic_two.save
+
+ assert_predicate topic_one, :persisted?
+ assert_predicate topic_two, :persisted?
+ end
+
+ raise ActiveRecord::Rollback
+ end
+
+ refute_predicate topic_one, :persisted?
+ refute_predicate topic_two, :persisted?
+ end
+
+ def test_double_nested_transaction_applies_parent_state_on_rollback
+ topic_one = Topic.new(title: "A new topic")
+ topic_two = Topic.new(title: "Another new topic")
+ topic_three = Topic.new(title: "Another new topic of course")
+
+ Topic.transaction do
+ topic_one.save
+
+ Topic.transaction do
+ topic_two.save
+
+ Topic.transaction do
+ topic_three.save
+ end
+ end
+
+ assert_predicate topic_one, :persisted?
+ assert_predicate topic_two, :persisted?
+ assert_predicate topic_three, :persisted?
+
+ raise ActiveRecord::Rollback
+ end
+
+ refute_predicate topic_one, :persisted?
+ refute_predicate topic_two, :persisted?
+ refute_predicate topic_three, :persisted?
+ end
+
def test_manually_rolling_back_a_transaction
Topic.transaction do
@first.approved = true
@@ -595,6 +665,52 @@ class TransactionTest < ActiveRecord::TestCase
assert_not topic.frozen?
end
+ def test_restore_id_after_rollback
+ topic = Topic.new
+
+ Topic.transaction do
+ topic.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_nil topic.id
+ end
+
+ def test_restore_custom_primary_key_after_rollback
+ movie = Movie.new(name: "foo")
+
+ Movie.transaction do
+ movie.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_nil movie.id
+ end
+
+ def test_assign_id_after_rollback
+ topic = Topic.create!
+
+ Topic.transaction do
+ topic.save!
+ raise ActiveRecord::Rollback
+ end
+
+ topic.id = nil
+ assert_nil topic.id
+ end
+
+ def test_assign_custom_primary_key_after_rollback
+ movie = Movie.create!(name: "foo")
+
+ Movie.transaction do
+ movie.save!
+ raise ActiveRecord::Rollback
+ end
+
+ movie.id = nil
+ assert_nil movie.id
+ end
+
def test_rollback_of_frozen_records
topic = Topic.create.freeze
Topic.transaction do
@@ -679,6 +795,44 @@ class TransactionTest < ActiveRecord::TestCase
assert transaction.state.committed?
end
+ def test_set_state_method_is_deprecated
+ connection = Topic.connection
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
+
+ transaction.commit
+
+ assert_deprecated do
+ transaction.state.set_state(:rolledback)
+ end
+ end
+
+ def test_mark_transaction_state_as_committed
+ connection = Topic.connection
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
+
+ transaction.rollback
+
+ assert_equal :committed, transaction.state.commit!
+ end
+
+ def test_mark_transaction_state_as_rolledback
+ connection = Topic.connection
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
+
+ transaction.commit
+
+ assert_equal :rolledback, transaction.state.rollback!
+ end
+
+ def test_mark_transaction_state_as_nil
+ connection = Topic.connection
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
+
+ transaction.commit
+
+ assert_nil transaction.state.nullify!
+ end
+
def test_transaction_rollback_with_primarykeyless_tables
connection = ActiveRecord::Base.connection
connection.create_table(:transaction_without_primary_keys, force: true, id: false) do |t|
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index bfc13d683d..dd8d2c1178 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -119,6 +119,14 @@ class YamlSerializationTest < ActiveRecord::TestCase
assert_equal author.changes, dumped.changes
end
+ def test_yaml_encoding_keeps_false_values
+ topic = Topic.first
+ topic.approved = false
+ dumped = YAML.load(YAML.dump(topic))
+
+ assert_equal false, dumped.approved
+ end
+
private
def yaml_fixture(file_name)
diff --git a/activerecord/test/fixtures/naked/yml/parrots.yml b/activerecord/test/fixtures/naked/yml/parrots.yml
index 3e10331105..76f66e01ae 100644
--- a/activerecord/test/fixtures/naked/yml/parrots.yml
+++ b/activerecord/test/fixtures/naked/yml/parrots.yml
@@ -1,2 +1,3 @@
george:
arrr: "Curious George"
+ foobar: Foobar
diff --git a/activerecord/test/migrations/magic/1_currencies_have_symbols.rb b/activerecord/test/migrations/magic/1_currencies_have_symbols.rb
index d4b0e6cd95..2ba2875751 100644
--- a/activerecord/test/migrations/magic/1_currencies_have_symbols.rb
+++ b/activerecord/test/migrations/magic/1_currencies_have_symbols.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
# coding: ISO-8859-15
class CurrenciesHaveSymbols < ActiveRecord::Migration::Current
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index 49d7b24a3b..3d441b1d48 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -8,6 +8,8 @@ class Club < ActiveRecord::Base
has_many :favourites, -> { where(memberships: { favourite: true }) }, through: :memberships, source: :member
+ scope :general, -> { left_joins(:category).where(categories: { name: "General" }) }
+
private
def private_method
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index d227f6fe86..eecf923046 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -9,7 +9,6 @@ class Comment < ActiveRecord::Base
belongs_to :post, counter_cache: true
belongs_to :author, polymorphic: true
belongs_to :resource, polymorphic: true
- belongs_to :developer
has_many :ratings
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index 36f2461b84..b9597c6b9a 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -22,6 +22,7 @@ class Member < ActiveRecord::Base
has_many :organization_member_details_2, through: :organization, source: :member_details
has_one :club_category, through: :club, source: :category
+ has_one :general_club, -> { general }, through: :current_membership, source: :club
has_many :current_memberships, -> { where favourite: true }
has_many :clubs, through: :current_memberships
diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb
index 2c3ad230a7..0f8be0ad85 100644
--- a/activerecord/test/models/membership.rb
+++ b/activerecord/test/models/membership.rb
@@ -1,4 +1,5 @@
class Membership < ActiveRecord::Base
+ enum type: %i(Membership CurrentMembership SuperMembership SelectedMembership TenantMembership)
belongs_to :member
belongs_to :club
end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 4c913b3b72..ed64e0ee52 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -22,6 +22,7 @@ class Post < ActiveRecord::Base
scope :ranked_by_comments, -> { order("comments_count DESC") }
scope :limit_by, lambda { |l| limit(l) }
+ scope :locked, -> { lock }
belongs_to :author
belongs_to :readonly_author, -> { readonly }, class_name: "Author", foreign_key: :author_id
diff --git a/activerecord/test/models/subject.rb b/activerecord/test/models/subject.rb
deleted file mode 100644
index 504f68a296..0000000000
--- a/activerecord/test/models/subject.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# used for OracleSynonymTest, see test/synonym_test_oracle.rb
-#
-class Subject < ActiveRecord::Base
- # added initialization of author_email_address in the same way as in Topic class
- # as otherwise synonym test was failing
- after_initialize :set_email_address
-
- private
- def set_email_address
- unless persisted?
- self.author_email_address = "test@test.com"
- end
- end
-end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 50f1d9bfe7..f534e9c00e 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -107,7 +107,7 @@ ActiveRecord::Schema.define do
t.boolean :has_fun, null: false, default: false
end
- create_table :bulbs, force: true do |t|
+ create_table :bulbs, primary_key: "ID", force: true do |t|
t.integer :car_id
t.string :name
t.boolean :frickinawesome, default: false
@@ -453,11 +453,13 @@ ActiveRecord::Schema.define do
create_table :lock_without_defaults, force: true do |t|
t.column :title, :string
t.column :lock_version, :integer
+ t.timestamps null: true
end
create_table :lock_without_defaults_cust, force: true do |t|
t.column :title, :string
t.column :custom_lock_version, :integer
+ t.timestamps null: true
end
create_table :magazines, force: true do |t|
@@ -489,7 +491,7 @@ ActiveRecord::Schema.define do
t.datetime :joined_on
t.integer :club_id, :member_id
t.boolean :favourite, default: false
- t.string :type
+ t.integer :type
end
create_table :member_types, force: true do |t|
@@ -807,16 +809,19 @@ ActiveRecord::Schema.define do
t.string :sponsorable_type
end
- create_table :string_key_objects, id: false, primary_key: :id, force: true do |t|
- t.string :id
- t.string :name
- t.integer :lock_version, null: false, default: 0
+ create_table :string_key_objects, id: false, force: true do |t|
+ t.string :id, null: false
+ t.string :name
+ t.integer :lock_version, null: false, default: 0
+ t.index :id, unique: true
end
- create_table :subscribers, force: true, id: false do |t|
+ create_table :subscribers, id: false, force: true do |t|
t.string :nick, null: false
t.string :name
- t.column :books_count, :integer, null: false, default: 0
+ t.integer :id
+ t.integer :books_count, null: false, default: 0
+ t.integer :update_count, null: false, default: 0
t.index :nick, unique: true
end