aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md96
-rw-r--r--activerecord/Rakefile5
-rw-r--r--activerecord/lib/active_record/aggregations.rb9
-rw-r--r--activerecord/lib/active_record/associations.rb34
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb6
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb35
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb14
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb6
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb10
-rw-r--r--activerecord/lib/active_record/attribute.rb36
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb26
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb15
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb2
-rw-r--r--activerecord/lib/active_record/attribute_set.rb3
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb28
-rw-r--r--activerecord/lib/active_record/attribute_set/yaml_encoder.rb39
-rw-r--r--activerecord/lib/active_record/attributes.rb6
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb94
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb66
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb40
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb63
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/column.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/quoting.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_handling.rb29
-rw-r--r--activerecord/lib/active_record/core.rb38
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb4
-rw-r--r--activerecord/lib/active_record/enum.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb2
-rw-r--r--activerecord/lib/active_record/explain.rb29
-rw-r--r--activerecord/lib/active_record/fixtures.rb25
-rw-r--r--activerecord/lib/active_record/inheritance.rb3
-rw-r--r--activerecord/lib/active_record/integration.rb4
-rw-r--r--activerecord/lib/active_record/internal_metadata.rb13
-rw-r--r--activerecord/lib/active_record/legacy_yaml_adapter.rb2
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb18
-rw-r--r--activerecord/lib/active_record/migration.rb13
-rw-r--r--activerecord/lib/active_record/model_schema.rb26
-rw-r--r--activerecord/lib/active_record/null_relation.rb36
-rw-r--r--activerecord/lib/active_record/persistence.rb7
-rw-r--r--activerecord/lib/active_record/query_cache.rb14
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railtie.rb2
-rw-r--r--activerecord/lib/active_record/railties/controller_runtime.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb24
-rw-r--r--activerecord/lib/active_record/relation/batches.rb111
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb10
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb3
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb28
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb5
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb8
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/range_handler.rb8
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb17
-rw-r--r--activerecord/lib/active_record/result.rb8
-rw-r--r--activerecord/lib/active_record/runtime_registry.rb4
-rw-r--r--activerecord/lib/active_record/sanitization.rb4
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb15
-rw-r--r--activerecord/lib/active_record/statement_cache.rb17
-rw-r--r--activerecord/lib/active_record/table_metadata.rb7
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb3
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb3
-rw-r--r--activerecord/lib/active_record/touch_later.rb7
-rw-r--r--activerecord/lib/active_record/transactions.rb8
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb6
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb3
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql2/charset_collation_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb21
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb7
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb178
-rw-r--r--activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb28
-rw-r--r--activerecord/test/cases/aggregations_test.rb5
-rw-r--r--activerecord/test/cases/associations/eager_test.rb5
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb67
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb18
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb2
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb11
-rw-r--r--activerecord/test/cases/associations_test.rb12
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb322
-rw-r--r--activerecord/test/cases/attribute_test.rb7
-rw-r--r--activerecord/test/cases/attributes_test.rb44
-rw-r--r--activerecord/test/cases/base_test.rb12
-rw-r--r--activerecord/test/cases/batches_test.rb152
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb11
-rw-r--r--activerecord/test/cases/calculations_test.rb7
-rw-r--r--activerecord/test/cases/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/column_definition_test.rb7
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb5
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb10
-rw-r--r--activerecord/test/cases/connection_pool_test.rb17
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb12
-rw-r--r--activerecord/test/cases/date_test.rb (renamed from activerecord/test/cases/invalid_date_test.rb)14
-rw-r--r--activerecord/test/cases/dirty_test.rb30
-rw-r--r--activerecord/test/cases/enum_test.rb4
-rw-r--r--activerecord/test/cases/explain_test.rb8
-rw-r--r--activerecord/test/cases/finder_test.rb6
-rw-r--r--activerecord/test/cases/fixtures_test.rb40
-rw-r--r--activerecord/test/cases/integration_test.rb30
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb15
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb9
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb13
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb2
-rw-r--r--activerecord/test/cases/migration_test.rb15
-rw-r--r--activerecord/test/cases/mixin_test.rb11
-rw-r--r--activerecord/test/cases/multiparameter_attributes_test.rb28
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb8
-rw-r--r--activerecord/test/cases/primary_keys_test.rb30
-rw-r--r--activerecord/test/cases/query_cache_test.rb31
-rw-r--r--activerecord/test/cases/quoting_test.rb16
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb4
-rw-r--r--activerecord/test/cases/result_test.rb10
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb19
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb3
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb7
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb7
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb8
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb4
-rw-r--r--activerecord/test/cases/test_case.rb7
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb6
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb10
-rw-r--r--activerecord/test/migrations/to_copy/1_people_have_hobbies.rb2
-rw-r--r--activerecord/test/migrations/to_copy/2_people_have_descriptions.rb2
-rw-r--r--activerecord/test/migrations/to_copy2/2_create_comments.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb2
-rw-r--r--activerecord/test/models/customer.rb7
-rw-r--r--activerecord/test/models/topic.rb6
-rw-r--r--activerecord/test/models/user.rb6
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb2
-rw-r--r--activerecord/test/schema/schema.rb20
151 files changed, 1780 insertions, 1032 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 929f241b1b..14a6c3c9f7 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,83 @@
+* Fix the SELECT statement in `#table_comment` for MySQL.
+
+ *Takeshi Akima*
+
+* Virtual attributes will no longer raise when read on models loaded from the
+ database
+
+ *Sean Griffin*
+
+* Support calling the method `merge` in `scope`'s lambda.
+
+ *Yasuhiro Sugino*
+
+* Fixes multi-parameter attributes conversion with invalid params.
+
+ *Hiroyuki Ishii*
+
+* Add newline between each migration in `structure.sql`.
+
+ Keeps schema migration inserts as a single commit, but allows for easier
+ git diffing.
+
+ Fixes #25504.
+
+ *Grey Baker*, *Norberto Lopes*
+
+* The flag `error_on_ignored_order_or_limit` has been deprecated in favor of
+ the current `error_on_ignored_order`.
+
+ *Xavier Noria*
+
+* Batch processing methods support `limit`:
+
+ Post.limit(10_000).find_each do |post|
+ # ...
+ end
+
+ It also works in `find_in_batches` and `in_batches`.
+
+ *Xavier Noria*
+
+* Using `group` with an attribute that has a custom type will properly cast
+ the hash keys after calling a calculation method like `count`. Fixes #25595.
+
+ *Sean Griffin*
+
+* Fix the generated `#to_param` method to use `omission: ''` so that
+ the resulting output is actually up to 20 characters, not
+ effectively 17 to leave room for the default "...".
+ Also call `#parameterize` before `#truncate` and make the
+ `separator: /-/` to maximize the information included in the
+ output.
+
+ Fixes #23635.
+
+ *Rob Biedenharn*
+
+* Ensure concurrent invocations of the connection reaper cannot allocate the
+ same connection to two threads.
+
+ Fixes #25585.
+
+ *Matthew Draper*
+
+* Inspecting an object with an associated array of over 10 elements no longer
+ truncates the array, preventing `inspect` from looping infinitely in some
+ cases.
+
+ *Kevin McPhillips*
+
+* Removed the unused methods `ActiveRecord::Base.connection_id` and
+ `ActiveRecord::Base.connection_id=`.
+
+ *Sean Griffin*
+
+* Ensure hashes can be assigned to attributes created using `composed_of`.
+ Fixes #25210.
+
+ *Sean Griffin*
+
* Fix logging edge case where if an attribute was of the binary type and
was provided as a Hash.
@@ -8,9 +88,23 @@
*Johannes Opper*
-* Introduce ActiveRecord::TransactionSerializationError for catching
+* Introduce `ActiveRecord::TransactionSerializationError` for catching
transaction serialization failures or deadlocks.
*Erol Fornoles*
+* PostgreSQL: Fix db:structure:load silent failure on SQL error.
+
+ The command line flag `-v ON_ERROR_STOP=1` should be used
+ when invoking `psql` to make sure errors are not suppressed.
+
+ Example:
+
+ psql -v ON_ERROR_STOP=1 -q -f awesome-file.sql my-app-db
+
+ Fixes #23818.
+
+ *Ralin Chimev*
+
+
Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 46df733cfe..8fbea61335 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -21,7 +21,6 @@ desc 'Run mysql2, sqlite, and postgresql tests by default'
task :default => :test
task :package
-task "package:clean"
desc 'Run mysql2, sqlite, and postgresql tests'
task :test do
@@ -52,7 +51,7 @@ end
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
t.libs << 'test'
t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject {
- |x| x =~ /\/adapters\//
+ |x| x.include?('/adapters/')
} + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb"))
t.warning = true
@@ -65,7 +64,7 @@ end
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
puts [adapter, adapter_short].inspect
(Dir["test/cases/**/*_test.rb"].reject {
- |x| x =~ /\/adapters\//
+ |x| x.include?('/adapters/')
} + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file|
sh(Gem.ruby, '-w' ,"-Itest", file)
end or raise "Failures"
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 3ff41ed81b..8bed5bca28 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -256,15 +256,16 @@ module ActiveRecord
def writer_method(name, class_name, mapping, allow_nil, converter)
define_method("#{name}=") do |part|
klass = class_name.constantize
- if part.is_a?(Hash)
- raise ArgumentError unless part.size == part.keys.max
- part = klass.new(*part.sort.map(&:last))
- end
unless part.is_a?(klass) || converter.nil? || part.nil?
part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
end
+ if part.is_a?(Hash)
+ raise ArgumentError unless part.size == part.keys.max
+ part = klass.new(*part.sort.map(&:last))
+ end
+
if part.nil? && allow_nil
mapping.each { |key, _| self[key] = nil }
@aggregation_cache[name] = nil
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 5a973fa801..c91b5d3fe3 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -300,10 +300,10 @@ module ActiveRecord
#
# === A word of warning
#
- # Don't create associations that have the same name as instance methods of
- # ActiveRecord::Base. Since the association adds a method with that name to
- # its model, it will override the inherited method and break things.
- # For instance, +attributes+ and +connection+ would be bad choices for association names.
+ # Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveRecord::Core] of
+ # <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to
+ # its model, using an association with the same name as one provided by <tt>ActiveRecord::Base</tt> will override the method inherited through <tt>ActiveRecord::Base</tt> and will break things.
+ # For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of <tt>ActiveRecord::Base</tt> instance methods.
#
# == Auto-generated methods
# See also Instance Public methods below for more details.
@@ -362,7 +362,7 @@ module ActiveRecord
# end
# end
#
- # If your model class is <tt>Project</tt>, the module is
+ # If your model class is <tt>Project</tt>, then the module is
# named <tt>Project::GeneratedAssociationMethods</tt>. The +GeneratedAssociationMethods+ module is
# included in the model class immediately after the (anonymous) generated attributes methods
# module, meaning an association will override the methods for an attribute with the same name.
@@ -845,8 +845,8 @@ module ActiveRecord
# Post.includes(:author).each do |post|
#
# This references the name of the #belongs_to association that also used the <tt>:author</tt>
- # symbol. After loading the posts, find will collect the +author_id+ from each one and load
- # all the referenced authors with one query. Doing so will cut down the number of queries
+ # symbol. After loading the posts, +find+ will collect the +author_id+ from each one and load
+ # all of the referenced authors with one query. Doing so will cut down the number of queries
# from 201 to 102.
#
# We can improve upon the situation further by referencing both associations in the finder with:
@@ -873,7 +873,7 @@ module ActiveRecord
#
# Since only one table is loaded at a time, conditions or orders cannot reference tables
# other than the main one. If this is the case, Active Record falls back to the previously
- # used LEFT OUTER JOIN based strategy. For example:
+ # used <tt>LEFT OUTER JOIN</tt> based strategy. For example:
#
# Post.includes([:author, :comments]).where(['comments.approved = ?', true])
#
@@ -881,18 +881,18 @@ module ActiveRecord
# <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
# <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions
# like this can have unintended consequences.
- # In the above example posts with no approved comments are not returned at all, because
+ # In the above example, posts with no approved comments are not returned at all because
# the conditions apply to the SQL statement as a whole and not just to the association.
#
# You must disambiguate column references for this fallback to happen, for example
# <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not.
#
- # If you want to load all posts (including posts with no approved comments) then write
- # your own LEFT OUTER JOIN query using ON
+ # If you want to load all posts (including posts with no approved comments), then write
+ # your own <tt>LEFT OUTER JOIN</tt> query using <tt>ON</tt>:
#
# Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'")
#
- # In this case it is usually more natural to include an association which has conditions defined on it:
+ # In this case, it is usually more natural to include an association which has conditions defined on it:
#
# class Post < ActiveRecord::Base
# has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment'
@@ -924,7 +924,7 @@ module ActiveRecord
#
# This will execute one query to load the addresses and load the addressables with one
# query per addressable type.
- # For example if all the addressables are either of class Person or Company then a total
+ # For example, if all the addressables are either of class Person or Company, then a total
# of 3 queries will be executed. The list of addressable types to load is determined on
# the back of the addresses loaded. This is not supported if Active Record has to fallback
# to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
@@ -1015,7 +1015,7 @@ module ActiveRecord
#
# == Bi-directional associations
#
- # When you specify an association there is usually an association on the associated model
+ # When you specify an association, there is usually an association on the associated model
# that specifies the same relationship in reverse. For example, with the following models:
#
# class Dungeon < ActiveRecord::Base
@@ -1032,7 +1032,7 @@ module ActiveRecord
# end
#
# The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are
- # the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+
+ # the inverse of each other, and the inverse of the +dungeon+ association on +EvilWizard+
# is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
# Active Record can guess the inverse of the association based on the name
# of the class. The result is the following:
@@ -1062,7 +1062,7 @@ module ActiveRecord
#
# * does not work with <tt>:through</tt> associations.
# * does not work with <tt>:polymorphic</tt> associations.
- # * for #belongs_to associations #has_many inverse associations are ignored.
+ # * inverse associations for #belongs_to associations #has_many are ignored.
#
# For more information, see the documentation for the +:inverse_of+ option.
#
@@ -1070,7 +1070,7 @@ module ActiveRecord
#
# === Dependent associations
#
- # #has_many, #has_one and #belongs_to associations support the <tt>:dependent</tt> option.
+ # #has_many, #has_one, and #belongs_to associations support the <tt>:dependent</tt> option.
# This allows you to specify that associated records should be deleted when the owner is
# deleted.
#
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 5fbd79d118..dd5fd607a2 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -76,8 +76,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
left_model.retrieve_connection
end
- def self.primary_key
- false
+ private
+
+ def self.suppress_composite_primary_key(pk)
+ pk unless pk.is_a?(Array)
end
}
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 0eaa0a4f36..c8805d107b 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -152,9 +152,7 @@ module ActiveRecord
if loaded?
n ? target.take(n) : target.first
else
- scope.take(n).tap do |record|
- set_inverse_instance record if record.is_a? ActiveRecord::Base
- end
+ scope.take(n)
end
end
@@ -457,23 +455,20 @@ module ActiveRecord
end
private
- def get_records
- return scope.to_a if skip_statement_cache?
-
- 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)
- }
- end
-
- binds = AssociationScope.get_bind_values(owner, reflection.chain)
- sc.execute binds, klass, klass.connection
- end
def find_target
- records = get_records
+ return scope.to_a if skip_statement_cache?
+
+ 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)
+ }
+ end
+
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
+ records = sc.execute(binds, klass, conn)
records.each { |record| set_inverse_instance(record) }
records
end
@@ -656,9 +651,7 @@ module ActiveRecord
args.shift if args.first.is_a?(Hash) && args.first.empty?
collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
- collection.send(type, *args).tap do |record|
- set_inverse_instance record if record.is_a? ActiveRecord::Base
- end
+ collection.send(type, *args)
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 5d1e7ffb73..806a905323 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -29,7 +29,7 @@ module ActiveRecord
# instantiation of the actual post records.
class CollectionProxy < Relation
delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
- delegate :find_nth, to: :scope
+ delegate :exists?, :update_all, :arel, to: :scope
def initialize(klass, association) #:nodoc:
@association = association
@@ -793,7 +793,7 @@ module ActiveRecord
# Returns +true+ if the collection is empty. If the collection has been
# loaded it is equivalent
# to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
- # it is equivalent to <tt>collection.exists?</tt>. If the collection has
+ # it is equivalent to <tt>!collection.exists?</tt>. If the collection has
# not already been loaded and you are going to fetch the records anyway it
# is better to check <tt>collection.length.zero?</tt>.
#
@@ -897,10 +897,6 @@ module ActiveRecord
!!@association.include?(record)
end
- def arel #:nodoc:
- scope.arel
- end
-
def proxy_association
@association
end
@@ -1074,6 +1070,12 @@ module ActiveRecord
proxy_association.reset_scope
self
end
+
+ private
+
+ def exec_queries
+ load_target
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index a9f6aaafef..4daafedcfb 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -27,14 +27,12 @@ module ActiveRecord
throw(:abort)
end
+ when :destroy
+ # No point in executing the counter update since we're going to destroy the parent anyway
+ load_target.each { |t| t.destroyed_by_association = reflection }
+ destroy_all
else
- if options[:dependent] == :destroy
- # No point in executing the counter update since we're going to destroy the parent anyway
- load_target.each { |t| t.destroyed_by_association = reflection }
- destroy_all
- else
- delete_all
- end
+ delete_all
end
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 36fc381343..ddd0b59cc1 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -196,7 +196,7 @@ module ActiveRecord
def find_target
return [] unless target_reflection_has_associated_record?
- get_records
+ super
end
# NOTE - not sure that we can actually cope with inverses here
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index 08e0ec691f..604904abcc 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -15,7 +15,7 @@ module ActiveRecord
ensure_not_nested
through_proxy = owner.association(through_reflection.name)
- through_record = through_proxy.send(:load_target)
+ through_record = through_proxy.load_target
if through_record && !record
through_record.destroy
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index b94feeff12..491152bbcb 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -92,8 +92,9 @@ module ActiveRecord
# associations # => [:appointments]
# joins # => []
#
- def initialize(base, associations, joins)
+ def initialize(base, associations, joins, eager_loading: true)
@alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins, base.type_caster)
+ @eager_loading = eager_loading
tree = self.class.make_tree associations
@join_root = JoinBase.new base, build(tree, base)
@join_root.children.each { |child| construct_tables! @join_root, child }
@@ -238,11 +239,12 @@ module ActiveRecord
reflection.check_eager_loadable!
if reflection.polymorphic?
+ next unless @eager_loading
raise EagerLoadPolymorphicError.new(reflection)
end
JoinAssociation.new reflection, build(right, reflection.klass)
- end
+ end.compact
end
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
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 c5fbe0d1d1..bdf77009eb 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -56,7 +56,9 @@ module ActiveRecord
klass_scope =
if klass.current_scope
- klass.current_scope.clone
+ klass.current_scope.clone.tap { |scope|
+ scope.joins_values = []
+ }
else
relation = ActiveRecord::Relation.create(
klass,
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index f913f0852a..1fe9a23263 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -44,8 +44,8 @@ module ActiveRecord
scope.scope_for_create.stringify_keys.except(klass.primary_key)
end
- def get_records
- return scope.limit(1).records if skip_statement_cache?
+ def find_target
+ return scope.take if skip_statement_cache?
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
@@ -56,11 +56,7 @@ module ActiveRecord
end
binds = AssociationScope.get_bind_values(owner, reflection.chain)
- sc.execute binds, klass, klass.connection
- end
-
- def find_target
- if record = get_records.first
+ if record = sc.execute(binds, klass, conn).first
set_inverse_instance record
end
end
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 24231dc9e1..2f41e9e45b 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -77,7 +77,11 @@ module ActiveRecord
end
def with_type(type)
- self.class.new(name, value_before_type_cast, type, original_attribute)
+ if changed_in_place?
+ with_value_from_user(value).with_type(type)
+ else
+ self.class.new(name, value_before_type_cast, type, original_attribute)
+ end
end
def type_cast(*)
@@ -108,6 +112,22 @@ module ActiveRecord
[self.class, name, value_before_type_cast, type].hash
end
+ def init_with(coder)
+ @name = coder["name"]
+ @value_before_type_cast = coder["value_before_type_cast"]
+ @type = coder["type"]
+ @original_attribute = coder["original_attribute"]
+ @value = coder["value"] if coder.map.key?("value")
+ end
+
+ def encode_with(coder)
+ coder["name"] = name
+ coder["value_before_type_cast"] = value_before_type_cast if value_before_type_cast
+ coder["type"] = type if type
+ coder["original_attribute"] = original_attribute if original_attribute
+ coder["value"] = value if defined?(@value)
+ end
+
protected
attr_reader :original_attribute
@@ -132,7 +152,7 @@ module ActiveRecord
end
def _original_value_for_database
- value_for_database
+ type.serialize(original_value)
end
class FromDatabase < Attribute # :nodoc:
@@ -160,7 +180,7 @@ module ActiveRecord
value
end
- def changed_in_place_from?(old_value)
+ def changed_in_place?
false
end
end
@@ -185,6 +205,8 @@ module ActiveRecord
end
class Uninitialized < Attribute # :nodoc:
+ UNINITIALIZED_ORIGINAL_VALUE = Object.new
+
def initialize(name, type)
super(name, nil, type)
end
@@ -195,12 +217,20 @@ module ActiveRecord
end
end
+ def original_value
+ UNINITIALIZED_ORIGINAL_VALUE
+ end
+
def value_for_database
end
def initialized?
false
end
+
+ def with_type(type)
+ self.class.new(name, type)
+ end
end
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 7e19dceaed..eadd73aab0 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -209,13 +209,13 @@ module ActiveRecord
# end
#
# person = Person.new
- # person.respond_to(:name) # => true
- # person.respond_to(:name=) # => true
- # person.respond_to(:name?) # => true
- # person.respond_to('age') # => true
- # person.respond_to('age=') # => true
- # person.respond_to('age?') # => true
- # person.respond_to(:nothing) # => false
+ # person.respond_to?(:name) # => true
+ # person.respond_to?(:name=) # => true
+ # person.respond_to?(:name?) # => true
+ # person.respond_to?('age') # => true
+ # person.respond_to?('age=') # => true
+ # person.respond_to?('age?') # => true
+ # person.respond_to?(:nothing) # => false
def respond_to?(name, include_private = false)
return false unless super
@@ -279,9 +279,8 @@ module ActiveRecord
# Returns an <tt>#inspect</tt>-like string for the value of the
# attribute +attr_name+. String attributes are truncated up to 50
# characters, Date and Time attributes are returned in the
- # <tt>:db</tt> format, Array attributes are truncated up to 10 values.
- # Other attributes return the value of <tt>#inspect</tt> without
- # modification.
+ # <tt>:db</tt> format. Other attributes return the value of
+ # <tt>#inspect</tt> without modification.
#
# person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
#
@@ -292,7 +291,7 @@ module ActiveRecord
# # => "\"2012-10-22 00:15:07\""
#
# person.attribute_for_inspect(:tag_ids)
- # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"
+ # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
def attribute_for_inspect(attr_name)
value = read_attribute(attr_name)
@@ -300,9 +299,6 @@ module ActiveRecord
"#{value[0, 50]}...".inspect
elsif value.is_a?(Date) || value.is_a?(Time)
%("#{value.to_s(:db)}")
- elsif value.is_a?(Array) && value.size > 10
- inspected = value.first(10).inspect
- %(#{inspected[0...-1]}, ...])
else
value.inspect
end
@@ -334,8 +330,6 @@ module ActiveRecord
#
# Note: +:id+ is always present.
#
- # Alias for the #read_attribute method.
- #
# class Person < ActiveRecord::Base
# belongs_to :organization
# end
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 0d5cb8b37c..d28edfb003 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -95,7 +95,8 @@ module ActiveRecord
base_name.foreign_key
else
if ActiveRecord::Base != self && table_exists?
- connection.schema_cache.primary_keys(table_name)
+ pk = connection.schema_cache.primary_keys(table_name)
+ suppress_composite_primary_key(pk)
else
'id'
end
@@ -122,6 +123,18 @@ module ActiveRecord
@quoted_primary_key = nil
@attributes_builder = nil
end
+
+ private
+
+ def suppress_composite_primary_key(pk)
+ return pk unless pk.is_a?(Array)
+
+ warn <<-WARNING.strip_heredoc
+ WARNING: Active Record does not support composite primary key.
+
+ #{table_name} has composite primary key. Composite primary key is ignored.
+ WARNING
+ end
end
end
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 e160460286..cbb4f0cff8 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -39,7 +39,7 @@ module ActiveRecord
end
def set_time_zone_without_conversion(value)
- ::Time.zone.local_to_utc(value).in_time_zone
+ ::Time.zone.local_to_utc(value).in_time_zone if value
end
def map_avoiding_infinite_recursion(value)
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index be581ac2a9..f868f23845 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -1,7 +1,10 @@
require 'active_record/attribute_set/builder'
+require 'active_record/attribute_set/yaml_encoder'
module ActiveRecord
class AttributeSet # :nodoc:
+ delegate :each_value, :fetch, to: :attributes
+
def initialize(attributes)
@attributes = attributes
end
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb
index 3bd7c7997b..4ffd39d82d 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activerecord/lib/active_record/attribute_set/builder.rb
@@ -3,11 +3,12 @@ require 'active_record/attribute'
module ActiveRecord
class AttributeSet # :nodoc:
class Builder # :nodoc:
- attr_reader :types, :always_initialized
+ attr_reader :types, :always_initialized, :default
- def initialize(types, always_initialized = nil)
+ def initialize(types, always_initialized = nil, &default)
@types = types
@always_initialized = always_initialized
+ @default = default
end
def build_from_database(values = {}, additional_types = {})
@@ -15,21 +16,22 @@ module ActiveRecord
values[always_initialized] = nil
end
- attributes = LazyAttributeHash.new(types, values, additional_types)
+ attributes = LazyAttributeHash.new(types, values, additional_types, &default)
AttributeSet.new(attributes)
end
end
end
class LazyAttributeHash # :nodoc:
- delegate :transform_values, :each_key, to: :materialize
+ delegate :transform_values, :each_key, :each_value, :fetch, to: :materialize
- def initialize(types, values, additional_types)
+ def initialize(types, values, additional_types, &default)
@types = types
@values = values
@additional_types = additional_types
@materialized = false
@delegate_hash = {}
+ @default = default || proc {}
end
def key?(key)
@@ -76,9 +78,21 @@ module ActiveRecord
end
end
+ def marshal_dump
+ materialize
+ end
+
+ def marshal_load(delegate_hash)
+ @delegate_hash = delegate_hash
+ @types = {}
+ @values = {}
+ @additional_types = {}
+ @materialized = true
+ end
+
protected
- attr_reader :types, :values, :additional_types, :delegate_hash
+ attr_reader :types, :values, :additional_types, :delegate_hash, :default
def materialize
unless @materialized
@@ -101,7 +115,7 @@ module ActiveRecord
if value_present
delegate_hash[name] = Attribute.from_database(name, value, type)
elsif types.key?(name)
- delegate_hash[name] = Attribute.uninitialized(name, type)
+ delegate_hash[name] = default.call(name) || Attribute.uninitialized(name, type)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb
new file mode 100644
index 0000000000..f9d527a5a3
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb
@@ -0,0 +1,39 @@
+module ActiveRecord
+ class AttributeSet
+ # Attempts to do more intelligent YAML dumping of an
+ # ActiveRecord::AttributeSet to reduce the size of the resulting string
+ class YAMLEncoder # :nodoc:
+ def initialize(default_types)
+ @default_types = default_types
+ end
+
+ def encode(attribute_set, coder)
+ coder['concise_attributes'] = attribute_set.each_value.map do |attr|
+ if attr.type.equal?(default_types[attr.name])
+ attr.with_type(nil)
+ else
+ attr
+ end
+ end
+ end
+
+ def decode(coder)
+ if coder['attributes']
+ coder['attributes']
+ else
+ attributes_hash = Hash[coder['concise_attributes'].map do |attr|
+ if attr.type.nil?
+ attr = attr.with_type(default_types[attr.name])
+ end
+ [attr.name, attr]
+ end]
+ AttributeSet.new(attributes_hash)
+ end
+ end
+
+ protected
+
+ attr_reader :default_types
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 519de271c3..ed0302e763 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -34,10 +34,10 @@ module ActiveRecord
# is not passed, the previous default value (if any) will be used.
# Otherwise, the default will be +nil+.
#
- # +array+ (PG only) specifies that the type should be an array (see the
+ # +array+ (PostgreSQL only) specifies that the type should be an array (see the
# examples below).
#
- # +range+ (PG only) specifies that the type should be a range (see the
+ # +range+ (PostgreSQL only) specifies that the type should be a range (see the
# examples below).
#
# ==== Examples
@@ -253,7 +253,7 @@ module ActiveRecord
name,
value,
type,
- _default_attributes[name],
+ _default_attributes.fetch(name.to_s) { nil },
)
else
default_attribute = Attribute.from_database(name, value, type)
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index 2456b8ad8c..5ac1e0c001 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -1,4 +1,5 @@
require 'yaml'
+require 'active_support/core_ext/regexp'
module ActiveRecord
module Coders # :nodoc:
@@ -20,7 +21,7 @@ module ActiveRecord
def load(yaml)
return object_class.new if object_class != Object && yaml.nil?
- return yaml unless yaml.is_a?(String) && yaml =~ /^---/
+ return yaml unless yaml.is_a?(String) && /^---/.match?(yaml)
obj = YAML.load(yaml)
assert_valid_value(obj)
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 f437dafec2..526adc9efe 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -74,7 +74,7 @@ module ActiveRecord
#--
# Synchronization policy:
# * all public methods can be called outside +synchronize+
- # * access to these i-vars needs to be in +synchronize+:
+ # * access to these instance variables needs to be in +synchronize+:
# * @connections
# * @now_connecting
# * private methods that require being called in a +synchronize+ blocks
@@ -329,13 +329,13 @@ module ActiveRecord
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
- # The cache of threads mapped to reserved connections, the sole purpose
- # of the cache is to speed-up +connection+ method, it is not the authoritative
- # registry of which thread owns which connection, that is tracked by
- # +connection.owner+ attr on each +connection+ instance.
+ # This variable tracks the cache of threads mapped to reserved connections, with the
+ # sole purpose of speeding up the +connection+ method. It is not the authoritative
+ # registry of which thread owns which connection. Connection ownership is tracked by
+ # the +connection.owner+ attr on each +connection+ instance.
# The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
- # then that +thread+ does indeed own that +conn+, however an absence of a such
- # mapping does not mean that the +thread+ doesn't own the said connection, in
+ # then that +thread+ does indeed own that +conn+. However, an absence of a such
+ # mapping does not mean that the +thread+ doesn't own the said connection. In
# that case +conn.owner+ attr should be consulted.
# Access and modification of +@thread_cached_conns+ does not require
# synchronization.
@@ -364,10 +364,10 @@ module ActiveRecord
@thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
end
- # Is there an open connection that is being used for the current thread?
+ # Returns true if there is an open connection being used for the current thread.
#
# This method only works for connections that have been obtained through
- # #connection or #with_connection methods, connections obtained through
+ # #connection or #with_connection methods. Connections obtained through
# #checkout will not be detected by #active_connection?
def active_connection?
@thread_cached_conns[connection_cache_key(Thread.current)]
@@ -415,7 +415,10 @@ module ActiveRecord
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
synchronize do
@connections.each do |conn|
- checkin conn
+ if conn.in_use?
+ conn.steal!
+ checkin conn
+ end
conn.disconnect!
end
@connections = []
@@ -426,9 +429,9 @@ module ActiveRecord
# Disconnects all connections in the pool, and clears the pool.
#
- # The pool first tries to gain ownership of all connections, if unable to
+ # The pool first tries to gain ownership of all connections. If unable to
# do so within a timeout interval (default duration is
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool is forcefully
+ # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool is forcefully
# disconnected without any regard for other connection owning threads.
def disconnect!
disconnect(false)
@@ -447,7 +450,10 @@ module ActiveRecord
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
synchronize do
@connections.each do |conn|
- checkin conn
+ if conn.in_use?
+ conn.steal!
+ checkin conn
+ end
conn.disconnect! if conn.requires_reloading?
end
@connections.delete_if(&:requires_reloading?)
@@ -474,9 +480,9 @@ module ActiveRecord
# Clears the cache which maps classes and re-connects connections that
# require reloading.
#
- # The pool first tries to gain ownership of all connections, if unable to
+ # The pool first tries to gain ownership of all connections. If unable to
# do so within a timeout interval (default duration is
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool forcefully
+ # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool forcefully
# clears the cache and reloads connections without any regard for other
# connection owning threads.
def clear_reloadable_connections!
@@ -530,20 +536,20 @@ module ActiveRecord
@available.delete conn
# @available.any_waiting? => true means that prior to removing this
- # conn, the pool was at its max size (@connections.size == @size)
- # this would mean that any threads stuck waiting in the queue wouldn't
+ # conn, the pool was at its max size (@connections.size == @size).
+ # This would mean that any threads stuck waiting in the queue wouldn't
# know they could checkout_new_connection, so let's do it for them.
# Because condition-wait loop is encapsulated in the Queue class
# (that in turn is oblivious to ConnectionPool implementation), threads
- # that are "stuck" there are helpless, they have no way of creating
+ # that are "stuck" there are helpless. They have no way of creating
# new connections and are completely reliant on us feeding available
# connections into the Queue.
needs_new_connection = @available.any_waiting?
end
# This is intentionally done outside of the synchronized section as we
- # would like not to hold the main mutex while checking out new connections,
- # thus there is some chance that needs_new_connection information is now
+ # would like not to hold the main mutex while checking out new connections.
+ # Thus there is some chance that needs_new_connection information is now
# stale, we can live with that (bulk_make_new_connections will make
# sure not to exceed the pool's @size limit).
bulk_make_new_connections(1) if needs_new_connection
@@ -556,17 +562,17 @@ module ActiveRecord
stale_connections = synchronize do
@connections.select do |conn|
conn.in_use? && !conn.owner.alive?
+ end.each do |conn|
+ conn.steal!
end
end
stale_connections.each do |conn|
- synchronize do
- if conn.active?
- conn.reset!
- checkin conn
- else
- remove conn
- end
+ if conn.active?
+ conn.reset!
+ checkin conn
+ else
+ remove conn
end
end
end
@@ -698,7 +704,7 @@ module ActiveRecord
def acquire_connection(checkout_timeout)
# NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to
# +conn.lease+ the returned connection (and to do this in a +synchronized+
- # section), this is not the cleanest implementation, as ideally we would
+ # section). This is not the cleanest implementation, as ideally we would
# <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+
# and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
# of the said methods and avoid an additional +synchronize+ overhead.
@@ -822,7 +828,7 @@ module ActiveRecord
# should use.
#
# The ConnectionHandler class is not coupled with the Active models, as it has no knowlodge
- # about the model. The model, needs to pass a specification name to the handler,
+ # about the model. The model needs to pass a specification name to the handler,
# in order to lookup the correct connection pool.
class ConnectionHandler
def initialize
@@ -837,8 +843,26 @@ module ActiveRecord
end
alias :connection_pools :connection_pool_list
- def establish_connection(spec)
- owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
+ def establish_connection(config)
+ resolver = ConnectionSpecification::Resolver.new(Base.configurations)
+ spec = resolver.spec(config)
+
+ remove_connection(spec.name)
+
+ message_bus = ActiveSupport::Notifications.instrumenter
+ payload = {
+ connection_id: object_id
+ }
+ if spec
+ payload[:spec_name] = spec.name
+ payload[:config] = spec.config
+ end
+
+ message_bus.instrument('!connection.active_record', payload) do
+ owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
+ end
+
+ owner_to_pool[spec.name]
end
# Returns true if there are any active connections among the connection
@@ -871,9 +895,9 @@ module ActiveRecord
# for (not necessarily the current class).
def retrieve_connection(spec_name) #:nodoc:
pool = retrieve_connection_pool(spec_name)
- raise ConnectionNotEstablished, "No connection pool with id #{spec_name} found." unless pool
+ raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool
conn = pool.connection
- raise ConnectionNotEstablished, "No connection for #{spec_name} in connection pool" unless conn
+ raise ConnectionNotEstablished, "No connection for '#{spec_name}' in connection pool" unless conn
conn
end
@@ -886,7 +910,7 @@ module ActiveRecord
# Remove the connection for this class. This will close the active
# connection and the defined connection (if they exist). The result
- # can be used as an argument for establish_connection, for easily
+ # can be used as an argument for #establish_connection, for easily
# re-establishing the connection.
def remove_connection(spec_name)
if pool = owner_to_pool.delete(spec_name)
@@ -907,7 +931,7 @@ module ActiveRecord
# A connection was established in an ancestor process that must have
# subsequently forked. We can't reuse the connection, but we can copy
# the specification and establish a new connection with it.
- establish_connection(ancestor_pool.spec).tap do |pool|
+ establish_connection(ancestor_pool.spec.to_hash).tap do |pool|
pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
end
else
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 507a925d32..e667e16964 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -10,7 +10,7 @@ module ActiveRecord
def to_sql(arel, binds = [])
if arel.respond_to?(:ast)
collected = visitor.accept(arel.ast, collector)
- collected.compile(binds.dup, self)
+ collected.compile(binds, self)
else
arel
end
@@ -18,11 +18,12 @@ module ActiveRecord
# This is used in the StatementCache object. It returns an object that
# can be used to query the database repeatedly.
- def cacheable_query(arel) # :nodoc:
+ def cacheable_query(klass, arel) # :nodoc:
+ collected = visitor.accept(arel.ast, collector)
if prepared_statements
- ActiveRecord::StatementCache.query visitor, arel.ast
+ klass.query(collected.value)
else
- ActiveRecord::StatementCache.partial_query visitor, arel.ast, collector
+ klass.partial_query(collected.value)
end
end
@@ -315,7 +316,7 @@ module ActiveRecord
end
end
key_list = fixture.keys.map { |name| quote_column_name(name) }
- value_list = prepare_binds_for_database(binds).map do |value|
+ value_list = binds.map(&:value_for_database).map do |value|
begin
quote(value)
rescue TypeError
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 860ef17dca..eda6af08e3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -112,19 +112,19 @@ module ActiveRecord
end
def quoted_true
- "'t'"
+ "'t'".freeze
end
def unquoted_true
- 't'
+ 't'.freeze
end
def quoted_false
- "'f'"
+ "'f'".freeze
end
def unquoted_false
- 'f'
+ 'f'.freeze
end
# Quote date/time values for use in SQL input. Includes microseconds
@@ -150,12 +150,12 @@ module ActiveRecord
quoted_date(value).sub(/\A2000-01-01 /, '')
end
- def prepare_binds_for_database(binds) # :nodoc:
- binds.map(&:value_for_database)
- end
-
private
+ def type_casted_binds(binds)
+ binds.map { |attr| type_cast(attr.value_for_database) }
+ end
+
def types_which_need_no_typecasting
[nil, Numeric, String]
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index bbb0e9249d..8dbafc5a4b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -51,11 +51,12 @@ module ActiveRecord
options[:primary_key] != default_primary_key
end
- def defined_for?(options_or_to_table = {})
- if options_or_to_table.is_a?(Hash)
- options_or_to_table.all? {|key, value| options[key].to_s == value.to_s }
+ def defined_for?(to_table_ord = nil, to_table: nil, **options)
+ if to_table_ord
+ self.to_table == to_table_ord.to_s
else
- to_table == options_or_to_table.to_s
+ (to_table.nil? || to_table.to_s == self.to_table) &&
+ options.all? { |k, v| self.options[k].to_s == v.to_s }
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 99a3e99bdc..19c265db6e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -129,14 +129,9 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table_name)
- pks = primary_keys(table_name)
- warn <<-WARNING.strip_heredoc if pks.count > 1
- WARNING: Rails does not support composite primary key.
-
- #{table_name} has composite primary key. Composite primary key is ignored.
- WARNING
-
- pks.first if pks.one?
+ pk = primary_keys(table_name)
+ pk = pk.first unless pk.size > 1
+ pk
end
# Creates a new table with the name +table_name+. +table_name+ may either
@@ -179,7 +174,7 @@ module ActiveRecord
# A Symbol can be used to specify the type of the generated primary key column.
# [<tt>:primary_key</tt>]
# The name of the primary key, if one is to be added automatically.
- # Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
+ # Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
#
# Note that Active Record models will automatically detect their
# primary key. This can be avoided by using
@@ -305,9 +300,9 @@ module ActiveRecord
# # Creates a table called 'assemblies_parts' with no id.
# create_join_table(:assemblies, :parts)
#
- # You can pass a +options+ hash can include the following keys:
+ # You can pass an +options+ hash which can include the following keys:
# [<tt>:table_name</tt>]
- # Sets the table name overriding the default
+ # Sets the table name, overriding the default.
# [<tt>:column_options</tt>]
# Any extra options you want appended to the columns definition.
# [<tt>:options</tt>]
@@ -433,7 +428,7 @@ module ActiveRecord
# t.remove_index :company_id
# end
#
- # See also Table for details on all of the various column transformation.
+ # See also Table for details on all of the various column transformations.
def change_table(table_name, options = {})
if supports_bulk_alter? && options[:bulk]
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
@@ -483,10 +478,10 @@ module ActiveRecord
#
# Available options are (none of these exists by default):
# * <tt>:limit</tt> -
- # Requests a maximum column length. This is number of characters for a <tt>:string</tt> column
+ # Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
# and number of bytes for <tt>:text</tt>, <tt>:binary</tt> and <tt>:integer</tt> columns.
# * <tt>:default</tt> -
- # The column's default value. Use nil for NULL.
+ # 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>.
@@ -495,7 +490,7 @@ module ActiveRecord
# * <tt>:scale</tt> -
# Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
#
- # Note: The precision is the total number of significant digits
+ # Note: The precision is the total number of significant digits,
# and the scale is the number of digits that can be stored following
# the decimal point. For example, the number 123.45 has a precision of 5
# and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
@@ -564,7 +559,7 @@ module ActiveRecord
#
# The +type+ and +options+ parameters will be ignored if present. It can be helpful
# to provide these in a migration's +change+ method so it can be reverted.
- # In that case, +type+ and +options+ will be used by add_column.
+ # In that case, +type+ and +options+ will be used by #add_column.
def remove_column(table_name, column_name, type = nil, options = {})
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
end
@@ -790,7 +785,7 @@ module ActiveRecord
# [<tt>:type</tt>]
# The reference column type. Defaults to +:integer+.
# [<tt>:index</tt>]
- # Add an appropriate index. Defaults to false.
+ # Add an appropriate index. Defaults to true.
# See #add_index for usage of this option.
# [<tt>:foreign_key</tt>]
# Add an appropriate foreign key constraint. Defaults to false.
@@ -847,14 +842,19 @@ module ActiveRecord
#
# remove_reference(:products, :user, index: true, foreign_key: true)
#
- def remove_reference(table_name, ref_name, options = {})
- if options[:foreign_key]
+ def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
+ if foreign_key
reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
- remove_foreign_key(table_name, reference_name)
+ if foreign_key.is_a?(Hash)
+ foreign_key_options = foreign_key
+ else
+ foreign_key_options = { to_table: reference_name }
+ end
+ remove_foreign_key(table_name, **foreign_key_options)
end
remove_column(table_name, "#{ref_name}_id")
- remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
+ remove_column(table_name, "#{ref_name}_type") if polymorphic
end
alias :remove_belongs_to :remove_reference
@@ -947,13 +947,13 @@ module ActiveRecord
# Checks to see if a foreign key exists on a table for a given foreign key definition.
#
- # # Check a foreign key exists
+ # # Checks to see if a foreign key exists.
# foreign_key_exists?(:accounts, :branches)
#
- # # Check a foreign key on a specified column exists
+ # # Checks to see if a foreign key on a specified column exists.
# foreign_key_exists?(:accounts, column: :owner_id)
#
- # # Check a foreign key with a custom name exists
+ # # Checks to see if a foreign key with a custom name exists.
# foreign_key_exists?(:accounts, name: "special_fk_name")
#
def foreign_key_exists?(from_table, options_or_to_table = {})
@@ -993,8 +993,8 @@ module ActiveRecord
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
if supports_multi_insert?
- sql = "INSERT INTO #{sm_table} (version) VALUES "
- sql << versions.map {|v| "('#{v}')" }.join(', ')
+ sql = "INSERT INTO #{sm_table} (version) VALUES\n"
+ sql << versions.map {|v| "('#{v}')" }.join(",\n")
sql << ";\n\n"
sql
else
@@ -1076,7 +1076,7 @@ module ActiveRecord
end
# Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
- # PostgreSQL, MySQL, and Oracle overrides this for custom DISTINCT syntax - they
+ # PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they
# require the order columns appear in the SELECT.
#
# columns_for_distinct("posts.id", ["posts.created_at desc"])
@@ -1123,7 +1123,6 @@ module ActiveRecord
index_type ||= options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
index_name ||= index_name(table_name, index_name_options(column_names))
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
if options.key?(:algorithm)
algorithm = index_algorithms.fetch(options[:algorithm]) {
@@ -1137,9 +1136,8 @@ module ActiveRecord
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
end
- if index_name.length > max_index_length
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
- end
+ validate_index_length!(table_name, index_name, options.fetch(:internal, false))
+
if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false)
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
end
@@ -1271,8 +1269,10 @@ module ActiveRecord
end
end
- def validate_index_length!(table_name, new_name) # :nodoc:
- if new_name.length > allowed_index_name_length
+ def validate_index_length!(table_name, new_name, internal = false) # :nodoc:
+ max_index_length = internal ? index_name_length : allowed_index_name_length
+
+ if new_name.length > max_index_length
raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index d4b9e301bc..0b8bacff4e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -130,7 +130,7 @@ module ActiveRecord
class BindCollector < Arel::Collectors::Bind
def compile(bvs, conn)
- casted_binds = conn.prepare_binds_for_database(bvs)
+ casted_binds = bvs.map(&:value_for_database)
super(casted_binds.map { |value| conn.quote(value) })
end
end
@@ -184,7 +184,30 @@ module ActiveRecord
# this method must only be called while holding connection pool's mutex
def expire
- @owner = nil
+ if in_use?
+ if @owner != Thread.current
+ raise ActiveRecordError, "Cannot expire connection, " <<
+ "it is owned by a different thread: #{@owner}. " <<
+ "Current thread: #{Thread.current}."
+ end
+
+ @owner = nil
+ else
+ raise ActiveRecordError, 'Cannot expire connection, it is not currently leased.'
+ end
+ end
+
+ # this method must only be called while holding connection pool's mutex (and a desire for segfaults)
+ def steal! # :nodoc:
+ if in_use?
+ if @owner != Thread.current
+ pool.send :remove_connection_from_thread_cache, self, @owner
+
+ @owner = Thread.current
+ end
+ else
+ raise ActiveRecordError, 'Cannot steal connection, it is not currently leased.'
+ end
end
def unprepared_statement
@@ -556,14 +579,15 @@ module ActiveRecord
exception
end
- def log(sql, name = "SQL", binds = [], statement_name = nil)
+ def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil)
@instrumenter.instrument(
"sql.active_record",
- :sql => sql,
- :name => name,
- :connection_id => object_id,
- :statement_name => statement_name,
- :binds => binds) { yield }
+ sql: sql,
+ name: name,
+ binds: binds,
+ type_casted_binds: type_casted_binds,
+ statement_name: statement_name,
+ connection_id: object_id) { yield }
rescue => e
raise translate_exception_class(e, sql)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 17aa4000c7..acc21866f1 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -9,6 +9,7 @@ require 'active_record/connection_adapters/mysql/schema_dumper'
require 'active_record/connection_adapters/mysql/type_metadata'
require 'active_support/core_ext/string/strip'
+require 'active_support/core_ext/regexp'
module ActiveRecord
module ConnectionAdapters
@@ -41,14 +42,14 @@ module ActiveRecord
NATIVE_DATABASE_TYPES = {
primary_key: "int auto_increment PRIMARY KEY",
string: { name: "varchar", limit: 255 },
- text: { name: "text" },
+ text: { name: "text", limit: 65535 },
integer: { name: "int", limit: 4 },
float: { name: "float" },
decimal: { name: "decimal" },
datetime: { name: "datetime" },
time: { name: "time" },
date: { name: "date" },
- binary: { name: "blob" },
+ binary: { name: "blob", limit: 65535 },
boolean: { name: "tinyint", limit: 1 },
json: { name: "json" },
}
@@ -85,7 +86,7 @@ module ActiveRecord
end
def mariadb? # :nodoc:
- full_version =~ /mariadb/i
+ /mariadb/i.match?(full_version)
end
# Returns true, since this connection adapter supports migrations.
@@ -409,10 +410,13 @@ module ActiveRecord
end
def table_comment(table_name) # :nodoc:
+ schema, name = extract_schema_qualified_name(table_name)
+
select_value(<<-SQL.strip_heredoc, 'SCHEMA')
SELECT table_comment
FROM information_schema.tables
- WHERE table_name=#{quote(table_name)}
+ WHERE table_schema = #{quote(schema)}
+ AND table_name = #{quote(name)}
SQL
end
@@ -460,7 +464,6 @@ module ActiveRecord
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
# In that case, +options+ and the block will be used by create_table.
def drop_table(table_name, options = {})
- create_table_info_cache.delete(table_name) if create_table_info_cache.key?(table_name)
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
@@ -515,19 +518,21 @@ module ActiveRecord
schema, name = extract_schema_qualified_name(table_name)
- fk_info = select_all <<-SQL.strip_heredoc
- SELECT fk.referenced_table_name as 'to_table'
- ,fk.referenced_column_name as 'primary_key'
- ,fk.column_name as 'column'
- ,fk.constraint_name as 'name'
+ fk_info = select_all(<<-SQL.strip_heredoc, 'SCHEMA')
+ SELECT fk.referenced_table_name AS 'to_table',
+ fk.referenced_column_name AS 'primary_key',
+ fk.column_name AS 'column',
+ fk.constraint_name AS 'name',
+ rc.update_rule AS 'on_update',
+ rc.delete_rule AS 'on_delete'
FROM information_schema.key_column_usage fk
- WHERE fk.referenced_column_name is not null
+ JOIN information_schema.referential_constraints rc
+ USING (constraint_schema, constraint_name)
+ WHERE fk.referenced_column_name IS NOT NULL
AND fk.table_schema = #{quote(schema)}
AND fk.table_name = #{quote(name)}
SQL
- create_table_info = create_table_info(table_name)
-
fk_info.map do |row|
options = {
column: row['column'],
@@ -535,14 +540,16 @@ module ActiveRecord
primary_key: row['primary_key']
}
- options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
- options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
+ options[:on_update] = extract_foreign_key_action(row['on_update'])
+ options[:on_delete] = extract_foreign_key_action(row['on_delete'])
ForeignKeyDefinition.new(table_name, row['to_table'], options)
end
end
- def table_options(table_name)
+ def table_options(table_name) # :nodoc:
+ table_options = {}
+
create_table_info = create_table_info(table_name)
# strip create_definitions and partition_options
@@ -551,10 +558,14 @@ module ActiveRecord
# strip AUTO_INCREMENT
raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+ table_options[:options] = raw_table_options
+
# strip COMMENT
- raw_table_options.sub!(/ COMMENT='.+'/, '')
+ if raw_table_options.sub!(/ COMMENT='.+'/, '')
+ table_options[:comment] = table_comment(table_name)
+ end
- raw_table_options
+ table_options
end
# Maps logical Rails types to MySQL-specific data types.
@@ -891,21 +902,15 @@ module ActiveRecord
end
end
- def extract_foreign_key_action(structure, name, action) # :nodoc:
- if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
- case $1
- when 'CASCADE'; :cascade
- when 'SET NULL'; :nullify
- end
+ def extract_foreign_key_action(specifier) # :nodoc:
+ case specifier
+ when 'CASCADE'; :cascade
+ when 'SET NULL'; :nullify
end
end
- def create_table_info_cache # :nodoc:
- @create_table_info_cache ||= {}
- end
-
def create_table_info(table_name) # :nodoc:
- create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
+ select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
end
def create_table_definition(*args) # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 901c98b22b..346916337e 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -13,6 +13,10 @@ module ActiveRecord
@config = original.config.dup
end
+ def to_hash
+ @config.merge(name: @name)
+ end
+
# Expands a connection string into a hash.
class ConnectionUrlResolver # :nodoc:
@@ -164,7 +168,7 @@ module ActiveRecord
# spec.config
# # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
#
- def spec(config, name = nil)
+ def spec(config)
spec = resolve(config).symbolize_keys
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
@@ -180,13 +184,11 @@ module ActiveRecord
adapter_method = "#{spec[:adapter]}_connection"
- name ||=
- if config.is_a?(Symbol)
- config.to_s
- else
- "primary"
- end
- ConnectionSpecification.new(name, spec, adapter_method)
+ unless ActiveRecord::Base.respond_to?(adapter_method)
+ raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
+ end
+
+ ConnectionSpecification.new(spec.delete(:name) || "primary", spec, adapter_method)
end
private
@@ -231,7 +233,7 @@ module ActiveRecord
#
def resolve_symbol_connection(spec)
if config = configurations[spec.to_s]
- resolve_connection(config)
+ resolve_connection(config).merge("name" => spec.to_s)
else
raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}")
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/column.rb b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
index 9c45fdd44a..ea554b188c 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
@@ -6,7 +6,6 @@ module ActiveRecord
def initialize(*)
super
- assert_valid_default
extract_default
end
@@ -38,12 +37,6 @@ module ActiveRecord
@default = null || strict ? nil : ''
end
end
-
- def assert_valid_default
- if blob_or_text_column? && default.present?
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
index 13c9b6cbd9..6d1215df2a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -13,19 +13,6 @@ module ActiveRecord
result
end
- # Returns a record hash with the column names as keys and column values
- # as values.
- def select_one(arel, name = nil, binds = [])
- arel, binds = binds_from_relation(arel, binds)
- @connection.query_options.merge!(as: :hash)
- select_result(to_sql(arel, binds), name, binds) do |result|
- @connection.next_result while @connection.more_results?
- result.first
- end
- ensure
- @connection.query_options.merge!(as: :array)
- end
-
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
def select_rows(sql, name = nil, binds = [])
@@ -90,9 +77,9 @@ module ActiveRecord
@connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
end
- type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
+ type_casted_binds = type_casted_binds(binds)
- log(sql, name, binds) do
+ log(sql, name, binds, type_casted_binds) do
if cache_stmt
cache = @statements[sql] ||= {
stmt: @connection.prepare(sql)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
index fbab654112..af1db30047 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
@@ -2,14 +2,14 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
module Quoting # :nodoc:
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
+ QUOTED_TRUE, QUOTED_FALSE = '1'.freeze, '0'.freeze
def quote_column_name(name)
- @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
+ @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`".freeze
end
def quote_table_name(name)
- @quoted_table_names[name] ||= super.gsub('.', '`.`')
+ @quoted_table_names[name] ||= super.gsub('.', '`.`').freeze
end
def quoted_true
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 6f2e03b370..f5232127c4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -124,6 +124,8 @@ module ActiveRecord
pk = primary_key(table_ref) if table_ref
end
+ pk = suppress_composite_primary_key(pk)
+
if pk && use_insert_returning?
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
end
@@ -164,6 +166,12 @@ module ActiveRecord
def exec_rollback_db_transaction
execute "ROLLBACK"
end
+
+ private
+
+ def suppress_composite_primary_key(pk)
+ pk unless pk.is_a?(Array)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 6414459cd1..9fcf8dbb95 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -28,7 +28,7 @@ module ActiveRecord
# - "schema.name".table_name
# - "schema.name"."table.name"
def quote_table_name(name) # :nodoc:
- @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted
+ @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
end
# Quotes schema names for use in SQL queries.
@@ -42,7 +42,7 @@ module ActiveRecord
# Quotes column names for use in SQL queries.
def quote_column_name(name) # :nodoc:
- @quoted_column_names[name] ||= PGconn.quote_ident(super)
+ @quoted_column_names[name] ||= PGconn.quote_ident(super).freeze
end
# Quote date/time values for use in SQL input.
@@ -58,7 +58,7 @@ module ActiveRecord
def quote_default_expression(value, column) # :nodoc:
if value.is_a?(Proc)
value.call
- elsif column.type == :uuid && value =~ /\(\)/
+ elsif column.type == :uuid && value.include?('()')
value # Does not quote function default values for UUID columns
elsif column.respond_to?(:array?)
value = type_cast_from_column(column, 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 6318b1c65a..4cf6d4b14a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -238,6 +238,10 @@ module ActiveRecord
PostgreSQLColumn.new(*args)
end
+ def table_options(table_name) # :nodoc:
+ { comment: table_comment(table_name) }
+ end
+
# Returns a comment stored in database for given table
def table_comment(table_name) # :nodoc:
name = Utils.extract_schema_qualified_name(table_name.to_s)
@@ -327,12 +331,12 @@ module ActiveRecord
end
# Returns the sequence name for a table's primary key or some other specified key.
- def default_sequence_name(table_name, pk = nil) #:nodoc:
- result = serial_sequence(table_name, pk || 'id')
+ def default_sequence_name(table_name, pk = 'id') #:nodoc:
+ result = serial_sequence(table_name, pk)
return nil unless result
Utils.extract_schema_qualified_name(result).to_s
rescue ActiveRecord::StatementInvalid
- PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
+ PostgreSQL::Name.new(nil, "#{table_name}_#{pk}_seq").to_s
end
def serial_sequence(table, column)
@@ -579,7 +583,7 @@ module ActiveRecord
end
def foreign_keys(table_name)
- fk_info = select_all <<-SQL.strip_heredoc
+ fk_info = select_all(<<-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
@@ -641,7 +645,7 @@ module ActiveRecord
when 1, 2; 'smallint'
when nil, 3, 4; 'integer'
when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.")
end
else
super(type, limit, precision, scale)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index c9d436e19f..e213c991c8 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -600,15 +600,15 @@ module ActiveRecord
end
def exec_no_cache(sql, name, binds)
- type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
- log(sql, name, binds) { @connection.async_exec(sql, type_casted_binds) }
+ type_casted_binds = type_casted_binds(binds)
+ log(sql, name, binds, type_casted_binds) { @connection.async_exec(sql, type_casted_binds) }
end
def exec_cache(sql, name, binds)
stmt_key = prepare_statement(sql)
- type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
+ type_casted_binds = type_casted_binds(binds)
- log(sql, name, binds, stmt_key) do
+ log(sql, name, binds, type_casted_binds, stmt_key) do
@connection.exec_prepared(stmt_key, type_casted_binds)
end
rescue ActiveRecord::StatementInvalid => e
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
index d5a181d3e2..fa20175b0e 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -11,7 +11,7 @@ module ActiveRecord
end
def quote_column_name(name)
- @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
+ @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}").freeze
end
def quoted_time(value)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index eb2268157b..41ed784d2e 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -159,7 +159,7 @@ module ActiveRecord
end
# Returns 62. SQLite supports index names up to 64
- # characters. The rest is used by rails internally to perform
+ # characters. The rest is used by Rails internally to perform
# temporary rename operations
def allowed_index_name_length
index_name_length - 2
@@ -188,9 +188,9 @@ module ActiveRecord
end
def exec_query(sql, name = nil, binds = [], prepare: false)
- type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
+ type_casted_binds = type_casted_binds(binds)
- log(sql, name, binds) do
+ log(sql, name, binds, type_casted_binds) do
# Don't cache statements if they are not prepared
unless prepare
stmt = @connection.prepare(sql)
@@ -203,7 +203,6 @@ module ActiveRecord
ensure
stmt.close
end
- stmt = records
else
cache = @statements[sql] ||= {
:stmt => @connection.prepare(sql)
@@ -212,9 +211,10 @@ module ActiveRecord
cols = cache[:cols] ||= stmt.columns
stmt.reset!
stmt.bind_params(type_casted_binds)
+ records = stmt.to_a
end
- ActiveRecord::Result.new(cols, stmt.to_a)
+ ActiveRecord::Result.new(cols, records)
end
end
@@ -548,7 +548,7 @@ module ActiveRecord
columns_string.split(',').each do |column_string|
# This regex will match the column name and collation type and will save
# the value in $1 and $2 respectively.
- collation_hash[$1] = $2 if (COLLATE_REGEX =~ column_string)
+ collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
end
basic_structure.map! do |column|
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index f932deb18d..f735bc697b 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -44,21 +44,18 @@ module ActiveRecord
#
# The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
# may be returned on an error.
- def establish_connection(spec = nil)
+ def establish_connection(config = nil)
raise "Anonymous class is not allowed." unless name
- spec ||= DEFAULT_ENV.call.to_sym
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
- # TODO: uses name on establish_connection, for backwards compatibility
- spec = resolver.spec(spec, self == Base ? "primary" : name)
+ config ||= DEFAULT_ENV.call.to_sym
+ spec_name = self == Base ? "primary" : name
+ self.connection_specification_name = spec_name
- unless respond_to?(spec.adapter_method)
- raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
- end
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
+ spec = resolver.resolve(config).symbolize_keys
+ spec[:name] = spec_name
- remove_connection(spec.name)
- self.connection_specification_name = spec.name
- connection_handler.establish_connection spec
+ connection_handler.establish_connection(spec)
end
class MergeAndResolveDefaultUrlConfig # :nodoc:
@@ -101,14 +98,6 @@ module ActiveRecord
@connection_specification_name
end
- def connection_id
- ActiveRecord::RuntimeRegistry.connection_id ||= Thread.current.object_id
- end
-
- def connection_id=(connection_id)
- ActiveRecord::RuntimeRegistry.connection_id = connection_id
- end
-
# Returns the configuration of the associated connection as a hash:
#
# ActiveRecord::Base.connection_config
@@ -134,7 +123,7 @@ module ActiveRecord
def remove_connection(name = nil)
name ||= @connection_specification_name if defined?(@connection_specification_name)
- # if removing a connection that have a pool, we reset the
+ # if removing a connection that has a pool, we reset the
# connection_specification_name so it will use the parent
# pool.
if connection_handler.retrieve_connection_pool(name)
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index f936e865e4..3e3a7679ac 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -72,11 +72,31 @@ module ActiveRecord
##
# :singleton-method:
- # Specifies if an error should be raised on query limit or order being
+ # Specifies if an error should be raised if the query has an order being
# ignored when doing batch queries. Useful in applications where the
- # limit or scope being ignored is error-worthy, rather than a warning.
- mattr_accessor :error_on_ignored_order_or_limit, instance_writer: false
- self.error_on_ignored_order_or_limit = false
+ # 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
+
+ def self.error_on_ignored_order_or_limit
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ The flag error_on_ignored_order_or_limit is deprecated. Limits are
+ now supported. Please use error_on_ignored_order instead.
+ MSG
+ self.error_on_ignored_order
+ end
+
+ def error_on_ignored_order_or_limit
+ self.class.error_on_ignored_order_or_limit
+ end
+
+ def self.error_on_ignored_order_or_limit=(value)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ The flag error_on_ignored_order_or_limit is deprecated. Limits are
+ now supported. Please use error_on_ignored_order= instead.
+ MSG
+ self.error_on_ignored_order = value
+ end
##
# :singleton-method:
@@ -338,7 +358,7 @@ module ActiveRecord
# post.title # => 'hello world'
def init_with(coder)
coder = LegacyYamlAdapter.convert(self.class, coder)
- @attributes = coder['attributes']
+ @attributes = self.class.yaml_encoder.decode(coder)
init_internals
@@ -404,11 +424,9 @@ module ActiveRecord
# Post.new.encode_with(coder)
# coder # => {"attributes" => {"id" => nil, ... }}
def encode_with(coder)
- # FIXME: Remove this when we better serialize attributes
- coder['raw_attributes'] = attributes_before_type_cast
- coder['attributes'] = @attributes
+ self.class.yaml_encoder.encode(@attributes, coder)
coder['new_record'] = new_record?
- coder['active_record_yaml_version'] = 1
+ coder['active_record_yaml_version'] = 2
end
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
@@ -432,7 +450,7 @@ module ActiveRecord
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
def hash
if id
- id.hash
+ [self.class, id].hash
else
super
end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index b6dd6814db..0bdebb3989 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/regexp'
+
module ActiveRecord
module DynamicMatchers #:nodoc:
def respond_to?(name, include_private = false)
@@ -29,7 +31,7 @@ module ActiveRecord
attr_reader :matchers
def match(model, name)
- klass = matchers.find { |k| name =~ k.pattern }
+ klass = matchers.find { |k| k.pattern.match?(name) }
klass.new(model, name) if klass
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 7be332fb97..b884edf920 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -105,6 +105,8 @@ module ActiveRecord
end
class EnumType < Type::Value # :nodoc:
+ delegate :type, to: :subtype
+
def initialize(name, mapping, subtype)
@name = name
@mapping = mapping
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index a317fb4d44..3f75ce4099 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -292,7 +292,7 @@ module ActiveRecord
#
# * http://www.postgresql.org/docs/current/static/transaction-iso.html
# * https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html#error_er_lock_deadlock
- class TransactionRollbackError < ActiveRecordError
+ class TransactionRollbackError < StatementInvalid
end
# SerializationFailure will be raised when a transaction is rolled
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 727a9befc1..ac27e72f2b 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -16,15 +16,14 @@ module ActiveRecord
# Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
# Returns a formatted string ready to be logged.
def exec_explain(queries) # :nodoc:
- str = queries.map do |sql, bind|
- [].tap do |msg|
- msg << "EXPLAIN for: #{sql}"
- unless bind.empty?
- bind_msg = bind.map {|col, val| [col.name, val]}.inspect
- msg.last << " #{bind_msg}"
- end
- msg << connection.explain(sql, bind)
- end.join("\n")
+ str = queries.map do |sql, binds|
+ msg = "EXPLAIN for: #{sql}"
+ unless binds.empty?
+ msg << " "
+ msg << binds.map { |attr| render_bind(attr) }.inspect
+ end
+ msg << "\n"
+ msg << connection.explain(sql, binds)
end.join("\n")
# Overriding inspect to be more human readable, especially in the console.
@@ -34,5 +33,17 @@ module ActiveRecord
str
end
+
+ private
+
+ def render_bind(attr)
+ value = if attr.type.binary? && attr.value
+ "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
+ else
+ connection.type_cast(attr.value_for_database)
+ end
+
+ [attr.name, value]
+ end
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index ed1bbf5dcd..0ec33f7d87 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -66,7 +66,7 @@ module ActiveRecord
# By default, +test_helper.rb+ will load all of your fixtures into your test
# database, so this test will succeed.
#
- # The testing environment will automatically load the all fixtures into the database before each
+ # The testing environment will automatically load all the fixtures into the database before each
# test. To ensure consistent data, the environment deletes the fixtures before running the load.
#
# In addition to being available in the database, the fixture's data may also be accessed by
@@ -968,6 +968,7 @@ module ActiveRecord
@fixture_cache = {}
@fixture_connections = []
@@already_loaded_fixtures ||= {}
+ @connection_subscriber = nil
# Load fixtures once and begin transaction.
if run_in_transaction?
@@ -977,10 +978,31 @@ module ActiveRecord
@loaded_fixtures = load_fixtures(config)
@@already_loaded_fixtures[self.class] = @loaded_fixtures
end
+
+ # Begin transactions for connections already established
@fixture_connections = enlist_fixture_connections
@fixture_connections.each do |connection|
connection.begin_transaction joinable: false
end
+
+ # When connections are established in the future, begin a transaction too
+ @connection_subscriber = ActiveSupport::Notifications.subscribe('!connection.active_record') do |_, _, _, _, payload|
+ spec_name = payload[:spec_name] if payload.key?(:spec_name)
+
+ if spec_name
+ begin
+ connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name)
+ rescue ConnectionNotEstablished
+ connection = nil
+ end
+
+ if connection && !@fixture_connections.include?(connection)
+ connection.begin_transaction joinable: false
+ @fixture_connections << connection
+ end
+ end
+ end
+
# Load fixtures for every test.
else
ActiveRecord::FixtureSet.reset_cache
@@ -995,6 +1017,7 @@ module ActiveRecord
def teardown_fixtures
# Rollback changes if a transaction is active.
if run_in_transaction?
+ ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
@fixture_connections.each do |connection|
connection.rollback_transaction if connection.transaction_open?
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 899683ee4f..b5fec57c8c 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -19,7 +19,7 @@ module ActiveRecord
# Be aware that because the type column is an attribute on the record every new
# subclass will instantly be marked as dirty and the type column will be included
# in the list of changed attributes on the record. This is different from non
- # STI classes:
+ # Single Table Inheritance(STI) classes:
#
# Company.new.changed? # => false
# Firm.new.changed? # => true
@@ -37,6 +37,7 @@ module ActiveRecord
included do
# Determines whether to store the full constant name including namespace when using STI.
+ # This is true, by default.
class_attribute :store_full_sti_class, instance_writer: false
self.store_full_sti_class = true
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 466c8509a4..d729deb07b 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -86,7 +86,7 @@ module ActiveRecord
#
# user = User.find_by(name: 'David Heinemeier Hansson')
# user.id # => 125
- # user_path(user) # => "/users/125-david"
+ # user_path(user) # => "/users/125-david-heinemeier"
#
# Because the generated param begins with the record's +id+, it is
# suitable for passing to +find+. In a controller, for example:
@@ -100,7 +100,7 @@ module ActiveRecord
define_method :to_param do
if (default = super()) &&
(result = send(method_name).to_s).present? &&
- (param = result.squish.truncate(20, separator: /\s/, omission: nil).parameterize).present?
+ (param = result.squish.parameterize.truncate(20, separator: /-/, omission: '')).present?
"#{default}-#{param}"
else
default
diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb
index 17a5dc1d1b..8b649eab0f 100644
--- a/activerecord/lib/active_record/internal_metadata.rb
+++ b/activerecord/lib/active_record/internal_metadata.rb
@@ -14,10 +14,6 @@ module ActiveRecord
"#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}"
end
- def original_table_name
- "#{table_name_prefix}active_record_internal_metadatas#{table_name_suffix}"
- end
-
def []=(key, value)
find_or_initialize_by(key: key).update_attributes!(value: value)
end
@@ -30,17 +26,8 @@ module ActiveRecord
ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) }
end
- def original_table_exists?
- # This method will be removed in Rails 5.1
- # Since it is only necessary when `active_record_internal_metadatas` could exist
- ActiveSupport::Deprecation.silence { connection.table_exists?(original_table_name) }
- end
-
# Creates an internal metadata table with columns +key+ and +value+
def create_table
- if original_table_exists?
- connection.rename_table(original_table_name, table_name)
- end
unless table_exists?
key_options = connection.internal_string_options_for_primary_key
diff --git a/activerecord/lib/active_record/legacy_yaml_adapter.rb b/activerecord/lib/active_record/legacy_yaml_adapter.rb
index 89dee58423..c7683f68c7 100644
--- a/activerecord/lib/active_record/legacy_yaml_adapter.rb
+++ b/activerecord/lib/active_record/legacy_yaml_adapter.rb
@@ -4,7 +4,7 @@ module ActiveRecord
return coder unless coder.is_a?(Psych::Coder)
case coder["active_record_yaml_version"]
- when 1 then coder
+ when 1, 2 then coder
else
if coder["attributes"].is_a?(AttributeSet)
Rails420.convert(klass, coder)
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 8e32af1c49..c4998ba686 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -20,18 +20,14 @@ module ActiveRecord
@odd = false
end
- def render_bind(attribute)
- value = if attribute.type.binary? && attribute.value
- if attribute.value.is_a?(Hash)
- "<#{attribute.value_for_database.to_s.bytesize} bytes of binary data>"
- else
- "<#{attribute.value.bytesize} bytes of binary data>"
- end
+ def render_bind(attr, type_casted_value)
+ value = if attr.type.binary? && attr.value
+ "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
else
- attribute.value_for_database
+ type_casted_value
end
- [attribute.name, value]
+ [attr.name, value]
end
def sql(event)
@@ -48,7 +44,9 @@ module ActiveRecord
binds = nil
unless (payload[:binds] || []).empty?
- binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect
+ binds = " " + payload[:binds].zip(payload[:type_casted_binds]).map { |attr, value|
+ render_bind(attr, value)
+ }.inspect
end
name = colorize_payload_name(name, payload[:name])
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 81fe053fe1..1bb4688717 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,5 +1,6 @@
-require "active_support/core_ext/module/attribute_accessors"
require 'set'
+require "active_support/core_ext/module/attribute_accessors"
+require 'active_support/core_ext/regexp'
module ActiveRecord
class MigrationError < ActiveRecordError#:nodoc:
@@ -126,9 +127,9 @@ module ActiveRecord
class PendingMigrationError < MigrationError#:nodoc:
def initialize(message = nil)
if !message && defined?(Rails.env)
- super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate RAILS_ENV=#{::Rails.env}")
+ super("Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate RAILS_ENV=#{::Rails.env}")
elsif !message
- super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate")
+ super("Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate")
else
super
end
@@ -145,7 +146,7 @@ module ActiveRecord
class NoEnvironmentInSchemaError < MigrationError #:nodoc:
def initialize
- msg = "Environment data not found in the schema. To resolve this issue, run: \n\n\tbin/rails db:environment:set"
+ msg = "Environment data not found in the schema. To resolve this issue, run: \n\n bin/rails db:environment:set"
if defined?(Rails.env)
super("#{msg} RAILS_ENV=#{::Rails.env}")
else
@@ -168,7 +169,7 @@ module ActiveRecord
msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
msg << "You are running in `#{ current }` environment. "
msg << "If you are sure you want to continue, first set the environment using:\n\n"
- msg << "\tbin/rails db:environment:set"
+ msg << " bin/rails db:environment:set"
if defined?(Rails.env)
super("#{msg} RAILS_ENV=#{::Rails.env}\n\n")
else
@@ -1057,7 +1058,7 @@ module ActiveRecord
end
def match_to_migration_filename?(filename) # :nodoc:
- File.basename(filename) =~ Migration::MigrationFilenameRegexp
+ Migration::MigrationFilenameRegexp.match?(File.basename(filename))
end
def parse_migration_filename(filename) # :nodoc:
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index f691a8319d..41cebb0e34 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -249,7 +249,11 @@ module ActiveRecord
end
def attributes_builder # :nodoc:
- @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key)
+ @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) do |name|
+ unless columns_hash.key?(name)
+ _default_attributes[name].dup
+ end
+ end
end
def columns_hash # :nodoc:
@@ -267,6 +271,10 @@ module ActiveRecord
@attribute_types ||= Hash.new(Type::Value.new)
end
+ def yaml_encoder # :nodoc:
+ @yaml_encoder ||= AttributeSet::YAMLEncoder.new(attribute_types)
+ end
+
# Returns the type of the attribute with the given name, after applying
# all modifiers. This method is the only valid source of information for
# anything related to the types of a model's attributes. This method will
@@ -278,8 +286,12 @@ module ActiveRecord
#
# +attr_name+ The name of the attribute to retrieve the type for. Must be
# a string
- def type_for_attribute(attr_name)
- attribute_types[attr_name]
+ def type_for_attribute(attr_name, &block)
+ if block
+ attribute_types.fetch(attr_name, &block)
+ else
+ attribute_types[attr_name]
+ end
end
# Returns a hash where the keys are column names and the values are
@@ -301,7 +313,12 @@ module ActiveRecord
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
# and columns used for single table inheritance have been removed.
def content_columns
- @content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
+ @content_columns ||= columns.reject do |c|
+ c.name == primary_key ||
+ c.name == inheritance_column ||
+ c.name.end_with?('_id') ||
+ c.name.end_with?('_count')
+ end
end
# Resets all the cached information about columns, which will cause them
@@ -375,6 +392,7 @@ module ActiveRecord
@columns = nil
@columns_hash = nil
@attribute_names = nil
+ @yaml_encoder = nil
direct_descendants.each do |descendant|
descendant.send(:reload_schema_from_cache)
end
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 1ab4e0404f..254550c378 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -1,9 +1,5 @@
module ActiveRecord
module NullRelation # :nodoc:
- def exec_queries
- @records = [].freeze
- end
-
def pluck(*column_names)
[]
end
@@ -20,10 +16,6 @@ module ActiveRecord
0
end
- def size
- calculate :size, nil
- end
-
def empty?
true
end
@@ -48,28 +40,8 @@ module ActiveRecord
""
end
- def count(*)
- calculate :count, nil
- end
-
- def sum(*)
- calculate :sum, nil
- end
-
- def average(*)
- calculate :average, nil
- end
-
- def minimum(*)
- calculate :minimum, nil
- end
-
- def maximum(*)
- calculate :maximum, nil
- end
-
def calculate(operation, _column_name)
- if [:count, :sum, :size].include? operation
+ if [:count, :sum].include? operation
group_values.any? ? Hash.new : 0
elsif [:average, :minimum, :maximum].include?(operation) && group_values.any?
Hash.new
@@ -85,5 +57,11 @@ module ActiveRecord
def or(other)
other.spawn
end
+
+ private
+
+ def exec_queries
+ @records = [].freeze
+ end
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index afed5e5e85..510e8a6e43 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -479,7 +479,12 @@ module ActiveRecord
# ball.touch(:updated_at) # => raises ActiveRecordError
#
def touch(*names, time: nil)
- raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
+ unless persisted?
+ raise ActiveRecordError, <<-MSG.squish
+ cannot touch on a new or destroyed record object. Consider using
+ persisted?, new_record?, or destroyed? before touching
+ MSG
+ end
time ||= current_time_from_proper_timezone
attributes = timestamp_attributes_for_update_in_model
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index ca12a603da..387dd8e9bd 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -5,7 +5,7 @@ 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 ActiveRecord::Base.connected?
+ if connected?
connection.cache(&block)
else
yield
@@ -15,7 +15,7 @@ module ActiveRecord
# 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 ActiveRecord::Base.connected?
+ if connected?
connection.uncached(&block)
else
yield
@@ -26,16 +26,12 @@ module ActiveRecord
def self.run
connection = ActiveRecord::Base.connection
enabled = connection.query_cache_enabled
- connection_id = ActiveRecord::Base.connection_id
connection.enable_query_cache!
- [enabled, connection_id]
+ enabled
end
- def self.complete(state)
- enabled, connection_id = state
-
- ActiveRecord::Base.connection_id = connection_id
+ def self.complete(enabled)
ActiveRecord::Base.connection.clear_query_cache
ActiveRecord::Base.connection.disable_query_cache! unless enabled
end
@@ -44,7 +40,7 @@ module ActiveRecord
executor.register_hook(self)
executor.to_complete do
- unless ActiveRecord::Base.connection.transaction_open?
+ unless ActiveRecord::Base.connected? && ActiveRecord::Base.connection.transaction_open?
ActiveRecord::Base.clear_active_connections!
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 53ddd95bb0..f8dd35df0f 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -9,7 +9,7 @@ module ActiveRecord
delegate :find_each, :find_in_batches, :in_batches, to: :all
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or,
:where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly,
- :having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all
+ :having, :create_with, :uniq, :distinct, :references, :none, :unscope, :merge, to: :all
delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all
delegate :pluck, :ids, to: :all
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 98ea425d16..2c0ca62924 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -3,7 +3,7 @@ require "rails"
require "active_model/railtie"
# For now, action_controller must always be present with
-# rails, so let's make sure that it gets required before
+# Rails, so let's make sure that it gets required before
# here. This is needed for correctly setting up the middleware.
# In the future, this might become an optional require.
require "action_controller/railtie"
diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb
index 8727e46cb3..c13238cbe2 100644
--- a/activerecord/lib/active_record/railties/controller_runtime.rb
+++ b/activerecord/lib/active_record/railties/controller_runtime.rb
@@ -19,7 +19,7 @@ module ActiveRecord
end
def cleanup_view_runtime
- if logger.info? && ActiveRecord::Base.connected?
+ if logger && logger.info? && ActiveRecord::Base.connected?
db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime
self.db_runtime = (db_runtime || 0) + db_rt_before_render
runtime = super
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index d042fe5f8b..0792ba8f76 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,5 +1,3 @@
-require "arel/collectors/bind"
-
module ActiveRecord
# = Active Record \Relation
class Relation
@@ -65,7 +63,7 @@ module ActiveRecord
@klass.connection.insert(
im,
'SQL',
- primary_key,
+ primary_key || false,
primary_key_value,
nil,
binds)
@@ -279,12 +277,7 @@ module ActiveRecord
def empty?
return @records.empty? if loaded?
- if limit_value == 0
- true
- else
- c = count(:all)
- c.respond_to?(:zero?) ? c.zero? : c.empty?
- end
+ limit_value == 0 || !exists?
end
# Returns true if there are no records.
@@ -602,19 +595,16 @@ module ActiveRecord
# # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
def to_sql
@to_sql ||= begin
- relation = self
- connection = klass.connection
- visitor = connection.visitor
+ relation = self
if eager_loading?
find_with_associations { |rel| relation = rel }
end
- binds = relation.bound_attributes
- binds = connection.prepare_binds_for_database(binds)
- binds.map! { |value| connection.quote(value) }
- collect = visitor.accept(relation.arel.ast, Arel::Collectors::Bind.new)
- collect.substitute_binds(binds).join
+ conn = klass.connection
+ conn.unprepared_statement {
+ conn.to_sql(relation.arel, relation.bound_attributes)
+ }
end
end
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 3639625722..20ed4526b0 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -1,8 +1,8 @@
-require "active_record/relation/batches/batch_enumerator"
+require 'active_record/relation/batches/batch_enumerator'
module ActiveRecord
module Batches
- ORDER_OR_LIMIT_IGNORED_MESSAGE = "Scoped order and limit are ignored, it's forced to be batch order and batch size."
+ ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
# Looping through a collection of records from the database
# (using the Scoping::Named::ClassMethods.all method, for example)
@@ -34,15 +34,19 @@ module ActiveRecord
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
- # the order and limit have to be ignored due to batching.
+ # an order is present in the relation.
#
- # This is especially useful if you want multiple workers dealing with
- # the same processing queue. You can make worker 1 handle all the records
- # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
- # (by setting the +:start+ and +:finish+ option on each worker).
+ # Limits are honored, and if present there is no requirement for the batch
+ # size, it can be less than, equal, or greater than the limit.
#
- # # Let's process for a batch of 2000 records, skipping the first 2000 rows
- # Person.find_each(start: 2000, batch_size: 2000) do |person|
+ # The options +start+ and +finish+ are especially useful if you want
+ # multiple workers dealing with the same processing queue. You can make
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
+ # option on each worker.
+ #
+ # # Let's process from record 10_000 on.
+ # Person.find_each(start: 10_000) do |person|
# person.party_all_night!
# end
#
@@ -51,8 +55,8 @@ module ActiveRecord
# work. This also means that this method only works when the primary key is
# orderable (e.g. an integer or string).
#
- # NOTE: You can't set the limit either, that's used to control
- # the batch sizes.
+ # NOTE: By its nature, batch processing is subject to race conditions if
+ # other processes are modifying the database.
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
if block_given?
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
@@ -89,15 +93,19 @@ module ActiveRecord
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
- # the order and limit have to be ignored due to batching.
+ # an order is present in the relation.
+ #
+ # Limits are honored, and if present there is no requirement for the batch
+ # size, it can be less than, equal, or greater than the limit.
#
- # This is especially useful if you want multiple workers dealing with
- # the same processing queue. You can make worker 1 handle all the records
- # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
- # (by setting the +:start+ and +:finish+ option on each worker).
+ # The options +start+ and +finish+ are especially useful if you want
+ # multiple workers dealing with the same processing queue. You can make
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
+ # option on each worker.
#
- # # Let's process the next 2000 records
- # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
+ # # Let's process from record 10_000 on.
+ # Person.find_in_batches(start: 10_000) do |group|
# group.each { |person| person.party_all_night! }
# end
#
@@ -106,8 +114,8 @@ module ActiveRecord
# work. This also means that this method only works when the primary key is
# orderable (e.g. an integer or string).
#
- # NOTE: You can't set the limit either, that's used to control
- # the batch sizes.
+ # NOTE: By its nature, batch processing is subject to race conditions if
+ # other processes are modifying the database.
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
relation = self
unless block_given?
@@ -149,17 +157,19 @@ module ActiveRecord
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
- # the order and limit have to be ignored due to batching.
+ # an order is present in the relation.
+ #
+ # Limits are honored, and if present there is no requirement for the batch
+ # size, it can be less than, equal, or greater than the limit.
#
- # This is especially useful if you want to work with the
- # ActiveRecord::Relation object instead of the array of records, or if
- # you want multiple workers dealing with the same processing queue. You can
- # make worker 1 handle all the records between id 0 and 10,000 and worker 2
- # handle from 10,000 and beyond (by setting the +:start+ and +:finish+
- # option on each worker).
+ # The options +start+ and +finish+ are especially useful if you want
+ # multiple workers dealing with the same processing queue. You can make
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
+ # option on each worker.
#
- # # Let's process the next 2000 records
- # Person.in_batches(of: 2000, start: 2000).update_all(awesome: true)
+ # # Let's process from record 10_000 on.
+ # Person.in_batches(start: 10_000).update_all(awesome: true)
#
# An example of calling where query method on the relation:
#
@@ -179,19 +189,25 @@ module ActiveRecord
# consistent. Therefore the primary key must be orderable, e.g an integer
# or a string.
#
- # NOTE: You can't set the limit either, that's used to control the batch
- # sizes.
+ # NOTE: By its nature, batch processing is subject to race conditions if
+ # other processes are modifying the database.
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
relation = self
unless block_given?
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
end
- if arel.orders.present? || arel.taken.present?
- act_on_order_or_limit_ignored(error_on_ignore)
+ if arel.orders.present?
+ act_on_ignored_order(error_on_ignore)
+ end
+
+ batch_limit = of
+ if limit_value
+ remaining = limit_value
+ batch_limit = remaining if remaining < batch_limit
end
- relation = relation.reorder(batch_order).limit(of)
+ relation = relation.reorder(batch_order).limit(batch_limit)
relation = apply_limits(relation, start, finish)
batch_relation = relation
@@ -199,11 +215,11 @@ module ActiveRecord
if load
records = batch_relation.records
ids = records.map(&:id)
- yielded_relation = self.where(primary_key => ids)
+ yielded_relation = where(primary_key => ids)
yielded_relation.load_records(records)
else
ids = batch_relation.pluck(primary_key)
- yielded_relation = self.where(primary_key => ids)
+ yielded_relation = where(primary_key => ids)
end
break if ids.empty?
@@ -213,7 +229,20 @@ module ActiveRecord
yield yielded_relation
- break if ids.length < of
+ break if ids.length < batch_limit
+
+ if limit_value
+ remaining -= ids.length
+
+ if remaining == 0
+ # Saves a useless iteration when the limit is a multiple of the
+ # batch size.
+ break
+ elsif remaining < batch_limit
+ relation = relation.limit(remaining)
+ end
+ end
+
batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
end
end
@@ -230,13 +259,13 @@ module ActiveRecord
"#{quoted_table_name}.#{quoted_primary_key} ASC"
end
- def act_on_order_or_limit_ignored(error_on_ignore)
- raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order_or_limit : error_on_ignore)
+ def act_on_ignored_order(error_on_ignore)
+ raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order : error_on_ignore)
if raise_error
- raise ArgumentError.new(ORDER_OR_LIMIT_IGNORED_MESSAGE)
+ raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
elsif logger
- logger.warn(ORDER_OR_LIMIT_IGNORED_MESSAGE)
+ logger.warn(ORDER_IGNORE_MESSAGE)
end
end
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index d6d92b8607..a97b71815a 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -312,8 +312,10 @@ module ActiveRecord
Hash[calculated_data.map do |row|
key = group_columns.map { |aliaz, col_name|
- column = calculated_data.column_types.fetch(aliaz) do
- type_for(col_name)
+ column = type_for(col_name) do
+ calculated_data.column_types.fetch(aliaz) do
+ Type::Value.new
+ end
end
type_cast_calculated_value(row[aliaz], column)
}
@@ -346,9 +348,9 @@ module ActiveRecord
@klass.connection.table_alias_for(table_name)
end
- def type_for(field)
+ def type_for(field, &block)
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
- @klass.type_for_attribute(field_name)
+ @klass.type_for_attribute(field_name, &block)
end
def type_cast_calculated_value(value, type, operation = nil)
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 2484cb3264..ad74659cba 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,4 +1,5 @@
require 'active_support/concern'
+require 'active_support/core_ext/regexp'
module ActiveRecord
module Delegation # :nodoc:
@@ -58,7 +59,7 @@ module ActiveRecord
@delegation_mutex.synchronize do
return if method_defined?(method)
- if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
+ if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(*args, &block)
scoping { @klass.#{method}(*args, &block) }
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index e7e331f88d..916dca33bd 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -318,7 +318,7 @@ module ActiveRecord
return false if !conditions
- relation = apply_join_dependency(self, construct_join_dependency)
+ relation = apply_join_dependency(self, construct_join_dependency(eager_loading: false))
return false if ActiveRecord::NullRelation === relation
relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1)
@@ -333,6 +333,8 @@ module ActiveRecord
end
connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
+ rescue RangeError
+ false
end
# This method is called whenever no records are found with either a single
@@ -392,21 +394,13 @@ module ActiveRecord
end
end
- def construct_join_dependency(joins = [])
+ def construct_join_dependency(joins = [], eager_loading: true)
including = eager_load_values + includes_values
- ActiveRecord::Associations::JoinDependency.new(@klass, including, joins)
+ ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, eager_loading: eager_loading)
end
def construct_relation_for_association_calculations
- from = arel.froms.first
- if Arel::Table === from
- apply_join_dependency(self, construct_join_dependency(joins_values))
- else
- # FIXME: as far as I can tell, `from` will always be an Arel::Table.
- # There are no tests that test this branch, but presumably it's
- # possible for `from` to be a list?
- apply_join_dependency(self, construct_join_dependency(from))
- end
+ apply_join_dependency(self, construct_join_dependency(joins_values))
end
def apply_join_dependency(relation, join_dependency)
@@ -520,7 +514,7 @@ module ActiveRecord
def find_take
if loaded?
- @records.first
+ records.first
else
@take ||= limit(1).records.first
end
@@ -537,7 +531,7 @@ module ActiveRecord
MSG
end
if loaded?
- @records[index]
+ records[index]
else
offset ||= offset_index
@offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first
@@ -563,7 +557,7 @@ module ActiveRecord
def find_nth_from_last(index)
if loaded?
- @records[-index]
+ records[-index]
else
relation = if order_values.empty? && primary_key
order(arel_attribute(primary_key).asc)
@@ -579,12 +573,12 @@ module ActiveRecord
# e.g., reverse_order.offset(index-1).first
end
end
-
+
private
def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
if loaded?
- @records[index, limit]
+ records[index, limit]
else
index += offset
find_nth_with_limit(index, limit)
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index ecce949370..e5496c02b2 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -15,11 +15,11 @@ module ActiveRecord
@table = table
@handlers = []
- register_handler(BasicObject, BasicObjectHandler.new(self))
+ register_handler(BasicObject, BasicObjectHandler.new)
register_handler(Class, ClassHandler.new(self))
register_handler(Base, BaseHandler.new(self))
- register_handler(Range, RangeHandler.new(self))
- register_handler(RangeHandler::RangeWithBinds, RangeHandler.new(self))
+ register_handler(Range, RangeHandler.new)
+ register_handler(RangeHandler::RangeWithBinds, RangeHandler.new)
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new(self))
register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
index d7fd878265..413cb9fd84 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
@@ -2,13 +2,14 @@ module ActiveRecord
class PredicateBuilder
class AssociationQueryHandler # :nodoc:
def self.value_for(table, column, value)
- klass = if table.associated_table(column).polymorphic_association? && ::Array === value && value.first.is_a?(Base)
+ associated_table = table.associated_table(column)
+ klass = if associated_table.polymorphic_association? && ::Array === value && value.first.is_a?(Base)
PolymorphicArrayValue
else
AssociationQueryValue
end
- klass.new(table.associated_table(column), value)
+ klass.new(associated_table, value)
end
def initialize(predicate_builder)
diff --git a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb
index 6cec75dc0a..79cde00303 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb
@@ -1,17 +1,9 @@
module ActiveRecord
class PredicateBuilder
class BasicObjectHandler # :nodoc:
- def initialize(predicate_builder)
- @predicate_builder = predicate_builder
- end
-
def call(attribute, value)
attribute.eq(value)
end
-
- protected
-
- attr_reader :predicate_builder
end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
index 306d4694ae..5db778e19c 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
@@ -3,10 +3,6 @@ module ActiveRecord
class RangeHandler # :nodoc:
RangeWithBinds = Struct.new(:begin, :end, :exclude_end?)
- def initialize(predicate_builder)
- @predicate_builder = predicate_builder
- end
-
def call(attribute, value)
if value.begin.respond_to?(:infinite?) && value.begin.infinite?
if value.end.respond_to?(:infinite?) && value.end.infinite?
@@ -24,10 +20,6 @@ module ActiveRecord
attribute.between(value)
end
end
-
- protected
-
- attr_reader :predicate_builder
end
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 6477629560..0749bb30b5 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -4,6 +4,7 @@ require "active_record/relation/where_clause"
require "active_record/relation/where_clause_factory"
require 'active_model/forbidden_attributes_protection'
require 'active_support/core_ext/string/filters'
+require 'active_support/core_ext/regexp'
module ActiveRecord
module QueryMethods
@@ -658,7 +659,7 @@ module ActiveRecord
# present). Neither relation may have a #limit, #offset, or #distinct set.
#
# Post.where("id = 1").or(Post.where("author_id = 3"))
- # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'author_id = 3'))
+ # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
#
def or(other)
unless other.is_a? Relation
@@ -1008,12 +1009,6 @@ module ActiveRecord
self.send(unscope_code, result)
end
- def association_for_table(table_name)
- table_name = table_name.to_s
- @klass._reflect_on_association(table_name) ||
- @klass._reflect_on_association(table_name.singularize)
- end
-
def build_from
opts = from_clause.value
name = from_clause.name
@@ -1141,10 +1136,10 @@ module ActiveRecord
end
def does_not_support_reverse?(order)
- #uses sql function with multiple arguments
- order =~ /\([^()]*,[^()]*\)/ ||
- # uses "nulls first" like construction
- order =~ /nulls (first|last)\Z/i
+ # Uses SQL function with multiple arguments.
+ /\([^()]*,[^()]*\)/.match?(order) ||
+ # Uses "nulls first" like construction.
+ /nulls (first|last)\Z/i.match?(order)
end
def build_order(arel)
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 8e6cd6c82f..b9fd0f5326 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -75,8 +75,14 @@ module ActiveRecord
hash_rows[idx]
end
+ def first
+ return nil if @rows.empty?
+ Hash[@columns.zip(@rows.first)]
+ end
+
def last
- hash_rows.last
+ return nil if @rows.empty?
+ Hash[@columns.zip(@rows.last)]
end
def cast_values(type_overrides = {}) # :nodoc:
diff --git a/activerecord/lib/active_record/runtime_registry.rb b/activerecord/lib/active_record/runtime_registry.rb
index 56e88bc661..9e86999c1b 100644
--- a/activerecord/lib/active_record/runtime_registry.rb
+++ b/activerecord/lib/active_record/runtime_registry.rb
@@ -12,9 +12,9 @@ module ActiveRecord
class RuntimeRegistry # :nodoc:
extend ActiveSupport::PerThreadRegistry
- attr_accessor :connection_handler, :sql_runtime, :connection_id
+ attr_accessor :connection_handler, :sql_runtime
- [:connection_handler, :sql_runtime, :connection_id].each do |val|
+ [:connection_handler, :sql_runtime].each do |val|
class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__
class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index a9e1fd0dad..f007e9e733 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/regexp'
+
module ActiveRecord
module Sanitization
extend ActiveSupport::Concern
@@ -153,7 +155,7 @@ module ActiveRecord
# # => "name='foo''bar' and group_id='4'"
def sanitize_sql_array(ary)
statement, *values = ary
- if values.first.is_a?(Hash) && statement =~ /:\w+/
+ if values.first.is_a?(Hash) && /:\w+/.match?(statement)
replace_named_bind_variables(statement, values.first)
elsif statement.include?('?')
replace_bind_variables(statement, values)
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index d769376d1a..cc3bb1cd06 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -105,12 +105,7 @@ HEADER
tbl = StringIO.new
# first dump primary key column
- if @connection.respond_to?(:primary_keys)
- pk = @connection.primary_keys(table)
- pk = pk.first unless pk.size > 1
- else
- pk = @connection.primary_key(table)
- end
+ pk = @connection.primary_key(table)
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
@@ -132,10 +127,10 @@ HEADER
tbl.print ", force: :cascade"
table_options = @connection.table_options(table)
- tbl.print ", options: #{table_options.inspect}" unless table_options.blank?
-
- if comment = @connection.table_comment(table).presence
- tbl.print ", comment: #{comment.inspect}"
+ if table_options.present?
+ table_options.each do |key, value|
+ tbl.print ", #{key}: #{value.inspect}" if value.present?
+ end
end
tbl.puts " do |t|"
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index 6c896ccea6..8a29547bda 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -40,7 +40,7 @@ module ActiveRecord
end
class PartialQuery < Query # :nodoc:
- def initialize values
+ def initialize(values)
@values = values
@indexes = values.each_with_index.find_all { |thing,i|
Arel::Nodes::BindParam === thing
@@ -49,19 +49,18 @@ module ActiveRecord
def sql_for(binds, connection)
val = @values.dup
- binds = connection.prepare_binds_for_database(binds)
- @indexes.each { |i| val[i] = connection.quote(binds.shift) }
+ casted_binds = binds.map(&:value_for_database)
+ @indexes.each { |i| val[i] = connection.quote(casted_binds.shift) }
val.join
end
end
- def self.query(visitor, ast)
- Query.new visitor.accept(ast, Arel::Collectors::SQLString.new).value
+ def self.query(sql)
+ Query.new(sql)
end
- def self.partial_query(visitor, ast, collector)
- collected = visitor.accept(ast, collector).value
- PartialQuery.new collected
+ def self.partial_query(values)
+ PartialQuery.new(values)
end
class Params # :nodoc:
@@ -92,7 +91,7 @@ module ActiveRecord
def self.create(connection, block = Proc.new)
relation = block.call Params.new
bind_map = BindMap.new relation.bound_attributes
- query_builder = connection.cacheable_query relation.arel
+ query_builder = connection.cacheable_query(self, relation.arel)
new query_builder, bind_map
end
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index 0faad48ce3..5fe0d8b5e4 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -42,10 +42,11 @@ module ActiveRecord
end
def associated_table(table_name)
- return self if table_name == arel_table.name
+ association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.singularize)
- association = klass._reflect_on_association(table_name)
- if association && !association.polymorphic?
+ if !association && table_name == arel_table.name
+ return self
+ elsif association && !association.polymorphic?
association_klass = association.klass
arel_table = association_klass.arel_table.alias(table_name)
else
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 0df46d54df..e3e665e149 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -120,7 +120,7 @@ module ActiveRecord
old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
each_local_configuration { |configuration| create configuration }
if old_pool
- ActiveRecord::Base.connection_handler.establish_connection(old_pool.spec)
+ ActiveRecord::Base.connection_handler.establish_connection(old_pool.spec.to_hash)
end
end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index af0c935342..8574807fd2 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -58,6 +58,7 @@ module ActiveRecord
args.concat(["--result-file", "#{filename}"])
args.concat(["--no-data"])
args.concat(["--routines"])
+ args.concat(["--skip-comments"])
args.concat(["#{configuration['database']}"])
run_cmd('mysqldump', args, 'dumping')
@@ -89,7 +90,7 @@ module ActiveRecord
end
def error_class
- if configuration['adapter'] =~ /jdbc/
+ if configuration['adapter'].include?('jdbc')
require 'active_record/railties/jdbcmysql_error'
ArJdbcMySQL::Error
elsif defined?(Mysql2)
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 8b4874044c..b19ab57ee4 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -2,6 +2,7 @@ module ActiveRecord
module Tasks # :nodoc:
class PostgreSQLDatabaseTasks # :nodoc:
DEFAULT_ENCODING = ENV['CHARSET'] || 'utf8'
+ ON_ERROR_STOP_1 = 'ON_ERROR_STOP=1'.freeze
delegate :connection, :establish_connection, :clear_active_connections!,
to: ActiveRecord::Base
@@ -67,7 +68,7 @@ module ActiveRecord
def structure_load(filename)
set_psql_env
- args = [ '-q', '-f', filename, configuration['database'] ]
+ args = [ '-v', ON_ERROR_STOP_1, '-q', '-f', filename, configuration['database'] ]
run_cmd('psql', args, 'loading' )
end
diff --git a/activerecord/lib/active_record/touch_later.rb b/activerecord/lib/active_record/touch_later.rb
index 9a80a63e28..598873ea3a 100644
--- a/activerecord/lib/active_record/touch_later.rb
+++ b/activerecord/lib/active_record/touch_later.rb
@@ -8,7 +8,12 @@ module ActiveRecord
end
def touch_later(*names) # :nodoc:
- raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
+ unless persisted?
+ raise ActiveRecordError, <<-MSG.squish
+ cannot touch on a new or destroyed record object. Consider using
+ persisted?, new_record?, or destroyed? before touching
+ MSG
+ end
@_defer_touch_attrs ||= timestamp_attributes_for_update_in_model
@_defer_touch_attrs |= names
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 77c2845d88..0ee1ebcfbe 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -189,8 +189,8 @@ module ActiveRecord
#
# === Caveats
#
- # If you're on MySQL, then do not use DDL operations in nested transactions
- # blocks that are emulated with savepoints. That is, do not execute statements
+ # If you're on MySQL, then do not use Data Definition Language(DDL) operations in nested
+ # transactions blocks that are emulated with savepoints. That is, do not execute statements
# like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
# releases all savepoints upon executing a DDL operation. When +transaction+
# is finished and tries to release the savepoint it created earlier, a
@@ -480,11 +480,11 @@ module ActiveRecord
# Updates the attributes on this particular Active Record object so that
# if it's associated with a transaction, then the state of the Active Record
- # object will be updated to reflect the current state of the transaction
+ # object will be updated to reflect the current state of the transaction.
#
# The +@transaction_state+ variable stores the states of the associated
# transaction. This relies on the fact that a transaction can only be in
- # one rollback or commit (otherwise a list of states would be required)
+ # one rollback or commit (otherwise a list of states would be required).
# Each Active Record object inside of a transaction carries that transaction's
# TransactionState.
#
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 1f59276137..ec9f498c40 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -65,8 +65,6 @@ module ActiveRecord
column = klass.columns_hash[attribute_name]
cast_type = klass.type_for_attribute(attribute_name)
- value = cast_type.serialize(value)
- value = klass.connection.type_cast(value)
comparison = if !options[:case_sensitive] && !value.nil?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
@@ -77,11 +75,9 @@ module ActiveRecord
if value.nil?
klass.unscoped.where(comparison)
else
- bind = Relation::QueryAttribute.new(attribute_name, value, Type::Value.new)
+ bind = Relation::QueryAttribute.new(attribute_name, value, cast_type)
klass.unscoped.where(comparison, bind)
end
- rescue RangeError
- klass.none
end
def scope_relation(record, table, relation)
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index 4e5872b585..de03550ec2 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -1,4 +1,5 @@
require 'rails/generators/active_record'
+require 'active_support/core_ext/regexp'
module ActiveRecord
module Generators # :nodoc:
@@ -60,7 +61,7 @@ module ActiveRecord
# A migration file name can only contain underscores (_), lowercase characters,
# and numbers 0-9. Any other file name will raise an IllegalMigrationNameError.
def validate_file_name!
- unless file_name =~ /^[_a-z0-9]+$/
+ unless /^[_a-z0-9]+$/.match?(file_name)
raise IllegalMigrationNameError.new(file_name)
end
end
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index f191eff5bf..0d72913258 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -21,14 +21,14 @@ module ActiveRecord
end
def create_model_file
- template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
generate_application_record
+ template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
end
def create_module_file
return if regular_class_path.empty?
- template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke
generate_application_record
+ template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke
end
hook_for :test_framework
@@ -48,7 +48,7 @@ module ActiveRecord
# Used by the migration template to determine the parent name of the model
def parent_class_name
- options[:parent] || determine_default_parent_class
+ options[:parent] || 'ApplicationRecord'
end
def application_record_exist?
@@ -64,14 +64,6 @@ module ActiveRecord
'app/models/application_record.rb'
end
end
-
- def determine_default_parent_class
- if application_record_exist?
- "ApplicationRecord"
- else
- "ActiveRecord::Base"
- end
- end
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
index 668c07dacb..c8028b6b36 100644
--- a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
@@ -49,6 +49,6 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase
test "schema dump includes collation" do
output = dump_table_schema("charset_collations")
assert_match %r{t.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output
- assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output
+ assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+collation: "ucs2_unicode_ci"$}, output
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 43957791b1..57ea8258d1 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -103,3 +103,24 @@ module ActiveRecord
end
end
end
+
+class Mysql2AnsiQuotesTest < ActiveRecord::Mysql2TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.execute("SET SESSION sql_mode='ANSI_QUOTES'")
+ end
+
+ def teardown
+ @connection.reconnect!
+ end
+
+ def test_primary_key_method_with_ansi_quotes
+ assert_equal "id", @connection.primary_key("topics")
+ end
+
+ def test_foreign_keys_method_with_ansi_quotes
+ fks = @connection.foreign_keys("lessons_students")
+ assert_equal([["lessons_students", "students", :cascade]],
+ fks.map {|fk| [fk.from_table, fk.to_table, fk.on_delete] })
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 380a90d765..70792e937c 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -189,8 +189,13 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
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))
+ end
+
+ def test_attribute_for_inspect_for_array_field_for_large_array
record = PgArray.new { |a| a.ratings = (1..11).to_a }
- assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]", record.attribute_for_inspect(:ratings))
+ assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]", record.attribute_for_inspect(:ratings))
end
def test_escaping
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 29bf2c15ea..b00f8f5706 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -7,14 +7,14 @@ class PostgreSQLExplainTest < ActiveRecord::PostgreSQLTestCase
def test_explain_for_one_query
explain = Developer.where(id: 1).explain
- assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = \$?1), explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
assert_match %(QUERY PLAN), explain
end
def test_explain_with_eager_loading
explain = Developer.where(id: 1).includes(:audit_logs).explain
assert_match %(QUERY PLAN), explain
- assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = \$?1), explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index a1a6e5f16a..4d25bd615d 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -7,13 +7,13 @@ class SQLite3ExplainTest < ActiveRecord::SQLite3TestCase
def test_explain_for_one_query
explain = Developer.where(id: 1).explain
- assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\?|1)), explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\? \[\["id", 1\]\]|1)), explain
assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
end
def test_explain_with_eager_loading
explain = Developer.where(id: 1).includes(:audit_logs).explain
- assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\?|1)), explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\? \[\["id", 1\]\]|1)), explain
assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
assert_match(/(SCAN )?TABLE audit_logs/, explain)
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index f3ec2b98d3..fe2425b845 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -3,98 +3,92 @@ require 'bigdecimal'
require 'yaml'
require 'securerandom'
-module ActiveRecord
- module ConnectionAdapters
- class SQLite3Adapter
- class QuotingTest < ActiveRecord::SQLite3TestCase
- def setup
- @conn = ActiveRecord::Base.connection
- end
-
- def test_type_cast_binary_encoding_without_logger
- @conn.extend(Module.new { def logger; end })
- binary = SecureRandom.hex
- expected = binary.dup.encode!(Encoding::UTF_8)
- assert_equal expected, @conn.type_cast(binary)
- end
-
- def test_type_cast_symbol
- assert_equal 'foo', @conn.type_cast(:foo)
- end
-
- def test_type_cast_date
- date = Date.today
- expected = @conn.quoted_date(date)
- assert_equal expected, @conn.type_cast(date)
- end
-
- def test_type_cast_time
- time = Time.now
- expected = @conn.quoted_date(time)
- assert_equal expected, @conn.type_cast(time)
- end
-
- def test_type_cast_numeric
- assert_equal 10, @conn.type_cast(10)
- assert_equal 2.2, @conn.type_cast(2.2)
- end
-
- def test_type_cast_nil
- assert_equal nil, @conn.type_cast(nil)
- end
-
- def test_type_cast_true
- assert_equal 't', @conn.type_cast(true)
- end
-
- def test_type_cast_false
- assert_equal 'f', @conn.type_cast(false)
- end
-
- def test_type_cast_bigdecimal
- bd = BigDecimal.new '10.0'
- assert_equal bd.to_f, @conn.type_cast(bd)
- end
-
- def test_type_cast_unknown_should_raise_error
- obj = Class.new.new
- assert_raise(TypeError) { @conn.type_cast(obj) }
- end
-
- def test_type_cast_object_which_responds_to_quoted_id
- quoted_id_obj = Class.new {
- def quoted_id
- "'zomg'"
- end
-
- def id
- 10
- end
- }.new
- assert_equal 10, @conn.type_cast(quoted_id_obj)
-
- quoted_id_obj = Class.new {
- def quoted_id
- "'zomg'"
- end
- }.new
- assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) }
- end
-
- def test_quoting_binary_strings
- value = "hello".encode('ascii-8bit')
- type = Type::String.new
-
- assert_equal "'hello'", @conn.quote(type.serialize(value))
- end
-
- def test_quoted_time_returns_date_qualified_time
- value = ::Time.utc(2000, 1, 1, 12, 30, 0, 999999)
- type = Type::Time.new
-
- assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value))
- end
+class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
+ def setup
+ @conn = ActiveRecord::Base.connection
+ end
+
+ def test_type_cast_binary_encoding_without_logger
+ @conn.extend(Module.new { def logger; end })
+ binary = SecureRandom.hex
+ expected = binary.dup.encode!(Encoding::UTF_8)
+ assert_equal expected, @conn.type_cast(binary)
+ end
+
+ def test_type_cast_symbol
+ assert_equal 'foo', @conn.type_cast(:foo)
+ end
+
+ def test_type_cast_date
+ date = Date.today
+ expected = @conn.quoted_date(date)
+ assert_equal expected, @conn.type_cast(date)
+ end
+
+ def test_type_cast_time
+ time = Time.now
+ expected = @conn.quoted_date(time)
+ assert_equal expected, @conn.type_cast(time)
+ end
+
+ def test_type_cast_numeric
+ assert_equal 10, @conn.type_cast(10)
+ assert_equal 2.2, @conn.type_cast(2.2)
+ end
+
+ def test_type_cast_nil
+ assert_equal nil, @conn.type_cast(nil)
+ end
+
+ def test_type_cast_true
+ assert_equal 't', @conn.type_cast(true)
+ end
+
+ def test_type_cast_false
+ assert_equal 'f', @conn.type_cast(false)
+ end
+
+ def test_type_cast_bigdecimal
+ bd = BigDecimal.new '10.0'
+ assert_equal bd.to_f, @conn.type_cast(bd)
+ end
+
+ def test_type_cast_unknown_should_raise_error
+ obj = Class.new.new
+ assert_raise(TypeError) { @conn.type_cast(obj) }
+ end
+
+ def test_type_cast_object_which_responds_to_quoted_id
+ quoted_id_obj = Class.new {
+ def quoted_id
+ "'zomg'"
+ end
+
+ def id
+ 10
end
- end
+ }.new
+ assert_equal 10, @conn.type_cast(quoted_id_obj)
+
+ quoted_id_obj = Class.new {
+ def quoted_id
+ "'zomg'"
+ end
+ }.new
+ assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) }
+ end
+
+ def test_quoting_binary_strings
+ value = "hello".encode('ascii-8bit')
+ type = ActiveRecord::Type::String.new
+
+ assert_equal "'hello'", @conn.quote(type.serialize(value))
+ end
+
+ def test_quoted_time_returns_date_qualified_time
+ value = ::Time.utc(2000, 1, 1, 12, 30, 0, 999999)
+ type = ActiveRecord::Type::Time.new
+
+ assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value))
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
index 559b951109..24cc6875ab 100644
--- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
@@ -1,24 +1,20 @@
require 'cases/helper'
-module ActiveRecord::ConnectionAdapters
- class SQLite3Adapter
- class StatementPoolTest < ActiveRecord::SQLite3TestCase
- if Process.respond_to?(:fork)
- def test_cache_is_per_pid
+class SQLite3StatementPoolTest < ActiveRecord::SQLite3TestCase
+ if Process.respond_to?(:fork)
+ def test_cache_is_per_pid
- cache = StatementPool.new(10)
- cache['foo'] = 'bar'
- assert_equal 'bar', cache['foo']
+ cache = ActiveRecord::ConnectionAdapters::SQLite3Adapter::StatementPool.new(10)
+ cache['foo'] = 'bar'
+ assert_equal 'bar', cache['foo']
- pid = fork {
- lookup = cache['foo'];
- exit!(!lookup)
- }
+ pid = fork {
+ lookup = cache['foo'];
+ exit!(!lookup)
+ }
- Process.waitpid pid
- assert $?.success?, 'process should exit successfully'
- end
- end
+ Process.waitpid pid
+ assert $?.success?, 'process should exit successfully'
end
end
end
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index 5536702f58..8a728902a8 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -138,6 +138,11 @@ class AggregationsTest < ActiveRecord::TestCase
assert_equal 'Barnoit GUMBLEAU', customers(:barney).fullname.to_s
assert_kind_of Fullname, customers(:barney).fullname
end
+
+ def test_assigning_hash_to_custom_converter
+ customers(:barney).fullname = { first: "Barney", last: "Stinson" }
+ assert_equal "Barney STINSON", customers(:barney).name
+ end
end
class OverridingAggregationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 7f2a2229ee..80d9a6083b 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -90,6 +90,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_no_queries { authors.map(&:post) }
end
+ def test_calculate_with_string_in_from_and_eager_loading
+ assert_equal 10, Post.from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").count
+ end
+
def test_with_two_tables_in_from_without_getting_double_quoted
posts = Post.select("posts.*").from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").order("posts.id").to_a
assert_equal 2, posts.first.comments.size
@@ -1341,6 +1345,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_nothing_raised do
authors(:david).essays.includes(:writer).any?
+ authors(:david).essays.includes(:writer).exists?
end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 1bbca84bb2..a959f3c06a 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -19,6 +19,7 @@ require 'models/professor'
require 'models/treasure'
require 'models/price_estimate'
require 'models/club'
+require 'models/user'
require 'models/member'
require 'models/membership'
require 'models/sponsor'
@@ -156,7 +157,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
warning = capture(:stderr) do
country.treaties << treaty
end
- assert_no_match(/WARNING: Rails does not support composite primary key\./, warning)
+ assert_no_match(/WARNING: Active Record does not support composite primary key\./, warning)
end
def test_has_and_belongs_to_many
@@ -995,4 +996,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
Project.first.developers_required_by_default.create!(name: "Sean", salary: 50000)
end
end
+
+ def test_association_name_is_the_same_as_join_table_name
+ user = User.create!
+ assert_nothing_raised { user.jobs_pool.clear }
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 7ec0dfce7a..113131b28c 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -244,8 +244,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_do_not_call_callbacks_for_delete_all
car = Car.create(:name => 'honda')
car.funky_bulbs.create!
+ assert_equal 1, car.funky_bulbs.count
assert_nothing_raised { car.reload.funky_bulbs.delete_all }
- assert_equal 0, Bulb.count, "bulbs should have been deleted using :delete_all strategy"
+ assert_equal 0, car.funky_bulbs.count, "bulbs should have been deleted using :delete_all strategy"
end
def test_delete_all_on_association_is_the_same_as_not_loaded
@@ -438,8 +439,24 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal person, person.readers.first.person
end
- def force_signal37_to_load_all_clients_of_firm
- companies(:first_firm).clients_of_firm.each {|f| }
+ def test_update_all_respects_association_scope
+ person = Person.new
+ person.first_name = 'Naruto'
+ person.references << Reference.new
+ person.id = 10
+ person.references
+ person.save!
+ assert_equal 1, person.references.update_all(favourite: true)
+ end
+
+ def test_exists_respects_association_scope
+ person = Person.new
+ person.first_name = 'Sasuke'
+ person.references << Reference.new
+ person.id = 10
+ person.references
+ person.save!
+ assert_predicate person.references, :exists?
end
# sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
@@ -604,6 +621,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_find_ids_and_inverse_of
force_signal37_to_load_all_clients_of_firm
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
firm = companies(:first_firm)
client = firm.clients_of_firm.find(3)
assert_kind_of Client, client
@@ -728,6 +747,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_adding
force_signal37_to_load_all_clients_of_firm
+
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
natural = Client.new("name" => "Natural Company")
companies(:first_firm).clients_of_firm << natural
assert_equal 3, companies(:first_firm).clients_of_firm.size # checking via the collection
@@ -784,6 +806,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_adding_a_collection
force_signal37_to_load_all_clients_of_firm
+
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")])
assert_equal 4, companies(:first_firm).clients_of_firm.size
assert_equal 4, companies(:first_firm).clients_of_firm.reload.size
@@ -927,6 +952,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_create
force_signal37_to_load_all_clients_of_firm
+
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client")
assert new_client.persisted?
assert_equal new_client, companies(:first_firm).clients_of_firm.last
@@ -946,6 +974,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_deleting
force_signal37_to_load_all_clients_of_firm
+
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)
assert_equal 1, companies(:first_firm).clients_of_firm.size
assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
@@ -1100,6 +1131,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_deleting_a_collection
force_signal37_to_load_all_clients_of_firm
+
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
assert_equal 3, companies(:first_firm).clients_of_firm.size
companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1], companies(:first_firm).clients_of_firm[2]])
@@ -1109,6 +1143,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_delete_all
force_signal37_to_load_all_clients_of_firm
+
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
companies(:first_firm).dependent_clients_of_firm.create("name" => "Another Client")
clients = companies(:first_firm).dependent_clients_of_firm.to_a
assert_equal 3, clients.count
@@ -1120,6 +1157,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_delete_all_with_not_yet_loaded_association_collection
force_signal37_to_load_all_clients_of_firm
+
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
assert_equal 3, companies(:first_firm).clients_of_firm.size
companies(:first_firm).clients_of_firm.reset
@@ -1308,6 +1348,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_deleting_a_item_which_is_not_in_the_collection
force_signal37_to_load_all_clients_of_firm
+
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
summit = Client.find_by_name('Summit')
companies(:first_firm).clients_of_firm.delete(summit)
assert_equal 2, companies(:first_firm).clients_of_firm.size
@@ -1344,6 +1387,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_destroying
force_signal37_to_load_all_clients_of_firm
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
assert_difference "Client.count", -1 do
companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first)
end
@@ -1355,6 +1400,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_destroying_by_integer_id
force_signal37_to_load_all_clients_of_firm
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
assert_difference "Client.count", -1 do
companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id)
end
@@ -1366,6 +1413,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_destroying_by_string_id
force_signal37_to_load_all_clients_of_firm
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
assert_difference "Client.count", -1 do
companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id.to_s)
end
@@ -1376,6 +1425,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_destroying_a_collection
force_signal37_to_load_all_clients_of_firm
+
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
assert_equal 3, companies(:first_firm).clients_of_firm.size
@@ -1389,6 +1441,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_destroy_all
force_signal37_to_load_all_clients_of_firm
+
+ assert_predicate companies(:first_firm).clients_of_firm, :loaded?
+
clients = companies(:first_firm).clients_of_firm.to_a
assert !clients.empty?, "37signals has clients after load"
destroyed = companies(:first_firm).clients_of_firm.destroy_all
@@ -2407,4 +2462,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [bulb.id], car.bulb_ids
assert_no_queries { car.bulb_ids }
end
+
+ private
+
+ def force_signal37_to_load_all_clients_of_firm
+ companies(:first_firm).clients_of_firm.load_target
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index c9d9e29f09..1574f373c2 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -659,4 +659,22 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_deprecated { firm.account(true) }
end
+
+ class SpecialBook < ActiveRecord::Base
+ self.table_name = 'books'
+ belongs_to :author, class_name: 'SpecialAuthor'
+ end
+
+ class SpecialAuthor < ActiveRecord::Base
+ self.table_name = 'authors'
+ has_one :book, class_name: 'SpecialBook', foreign_key: 'author_id'
+ end
+
+ def test_assocation_enum_works_properly
+ author = SpecialAuthor.create!(name: 'Test')
+ book = SpecialBook.create!(status: 'published')
+ author.book = book
+
+ refute_equal 0, SpecialAuthor.joins(:book).where(books: { status: 'published' } ).count
+ end
end
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index b3fe759ad9..a2158e4f3b 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -86,7 +86,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
end
def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions
- real_count = Author.all.to_a.select {|a| a.posts.any? {|p| p.title =~ /^Welcome/} }.length
+ real_count = Author.all.to_a.select {|a| a.posts.any? {|p| p.title.start_with?('Welcome')} }.length
authors_with_welcoming_post_titles = Author.all.merge!(joins: :posts, where: "posts.title like 'Welcome%'").distinct.calculate(:count, 'authors.id')
assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'"
end
diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb
index 4af791b758..e3b257efb2 100644
--- a/activerecord/test/cases/associations/left_outer_join_association_test.rb
+++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb
@@ -5,6 +5,7 @@ require 'models/author'
require 'models/essay'
require 'models/categorization'
require 'models/person'
+require 'active_support/core_ext/regexp'
class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
fixtures :authors, :essays, :posts, :comments, :categorizations, :people
@@ -20,7 +21,7 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
Person.left_outer_joins(:agents => {:agents => :agents})
.left_outer_joins(:agents => {:agents => {:primary_contact => :agents}}).to_a
end
- assert queries.any? { |sql| /agents_people_4/i =~ sql }
+ assert queries.any? { |sql| /agents_people_4/i.match?(sql) }
end
end
@@ -36,12 +37,12 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
def test_construct_finder_sql_ignores_empty_left_outer_joins_hash
queries = capture_sql { Author.left_outer_joins({}) }
- assert queries.none? { |sql| /LEFT OUTER JOIN/i =~ sql }
+ assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
end
def test_construct_finder_sql_ignores_empty_left_outer_joins_array
queries = capture_sql { Author.left_outer_joins([]) }
- assert queries.none? { |sql| /LEFT OUTER JOIN/i =~ sql }
+ assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
end
def test_left_outer_joins_forbids_to_use_string_as_argument
@@ -50,8 +51,8 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
def test_join_conditions_added_to_join_clause
queries = capture_sql { Author.left_outer_joins(:essays).to_a }
- assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1)/i =~ sql }
- assert queries.none? { |sql| /WHERE/i =~ sql }
+ assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1|\:a1)/i.match?(sql) }
+ assert queries.none? { |sql| /WHERE/i.match?(sql) }
end
def test_find_with_sti_join
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 01a058918a..7efacb44f3 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -45,7 +45,6 @@ class AssociationsTest < ActiveRecord::TestCase
ship = Ship.create!(:name => "The good ship Dollypop")
part = ship.parts.create!(:name => "Mast")
part.mark_for_destruction
- ship.parts.send(:load_target)
assert ship.parts[0].marked_for_destruction?
end
@@ -54,7 +53,6 @@ class AssociationsTest < ActiveRecord::TestCase
part = ship.parts.create!(:name => "Mast")
part.mark_for_destruction
ShipPart.find(part.id).update_columns(name: 'Deck')
- ship.parts.send(:load_target)
assert_equal 'Deck', ship.parts[0].name
end
@@ -183,6 +181,14 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert !david.projects.loaded?
end
+ def test_load_does_load_target
+ david = developers(:david)
+
+ assert !david.projects.loaded?
+ david.projects.load
+ assert david.projects.loaded?
+ end
+
def test_inspect_does_not_reload_a_not_yet_loaded_target
andreas = Developer.new :name => 'Andreas', :log => 'new developer added'
assert !andreas.audit_logs.loaded?
@@ -248,6 +254,8 @@ class AssociationProxyTest < ActiveRecord::TestCase
test "first! works on loaded associations" do
david = authors(:david)
assert_equal david.posts.first, david.posts.reload.first!
+ assert david.posts.loaded?
+ assert_no_queries { david.posts.first! }
end
def test_reset_unloads_target
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 1db52af59b..9d66c9964c 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -27,15 +27,34 @@ class AttributeMethodsTest < ActiveRecord::TestCase
ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers)
end
- def test_attribute_for_inspect
+ test 'attribute_for_inspect with a string' do
t = topics(:first)
t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters"
- assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on)
assert_equal '"The First Topic Now Has A Title With\nNewlines And ..."', t.attribute_for_inspect(:title)
end
- def test_attribute_present
+ test 'attribute_for_inspect with a date' do
+ t = topics(:first)
+
+ assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on)
+ end
+
+ test 'attribute_for_inspect with an array' do
+ t = topics(:first)
+ t.content = [Object.new]
+
+ assert_match %r(\[#<Object:0x[0-9a-f]+>\]), t.attribute_for_inspect(:content)
+ end
+
+ test 'attribute_for_inspect with a long array' do
+ t = topics(:first)
+ t.content = (1..11).to_a
+
+ assert_equal "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]", t.attribute_for_inspect(:content)
+ end
+
+ test 'attribute_present' do
t = Topic.new
t.title = "hello there!"
t.written_on = Time.now
@@ -46,7 +65,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert !t.attribute_present?("author_name")
end
- def test_attribute_present_with_booleans
+ test 'attribute_present with booleans' do
b1 = Boolean.new
b1.value = false
assert b1.attribute_present?(:value)
@@ -64,44 +83,44 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert Boolean.find(b4.id).attribute_present?(:value)
end
- def test_caching_nil_primary_key
+ test 'caching a nil primary key' do
klass = Class.new(Minimalistic)
assert_called(klass, :reset_primary_key, returns: nil) do
2.times { klass.primary_key }
end
end
- def test_attribute_keys_on_new_instance
+ test 'attribute keys on a new instance' do
t = Topic.new
assert_equal nil, t.title, "The topics table has a title column, so it should be nil"
assert_raise(NoMethodError) { t.title2 }
end
- def test_boolean_attributes
+ test 'boolean attributes' do
assert !Topic.find(1).approved?
assert Topic.find(2).approved?
end
- def test_set_attributes
+ test 'set attributes' do
topic = Topic.find(1)
- topic.attributes = { "title" => "Budget", "author_name" => "Jason" }
+ topic.attributes = { title: 'Budget', author_name: 'Jason' }
topic.save
- assert_equal("Budget", topic.title)
- assert_equal("Jason", topic.author_name)
+ assert_equal('Budget', topic.title)
+ assert_equal('Jason', topic.author_name)
assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address)
end
- def test_set_attributes_without_hash
+ test 'set attributes without a hash' do
topic = Topic.new
assert_raise(ArgumentError) { topic.attributes = '' }
end
- def test_integers_as_nil
- test = AutoId.create('value' => '')
+ test 'integers as nil' do
+ test = AutoId.create(value: '')
assert_nil AutoId.find(test.id).value
end
- def test_set_attributes_with_block
+ test 'set attributes with a block' do
topic = Topic.new do |t|
t.title = "Budget"
t.author_name = "Jason"
@@ -111,7 +130,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal("Jason", topic.author_name)
end
- def test_respond_to?
+ test 'respond_to?' do
topic = Topic.find(1)
assert_respond_to topic, "title"
assert_respond_to topic, "title?"
@@ -125,7 +144,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert !topic.respond_to?(:nothingness)
end
- def test_respond_to_with_custom_primary_key
+ test 'respond_to? with a custom primary key' do
keyboard = Keyboard.create
assert_not_nil keyboard.key_number
assert_equal keyboard.key_number, keyboard.id
@@ -133,7 +152,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert keyboard.respond_to?('id')
end
- def test_id_before_type_cast_with_custom_primary_key
+ test 'id_before_type_cast with a custom primary key' do
keyboard = Keyboard.create
keyboard.key_number = '10'
assert_equal '10', keyboard.id_before_type_cast
@@ -142,8 +161,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal '10', keyboard.read_attribute_before_type_cast(:key_number)
end
- # Syck calls respond_to? before actually calling initialize
- def test_respond_to_with_allocated_object
+ # Syck calls respond_to? before actually calling initialize.
+ test 'respond_to? with an allocated object' do
klass = Class.new(ActiveRecord::Base) do
self.table_name = 'topics'
end
@@ -155,31 +174,32 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_respond_to topic, :title
end
- # IRB inspects the return value of "MyModel.allocate".
- def test_allocated_object_can_be_inspected
+ # IRB inspects the return value of MyModel.allocate.
+ test 'allocated objects can be inspected' do
topic = Topic.allocate
assert_equal "#<Topic not initialized>", topic.inspect
end
- def test_array_content
+ test 'array content' do
+ content = %w( one two three )
topic = Topic.new
- topic.content = %w( one two three )
+ topic.content = content
topic.save
- assert_equal(%w( one two three ), Topic.find(topic.id).content)
+ assert_equal content, Topic.find(topic.id).content
end
- def test_read_attributes_before_type_cast
- category = Category.new({:name=>"Test category", :type => nil})
- category_attrs = {"name"=>"Test category", "id" => nil, "type" => nil, "categorizations_count" => nil}
- assert_equal category_attrs , category.attributes_before_type_cast
+ test 'read attributes_before_type_cast' do
+ category = Category.new(name: 'Test category', type: nil)
+ category_attrs = { 'name' => 'Test category', 'id' => nil, 'type' => nil, 'categorizations_count' => nil }
+ assert_equal category_attrs, category.attributes_before_type_cast
end
if current_adapter?(:Mysql2Adapter)
- def test_read_attributes_before_type_cast_on_boolean
+ test 'read attributes_before_type_cast on a boolean' do
bool = Boolean.create!({ "value" => false })
- if RUBY_PLATFORM =~ /java/
- # JRuby will return the value before typecast as string
+ if RUBY_PLATFORM.include?('java')
+ # JRuby will return the value before typecast as string.
assert_equal "0", bool.reload.attributes_before_type_cast["value"]
else
assert_equal 0, bool.reload.attributes_before_type_cast["value"]
@@ -187,7 +207,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_read_attributes_before_type_cast_on_datetime
+ test 'read attributes_before_type_cast on a datetime' do
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
@@ -202,7 +222,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_read_attributes_after_type_cast_on_datetime
+ test 'read attributes_after_type_cast on a date' do
tz = "Pacific Time (US & Canada)"
in_time_zone tz do
@@ -223,7 +243,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_hash_content
+ test 'hash content' do
topic = Topic.new
topic.content = { "one" => 1, "two" => 2 }
topic.save
@@ -237,7 +257,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal 3, Topic.find(topic.id).content["three"]
end
- def test_update_array_content
+ test 'update array content' do
topic = Topic.new
topic.content = %w( one two three )
@@ -251,14 +271,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal(%w( one two three four five ), topic.content)
end
- def test_case_sensitive_attributes_hash
- # DB2 is not case-sensitive
+ test 'case-sensitive attributes hash' do
+ # DB2 is not case-sensitive.
return true if current_adapter?(:DB2Adapter)
assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.first.attributes
end
- def test_attributes_without_primary_key
+ test 'attributes without primary key' do
klass = Class.new(ActiveRecord::Base) do
self.table_name = 'developers_projects'
end
@@ -267,9 +287,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_not klass.new.has_attribute?('id')
end
- def test_hashes_not_mangled
- new_topic = { :title => "New Topic" }
- new_topic_values = { :title => "AnotherTopic" }
+ test 'hashes are not mangled' do
+ new_topic = { title: 'New Topic' }
+ new_topic_values = { title: 'AnotherTopic' }
topic = Topic.new(new_topic)
assert_equal new_topic[:title], topic.title
@@ -278,13 +298,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal new_topic_values[:title], topic.title
end
- def test_create_through_factory
- topic = Topic.create("title" => "New Topic")
+ test 'create through factory' do
+ topic = Topic.create(title: 'New Topic')
topicReloaded = Topic.find(topic.id)
assert_equal(topic, topicReloaded)
end
- def test_write_attribute
+ test 'write_attribute' do
topic = Topic.new
topic.send(:write_attribute, :title, "Still another topic")
assert_equal "Still another topic", topic.title
@@ -299,7 +319,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "Still another topic: part 4", topic.title
end
- def test_read_attribute
+ test 'read_attribute' do
topic = Topic.new
topic.title = "Don't change the topic"
assert_equal "Don't change the topic", topic.read_attribute("title")
@@ -309,7 +329,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "Don't change the topic", topic[:title]
end
- def test_read_attribute_raises_missing_attribute_error_when_not_exists
+ test 'read_attribute raises ActiveModel::MissingAttributeError when the attribute does not exist' do
computer = Computer.select('id').first
assert_raises(ActiveModel::MissingAttributeError) { computer[:developer] }
assert_raises(ActiveModel::MissingAttributeError) { computer[:extendedWarranty] }
@@ -317,7 +337,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_nothing_raised { computer[:developer] = 'Hello!' }
end
- def test_read_attribute_when_false
+ test 'read_attribute when false' do
topic = topics(:first)
topic.approved = false
assert !topic.approved?, "approved should be false"
@@ -325,7 +345,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert !topic.approved?, "approved should be false"
end
- def test_read_attribute_when_true
+ test 'read_attribute when true' do
topic = topics(:first)
topic.approved = true
assert topic.approved?, "approved should be true"
@@ -333,7 +353,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert topic.approved?, "approved should be true"
end
- def test_read_write_boolean_attribute
+ test 'boolean attributes writing and reading' do
topic = Topic.new
topic.approved = "false"
assert !topic.approved?, "approved should be false"
@@ -348,7 +368,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert topic.approved?, "approved should be true"
end
- def test_overridden_write_attribute
+ test 'overridden write_attribute' do
topic = Topic.new
def topic.write_attribute(attr_name, value)
super(attr_name, value.downcase)
@@ -367,7 +387,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "yet another topic: part 4", topic.title
end
- def test_overridden_read_attribute
+ test 'overridden read_attribute' do
topic = Topic.new
topic.title = "Stop changing the topic"
def topic.read_attribute(attr_name)
@@ -381,40 +401,40 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "STOP CHANGING THE TOPIC", topic[:title]
end
- def test_read_overridden_attribute
- topic = Topic.new(:title => 'a')
+ test 'read overridden attribute' do
+ topic = Topic.new(title: 'a')
def topic.title() 'b' end
assert_equal 'a', topic[:title]
end
- def test_query_attribute_string
+ test 'string attribute predicate' do
[nil, "", " "].each do |value|
- assert_equal false, Topic.new(:author_name => value).author_name?
+ assert_equal false, Topic.new(author_name: value).author_name?
end
- assert_equal true, Topic.new(:author_name => "Name").author_name?
+ assert_equal true, Topic.new(author_name: 'Name').author_name?
end
- def test_query_attribute_number
- [nil, 0, "0"].each do |value|
- assert_equal false, Developer.new(:salary => value).salary?
+ test 'number attribute predicate' do
+ [nil, 0, '0'].each do |value|
+ assert_equal false, Developer.new(salary: value).salary?
end
- assert_equal true, Developer.new(:salary => 1).salary?
- assert_equal true, Developer.new(:salary => "1").salary?
+ assert_equal true, Developer.new(salary: 1).salary?
+ assert_equal true, Developer.new(salary: '1').salary?
end
- def test_query_attribute_boolean
+ test 'boolean attribute predicate' do
[nil, "", false, "false", "f", 0].each do |value|
- assert_equal false, Topic.new(:approved => value).approved?
+ assert_equal false, Topic.new(approved: value).approved?
end
[true, "true", "1", 1].each do |value|
- assert_equal true, Topic.new(:approved => value).approved?
+ assert_equal true, Topic.new(approved: value).approved?
end
end
- def test_query_attribute_with_custom_fields
+ test 'custom field attribute predicate' do
object = Company.find_by_sql(<<-SQL).first
SELECT c1.*, c2.type as string_value, c2.rating as int_value
FROM companies c1, companies c2
@@ -435,23 +455,23 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert !object.int_value?
end
- def test_non_attribute_access_and_assignment
+ test 'non-attribute read and write' do
topic = Topic.new
assert !topic.respond_to?("mumbo")
assert_raise(NoMethodError) { topic.mumbo }
assert_raise(NoMethodError) { topic.mumbo = 5 }
end
- def test_undeclared_attribute_method_does_not_affect_respond_to_and_method_missing
- topic = @target.new(:title => 'Budget')
+ test 'undeclared attribute method does not affect respond_to? and method_missing' do
+ topic = @target.new(title: 'Budget')
assert topic.respond_to?('title')
assert_equal 'Budget', topic.title
assert !topic.respond_to?('title_hello_world')
assert_raise(NoMethodError) { topic.title_hello_world }
end
- def test_declared_prefixed_attribute_method_affects_respond_to_and_method_missing
- topic = @target.new(:title => 'Budget')
+ test 'declared prefixed attribute method affects respond_to? and method_missing' do
+ topic = @target.new(title: 'Budget')
%w(default_ title_).each do |prefix|
@target.class_eval "def #{prefix}attribute(*args) args end"
@target.attribute_method_prefix prefix
@@ -464,11 +484,11 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_declared_suffixed_attribute_method_affects_respond_to_and_method_missing
+ test 'declared suffixed attribute method affects respond_to? and method_missing' do
%w(_default _title_default _it! _candidate= able?).each do |suffix|
@target.class_eval "def attribute#{suffix}(*args) args end"
@target.attribute_method_suffix suffix
- topic = @target.new(:title => 'Budget')
+ topic = @target.new(title: 'Budget')
meth = "title#{suffix}"
assert topic.respond_to?(meth)
@@ -478,11 +498,11 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_declared_affixed_attribute_method_affects_respond_to_and_method_missing
+ test 'declared affixed attribute method affects respond_to? and method_missing' do
[['mark_', '_for_update'], ['reset_', '!'], ['default_', '_value?']].each do |prefix, suffix|
@target.class_eval "def #{prefix}attribute#{suffix}(*args) args end"
- @target.attribute_method_affix({ :prefix => prefix, :suffix => suffix })
- topic = @target.new(:title => 'Budget')
+ @target.attribute_method_affix(prefix: prefix, suffix: suffix)
+ topic = @target.new(title: 'Budget')
meth = "#{prefix}title#{suffix}"
assert topic.respond_to?(meth)
@@ -492,38 +512,38 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_should_unserialize_attributes_for_frozen_records
- myobj = {:value1 => :value2}
- topic = Topic.create("content" => myobj)
+ test 'should unserialize attributes for frozen records' do
+ myobj = { value1: :value2 }
+ topic = Topic.create(content: myobj)
topic.freeze
assert_equal myobj, topic.content
end
- def test_typecast_attribute_from_select_to_false
- Topic.create(:title => 'Budget')
- # Oracle does not support boolean expressions in SELECT
+ test 'typecast attribute from select to false' do
+ Topic.create(title: 'Budget')
+ # Oracle does not support boolean expressions in SELECT.
if current_adapter?(:OracleAdapter, :FbAdapter)
- topic = Topic.all.merge!(:select => "topics.*, 0 as is_test").first
+ topic = Topic.all.merge!(select: 'topics.*, 0 as is_test').first
else
- topic = Topic.all.merge!(:select => "topics.*, 1=2 as is_test").first
+ topic = Topic.all.merge!(select: 'topics.*, 1=2 as is_test').first
end
assert !topic.is_test?
end
- def test_typecast_attribute_from_select_to_true
- Topic.create(:title => 'Budget')
- # Oracle does not support boolean expressions in SELECT
+ test 'typecast attribute from select to true' do
+ Topic.create(title: 'Budget')
+ # Oracle does not support boolean expressions in SELECT.
if current_adapter?(:OracleAdapter, :FbAdapter)
- topic = Topic.all.merge!(:select => "topics.*, 1 as is_test").first
+ topic = Topic.all.merge!(select: 'topics.*, 1 as is_test').first
else
- topic = Topic.all.merge!(:select => "topics.*, 2=2 as is_test").first
+ topic = Topic.all.merge!(select: 'topics.*, 2=2 as is_test').first
end
assert topic.is_test?
end
- def test_raises_dangerous_attribute_error_when_defining_activerecord_method_in_model
+ test 'raises ActiveRecord::DangerousAttributeError when defining an AR method in a model' do
%w(save create_or_update).each do |method|
- klass = Class.new ActiveRecord::Base
+ klass = Class.new(ActiveRecord::Base)
klass.class_eval "def #{method}() 'defined #{method}' end"
assert_raise ActiveRecord::DangerousAttributeError do
klass.instance_method_already_implemented?(method)
@@ -531,7 +551,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_converted_values_are_returned_after_assignment
+ test 'converted values are returned after assignment' do
developer = Developer.new(name: 1337, salary: "50000")
assert_equal "50000", developer.salary_before_type_cast
@@ -546,7 +566,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "1337", developer.name
end
- def test_write_nil_to_time_attributes
+ test 'write nil to time attribute' do
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
record.written_on = nil
@@ -554,7 +574,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_write_time_to_date_attributes
+ test 'write time to date attribute' do
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
record.last_read = Time.utc(2010, 1, 1, 10)
@@ -562,7 +582,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_time_attributes_are_retrieved_in_current_time_zone
+ test 'time attributes are retrieved in the current time zone' do
in_time_zone "Pacific Time (US & Canada)" do
utc_time = Time.utc(2008, 1, 1)
record = @target.new
@@ -574,7 +594,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_setting_time_zone_aware_attribute_to_utc
+ test 'setting a time zone-aware attribute to UTC' do
in_time_zone "Pacific Time (US & Canada)" do
utc_time = Time.utc(2008, 1, 1)
record = @target.new
@@ -585,7 +605,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_setting_time_zone_aware_attribute_in_other_time_zone
+ test 'setting time zone-aware attribute in other time zone' do
utc_time = Time.utc(2008, 1, 1)
cst_time = utc_time.in_time_zone("Central Time (US & Canada)")
in_time_zone "Pacific Time (US & Canada)" do
@@ -597,18 +617,18 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_setting_time_zone_aware_read_attribute
+ test 'setting time zone-aware read attribute' do
utc_time = Time.utc(2008, 1, 1)
cst_time = utc_time.in_time_zone("Central Time (US & Canada)")
in_time_zone "Pacific Time (US & Canada)" do
- record = @target.create(:written_on => cst_time).reload
+ record = @target.create(written_on: cst_time).reload
assert_equal utc_time, record[:written_on]
assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record[:written_on].time_zone
assert_equal Time.utc(2007, 12, 31, 16), record[:written_on].time
end
end
- def test_setting_time_zone_aware_attribute_with_string
+ test 'setting time zone-aware attribute with a string' do
utc_time = Time.utc(2008, 1, 1)
(-11..13).each do |timezone_offset|
time_string = utc_time.in_time_zone(timezone_offset).to_s
@@ -622,9 +642,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_time_zone_aware_attribute_saved
+ test 'time zone-aware attribute saved' do
in_time_zone 1 do
- record = @target.create(:written_on => '2012-02-20 10:00')
+ record = @target.create(written_on: '2012-02-20 10:00')
record.written_on = '2012-02-20 09:00'
record.save
@@ -632,7 +652,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_setting_time_zone_aware_attribute_to_blank_string_returns_nil
+ test 'setting a time zone-aware attribute to a blank string returns nil' do
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
record.written_on = ' '
@@ -641,7 +661,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_setting_time_zone_aware_attribute_interprets_time_zone_unaware_string_in_time_zone
+ test 'setting a time zone-aware attribute interprets time zone-unaware string in time zone' do
time_string = 'Tue Jan 01 00:00:00 2008'
(-11..13).each do |timezone_offset|
in_time_zone timezone_offset do
@@ -654,7 +674,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_setting_time_zone_aware_datetime_in_current_time_zone
+ test 'setting a time zone-aware datetime in the current time zone' do
utc_time = Time.utc(2008, 1, 1)
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
@@ -665,7 +685,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_yaml_dumping_record_with_time_zone_aware_attribute
+ test 'YAML dumping a record with time zone-aware attribute' do
in_time_zone "Pacific Time (US & Canada)" do
record = Topic.new(id: 1)
record.written_on = "Jan 01 00:00:00 2014"
@@ -673,7 +693,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_setting_time_zone_aware_time_in_current_time_zone
+ test 'setting a time zone-aware time in the current time zone' do
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
time_string = "10:00:00"
@@ -688,7 +708,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_setting_time_zone_aware_time_with_dst
+ test 'setting a time zone-aware time with DST' do
in_time_zone "Pacific Time (US & Canada)" do
current_time = Time.zone.local(2014, 06, 15, 10)
record = @target.new(bonus_time: current_time)
@@ -702,7 +722,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_removing_time_zone_aware_types
+ test 'removing time zone-aware types' do
with_time_zone_aware_types(:datetime) do
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new(bonus_time: "10:00:00")
@@ -714,14 +734,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_time_zone_aware_attributes_dont_recurse_infinitely_on_invalid_values
+ test 'time zone-aware attributes do not recurse infinitely on invalid values' do
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new(bonus_time: [])
assert_equal nil, record.bonus_time
end
end
- def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable
+ test 'setting a time_zone_conversion_for_attributes should write the value on a class variable' do
Topic.skip_time_zone_conversion_for_attributes = [:field_a]
Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b]
@@ -729,44 +749,44 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal [:field_b], Minimalistic.skip_time_zone_conversion_for_attributes
end
- def test_read_attributes_respect_access_control
- privatize("title")
+ test 'attribute readers respect access control' do
+ privatize('title')
- topic = @target.new(:title => "The pros and cons of programming naked.")
+ topic = @target.new(title: 'The pros and cons of programming naked.')
assert !topic.respond_to?(:title)
exception = assert_raise(NoMethodError) { topic.title }
- assert exception.message.include?("private method")
+ assert exception.message.include?('private method')
assert_equal "I'm private", topic.send(:title)
end
- def test_write_attributes_respect_access_control
- privatize("title=(value)")
+ test 'attribute writers respect access control' do
+ privatize('title=(value)')
topic = @target.new
assert !topic.respond_to?(:title=)
- exception = assert_raise(NoMethodError) { topic.title = "Pants"}
- assert exception.message.include?("private method")
- topic.send(:title=, "Very large pants")
+ exception = assert_raise(NoMethodError) { topic.title = 'Pants' }
+ assert exception.message.include?('private method')
+ topic.send(:title=, 'Very large pants')
end
- def test_question_attributes_respect_access_control
- privatize("title?")
+ test 'attribute predicates respect access control' do
+ privatize('title?')
- topic = @target.new(:title => "Isaac Newton's pants")
+ topic = @target.new(title: "Isaac Newton's pants")
assert !topic.respond_to?(:title?)
exception = assert_raise(NoMethodError) { topic.title? }
- assert exception.message.include?("private method")
+ assert exception.message.include?('private method')
assert topic.send(:title?)
end
- def test_bulk_update_respects_access_control
- privatize("title=(value)")
+ test 'bulk updates respect access control' do
+ privatize('title=(value)')
- assert_raise(ActiveRecord::UnknownAttributeError) { @target.new(:title => "Rants about pants") }
- assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } }
+ assert_raise(ActiveRecord::UnknownAttributeError) { @target.new(title: 'Rants about pants') }
+ assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { title: 'Ants in pants' } }
end
- def test_bulk_update_raise_unknown_attribute_error
+ test 'bulk update raises ActiveRecord::UnknownAttributeError' do
error = assert_raises(ActiveRecord::UnknownAttributeError) {
Topic.new(hello: "world")
}
@@ -775,20 +795,20 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "unknown attribute 'hello' for Topic.", error.message
end
- def test_methods_override_in_multi_level_subclass
+ test 'method overrides in multi-level subclasses' do
klass = Class.new(Developer) do
def name
"dev:#{read_attribute(:name)}"
end
end
- 2.times { klass = Class.new klass }
+ 2.times { klass = Class.new(klass) }
dev = klass.new(name: 'arthurnn')
dev.save!
assert_equal 'dev:arthurnn', dev.reload.name
end
- def test_global_methods_are_overwritten
+ test 'global methods are overwritten' do
klass = Class.new(ActiveRecord::Base) do
self.table_name = 'computers'
end
@@ -798,8 +818,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_nil computer.system
end
- def test_global_methods_are_overwritten_when_subclassing
- klass = Class.new(ActiveRecord::Base) { self.abstract_class = true }
+ test 'global methods are overwritten when subclassing' do
+ klass = Class.new(ActiveRecord::Base) do
+ self.abstract_class = true
+ end
subklass = Class.new(klass) do
self.table_name = 'computers'
@@ -811,7 +833,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_nil computer.system
end
- def test_instance_method_should_be_defined_on_the_base_class
+ test 'instance methods should be defined on the base class' do
subklass = Class.new(Topic)
Topic.define_attribute_methods
@@ -827,14 +849,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert subklass.method_defined?(:id), "subklass is missing id method"
end
- def test_read_attribute_with_nil_should_not_asplode
- assert_equal nil, Topic.new.read_attribute(nil)
+ test 'read_attribute with nil should not asplode' do
+ assert_nil Topic.new.read_attribute(nil)
end
# If B < A, and A defines an accessor for 'foo', we don't want to override
# that by defining a 'foo' method in the generated methods module for B.
# (That module will be inserted between the two, e.g. [B, <GeneratedAttributes>, A].)
- def test_inherited_custom_accessors
+ test 'inherited custom accessors' do
klass = new_topic_like_ar_class do
self.abstract_class = true
def title; "omg"; end
@@ -850,7 +872,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "lol", topic.author_name
end
- def test_inherited_custom_accessors_with_reserved_names
+ test 'inherited custom accessors with reserved names' do
klass = Class.new(ActiveRecord::Base) do
self.table_name = 'computers'
self.abstract_class = true
@@ -868,7 +890,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal 99, computer.developer
end
- def test_on_the_fly_super_invokable_generated_attribute_methods_via_method_missing
+ test 'on_the_fly_super_invokable_generated_attribute_methods_via_method_missing' do
klass = new_topic_like_ar_class do
def title
super + '!'
@@ -879,7 +901,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal real_topic.title + '!', klass.find(real_topic.id).title
end
- def test_on_the_fly_super_invokable_generated_predicate_attribute_methods_via_method_missing
+ test 'on-the-fly super-invokable generated attribute predicates via method_missing' do
klass = new_topic_like_ar_class do
def title?
!super
@@ -890,7 +912,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal !real_topic.title?, klass.find(real_topic.id).title?
end
- def test_calling_super_when_parent_does_not_define_method_raises_error
+ test 'calling super when the parent does not define method raises NoMethodError' do
klass = new_topic_like_ar_class do
def some_method_that_is_not_on_super
super
@@ -902,38 +924,38 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_attribute_method?
+ test 'attribute_method?' do
assert @target.attribute_method?(:title)
assert @target.attribute_method?(:title=)
assert_not @target.attribute_method?(:wibble)
end
- def test_attribute_method_returns_false_if_table_does_not_exist
+ test 'attribute_method? returns false if the table does not exist' do
@target.table_name = 'wibble'
assert_not @target.attribute_method?(:title)
end
- def test_attribute_names_on_new_record
+ test 'attribute_names on a new record' do
model = @target.new
assert_equal @target.column_names, model.attribute_names
end
- def test_attribute_names_on_queried_record
+ test 'attribute_names on a queried record' do
model = @target.last!
assert_equal @target.column_names, model.attribute_names
end
- def test_attribute_names_with_custom_select
+ test 'attribute_names with a custom select' do
model = @target.select('id').last!
assert_equal ['id'], model.attribute_names
- # Sanity check, make sure other columns exist
+ # Sanity check, make sure other columns exist.
assert_not_equal ['id'], @target.column_names
end
- def test_came_from_user
+ test 'came_from_user?' do
model = @target.first
assert_not model.id_came_from_user?
@@ -941,7 +963,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert model.id_came_from_user?
end
- def test_accessed_fields
+ test 'accessed_fields' do
model = @target.first
assert_equal [], model.accessed_fields
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
index a24a4fc6a4..b1b8639696 100644
--- a/activerecord/test/cases/attribute_test.rb
+++ b/activerecord/test/cases/attribute_test.rb
@@ -242,5 +242,12 @@ module ActiveRecord
attribute.with_value_from_user(1)
end
end
+
+ test "with_type preserves mutations" do
+ attribute = Attribute.from_database(:foo, "", Type::Value.new)
+ attribute.value << "1"
+
+ assert_equal 1, attribute.with_type(Type::Integer.new).value
+ end
end
end
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 7bcaa53aa2..604411da97 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -205,5 +205,49 @@ module ActiveRecord
assert_equal(:bar, child.new(foo: :bar).foo)
end
+
+ test "attributes not backed by database columns are not dirty when unchanged" do
+ refute OverloadedType.new.non_existent_decimal_changed?
+ end
+
+ test "attributes not backed by database columns are always initialized" do
+ OverloadedType.create!
+ model = OverloadedType.first
+
+ assert_nil model.non_existent_decimal
+ model.non_existent_decimal = "123"
+ assert_equal 123, model.non_existent_decimal
+ end
+
+ test "attributes not backed by database columns return the default on models loaded from database" do
+ child = Class.new(OverloadedType) do
+ attribute :non_existent_decimal, :decimal, default: 123
+ end
+ child.create!
+ model = child.first
+
+ assert_equal 123, model.non_existent_decimal
+ end
+
+ test "attributes not backed by database columns properly interact with mutation and dirty" do
+ child = Class.new(ActiveRecord::Base) do
+ self.table_name = "topics"
+ attribute :foo, :string, default: "lol"
+ end
+ child.create!
+ model = child.first
+
+ assert_equal "lol", model.foo
+
+ model.foo << "asdf"
+ assert_equal "lolasdf", model.foo
+ assert model.foo_changed?
+
+ model.reload
+ assert_equal "lol", model.foo
+
+ model.foo = "lol"
+ refute model.changed?
+ end
end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 3191393a41..7b1ce40c0d 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -14,7 +14,6 @@ require 'models/auto_id'
require 'models/boolean'
require 'models/column_name'
require 'models/subscriber'
-require 'models/keyboard'
require 'models/comment'
require 'models/minimalistic'
require 'models/warehouse_thing'
@@ -25,7 +24,6 @@ require 'models/joke'
require 'models/bird'
require 'models/car'
require 'models/bulb'
-require 'rexml/document'
require 'concurrent/atomic/count_down_latch'
class FirstAbstractClass < ActiveRecord::Base
@@ -1034,12 +1032,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal t1.title, t2.title
end
- def test_reload_with_exclusive_scope
- dev = DeveloperCalledDavid.first
- dev.update!(name: "NotDavid" )
- assert_equal dev, dev.reload
- end
-
def test_switching_between_table_name
k = Class.new(Joke)
@@ -1504,6 +1496,10 @@ class BasicsTest < ActiveRecord::TestCase
assert_not_equal Post.new.hash, Post.new.hash
end
+ test "records of different classes have different hashes" do
+ assert_not_equal Post.new(id: 1).hash, Developer.new(id: 1).hash
+ end
+
test "resetting column information doesn't remove attribute methods" do
topic = topics(:first)
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 91ff5146fd..8a4c1bd615 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -68,10 +68,26 @@ class EachTest < ActiveRecord::TestCase
end
end
- def test_warn_if_limit_scope_is_set
- assert_called(ActiveRecord::Base.logger, :warn) do
- Post.limit(1).find_each { |post| post }
+ test 'find_each should honor limit if passed a block' do
+ limit = @total - 1
+ total = 0
+
+ Post.limit(limit).find_each do |post|
+ total += 1
+ end
+
+ assert_equal limit, total
+ end
+
+ test 'find_each should honor limit if no block is passed' do
+ limit = @total - 1
+ total = 0
+
+ Post.limit(limit).find_each.each do |post|
+ total += 1
end
+
+ assert_equal limit, total
end
def test_warn_if_order_scope_is_set
@@ -84,7 +100,7 @@ class EachTest < ActiveRecord::TestCase
previous_logger = ActiveRecord::Base.logger
ActiveRecord::Base.logger = nil
assert_nothing_raised do
- Post.limit(1).find_each { |post| post }
+ Post.order('comments_count DESC').find_each { |post| post }
end
ensure
ActiveRecord::Base.logger = previous_logger
@@ -170,28 +186,28 @@ class EachTest < ActiveRecord::TestCase
end
end
- def test_find_in_batches_should_not_error_if_config_overriden
- # Set the config option which will be overriden
- prev = ActiveRecord::Base.error_on_ignored_order_or_limit
- ActiveRecord::Base.error_on_ignored_order_or_limit = true
+ def test_find_in_batches_should_not_error_if_config_overridden
+ # Set the config option which will be overridden
+ prev = ActiveRecord::Base.error_on_ignored_order
+ ActiveRecord::Base.error_on_ignored_order = true
assert_nothing_raised do
PostWithDefaultScope.find_in_batches(error_on_ignore: false){}
end
ensure
# Set back to default
- ActiveRecord::Base.error_on_ignored_order_or_limit = prev
+ ActiveRecord::Base.error_on_ignored_order = prev
end
def test_find_in_batches_should_error_on_config_specified_to_error
# Set the config option
- prev = ActiveRecord::Base.error_on_ignored_order_or_limit
- ActiveRecord::Base.error_on_ignored_order_or_limit = true
+ prev = ActiveRecord::Base.error_on_ignored_order
+ ActiveRecord::Base.error_on_ignored_order = true
assert_raise(ArgumentError) do
PostWithDefaultScope.find_in_batches(){}
end
ensure
# Set back to default
- ActiveRecord::Base.error_on_ignored_order_or_limit = prev
+ ActiveRecord::Base.error_on_ignored_order = prev
end
def test_find_in_batches_should_not_error_by_default
@@ -249,6 +265,28 @@ class EachTest < ActiveRecord::TestCase
end
end
+ test 'find_in_batches should honor limit if passed a block' do
+ limit = @total - 1
+ total = 0
+
+ Post.limit(limit).find_in_batches do |batch|
+ total += batch.size
+ end
+
+ assert_equal limit, total
+ end
+
+ test 'find_in_batches should honor limit if no block is passed' do
+ limit = @total - 1
+ total = 0
+
+ Post.limit(limit).find_in_batches.each do |batch|
+ total += batch.size
+ end
+
+ assert_equal limit, total
+ end
+
def test_in_batches_should_not_execute_any_query
assert_no_queries do
assert_kind_of ActiveRecord::Batches::BatchEnumerator, Post.in_batches(of: 2)
@@ -486,4 +524,94 @@ class EachTest < ActiveRecord::TestCase
assert_equal 1, Post.find_in_batches(:batch_size => 10_000).size
end
end
+
+ [true, false].each do |load|
+ test "in_batches should return limit records when limit is less than batch size and load is #{load}" do
+ limit = 3
+ batch_size = 5
+ total = 0
+
+ Post.limit(limit).in_batches(of: batch_size, load: load) do |batch|
+ total += batch.count
+ end
+
+ assert_equal limit, total
+ end
+
+ test "in_batches should return limit records when limit is greater than batch size and load is #{load}" do
+ limit = 5
+ batch_size = 3
+ total = 0
+
+ Post.limit(limit).in_batches(of: batch_size, load: load) do |batch|
+ total += batch.count
+ end
+
+ assert_equal limit, total
+ end
+
+ test "in_batches should return limit records when limit is a multiple of the batch size and load is #{load}" do
+ limit = 6
+ batch_size = 3
+ total = 0
+
+ Post.limit(limit).in_batches(of: batch_size, load: load) do |batch|
+ total += batch.count
+ end
+
+ assert_equal limit, total
+ end
+
+ test "in_batches should return no records if the limit is 0 and load is #{load}" do
+ limit = 0
+ batch_size = 1
+ total = 0
+
+ Post.limit(limit).in_batches(of: batch_size, load: load) do |batch|
+ total += batch.count
+ end
+
+ assert_equal limit, total
+ end
+
+ test "in_batches should return all if the limit is greater than the number of records when load is #{load}" do
+ limit = @total + 1
+ batch_size = 1
+ total = 0
+
+ Post.limit(limit).in_batches(of: batch_size, load: load) do |batch|
+ total += batch.count
+ end
+
+ assert_equal @total, total
+ end
+ end
+
+ test '.error_on_ignored_order_or_limit= is deprecated' do
+ begin
+ prev = ActiveRecord::Base.error_on_ignored_order
+ assert_deprecated 'Please use error_on_ignored_order= instead.' do
+ ActiveRecord::Base.error_on_ignored_order_or_limit = true
+ end
+ assert ActiveRecord::Base.error_on_ignored_order
+ ensure
+ ActiveRecord::Base.error_on_ignored_order = prev
+ end
+ end
+
+ test '.error_on_ignored_order_or_limit is deprecated' do
+ expected = ActiveRecord::Base.error_on_ignored_order
+ actual = assert_deprecated 'Please use error_on_ignored_order instead.' do
+ ActiveRecord::Base.error_on_ignored_order_or_limit
+ end
+ assert_equal expected, actual
+ end
+
+ test '#error_on_ignored_order_or_limit is deprecated' do
+ expected = ActiveRecord::Base.error_on_ignored_order
+ actual = assert_deprecated 'Please use error_on_ignored_order instead.' do
+ Post.new.error_on_ignored_order_or_limit
+ end
+ assert_equal expected, actual
+ end
end
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index fa924fe4cb..3f01885489 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -57,10 +57,13 @@ module ActiveRecord
end
def test_logs_bind_vars_after_type_cast
+ binds = [Relation::QueryAttribute.new("id", "10", Type::Integer.new)]
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
payload = {
:name => 'SQL',
:sql => 'select * from topics where id = ?',
- :binds => [Relation::QueryAttribute.new("id", "10", Type::Integer.new)]
+ :binds => binds,
+ :type_casted_binds => type_casted_binds
}
event = ActiveSupport::Notifications::Event.new(
'foo',
@@ -84,6 +87,12 @@ module ActiveRecord
logger.sql event
assert_match([[@pk.name, 10]].inspect, logger.debugs.first)
end
+
+ private
+
+ def type_cast(value)
+ ActiveRecord::Base.connection.type_cast(value)
+ end
end
end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index cfae700159..6acfec0621 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "models/book"
require 'models/club'
require 'models/company'
require "models/contract"
@@ -25,7 +26,7 @@ class NumericData < ActiveRecord::Base
end
class CalculationsTest < ActiveRecord::TestCase
- fixtures :companies, :accounts, :topics, :speedometers, :minivans
+ fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books
def test_should_sum_field
assert_equal 318, Account.sum(:credit_limit)
@@ -793,4 +794,8 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 50, result[1].credit_limit
assert_equal 50, result[2].credit_limit
end
+
+ def test_group_by_attribute_with_custom_type
+ assert_equal({ "proposed" => 2, "published" => 2 }, Book.group(:status).count)
+ end
end
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 4f70ae3a1d..8a722b4f22 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -31,7 +31,7 @@ class CallbackDeveloper < ActiveRecord::Base
end
ActiveRecord::Callbacks::CALLBACKS.each do |callback_method|
- next if callback_method.to_s =~ /^around_/
+ next if callback_method.to_s.start_with?('around_')
define_callback_method(callback_method)
ActiveSupport::Deprecation.silence { send(callback_method, callback_string(callback_method)) }
send(callback_method, callback_proc(callback_method))
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index 81162b7e98..78c0853992 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -60,15 +60,8 @@ module ActiveRecord
end
def test_should_not_set_default_for_blob_and_text_data_types
- assert_raise ArgumentError do
- MySQL::Column.new("title", "a", SqlTypeMetadata.new(sql_type: "blob"))
- end
-
text_type = MySQL::TypeMetadata.new(
SqlTypeMetadata.new(type: :text))
- assert_raise ArgumentError do
- MySQL::Column.new("title", "Hello", text_type)
- end
text_column = MySQL::Column.new("title", nil, text_type)
assert_equal nil, text_column.default
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 50f942f5aa..a019cc6490 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -5,16 +5,15 @@ module ActiveRecord
class ConnectionHandlerTest < ActiveRecord::TestCase
def setup
@handler = ConnectionHandler.new
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new Base.configurations
@spec_name = "primary"
- @pool = @handler.establish_connection(resolver.spec(:arunit, @spec_name))
+ @pool = @handler.establish_connection(ActiveRecord::Base.configurations['arunit'])
end
def test_establish_connection_uses_spec_name
config = {"readonly" => {"adapter" => 'sqlite3'}}
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config)
spec = resolver.spec(:readonly)
- @handler.establish_connection(spec)
+ @handler.establish_connection(spec.to_hash)
assert_not_nil @handler.retrieve_connection_pool('readonly')
ensure
diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
index 9ee92a3cd2..f25b85e8a7 100644
--- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -27,7 +27,7 @@ module ActiveRecord
ENV['DATABASE_URL'] = "postgres://localhost/foo"
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
actual = resolve_spec(:default_env, config)
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost", "name"=>"default_env" }
assert_equal expected, actual
end
@@ -37,7 +37,7 @@ module ActiveRecord
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
actual = resolve_spec(:foo, config)
- expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" }
+ expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost","name"=>"foo" }
assert_equal expected, actual
end
@@ -47,7 +47,7 @@ module ActiveRecord
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
actual = resolve_spec(:foo, config)
- expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" }
+ expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost","name"=>"foo" }
assert_equal expected, actual
end
@@ -55,7 +55,7 @@ module ActiveRecord
ENV['DATABASE_URL'] = "postgres://localhost/foo"
config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
actual = resolve_spec(:production, config)
- expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost" }
+ expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost", "name"=>"production" }
assert_equal expected, actual
end
@@ -93,7 +93,7 @@ module ActiveRecord
ENV['DATABASE_URL'] = "ibm-db://localhost/foo"
config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
actual = resolve_spec(:default_env, config)
- expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost" }
+ expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost", "name"=>"default_env" }
assert_equal expected, actual
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index a45ee281c7..bbcb42d58e 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -151,7 +151,7 @@ module ActiveRecord
assert_equal 1, active_connections(@pool).size
ensure
- @pool.connections.each(&:close)
+ @pool.connections.each { |conn| conn.close if conn.in_use? }
end
def test_remove_connection
@@ -341,6 +341,18 @@ module ActiveRecord
end
end
+ def test_connection_notification_is_called
+ payloads = []
+ subscription = ActiveSupport::Notifications.subscribe('!connection.active_record') do |name, started, finished, unique_id, payload|
+ payloads << payload
+ end
+ ActiveRecord::Base.establish_connection :arunit
+ assert_equal [:config, :connection_id, :spec_name], payloads[0].keys.sort
+ assert_equal 'primary', payloads[0][:spec_name]
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscription) if subscription
+ end
+
def test_pool_sets_connection_schema_cache
connection = pool.checkout
schema_cache = SchemaCache.new connection
@@ -432,6 +444,9 @@ module ActiveRecord
Thread.new { @pool.send(group_action_method) }.join
# assert connection has been forcefully taken away from us
assert_not @pool.active_connection?
+
+ # make a new connection for with_connection to clean up
+ @pool.connection
end
end
end
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index 3bddaf32ec..b30a83d9ce 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -28,7 +28,8 @@ module ActiveRecord
assert_equal({
"adapter" => "abstract",
"host" => "foo",
- "encoding" => "utf8" }, spec)
+ "encoding" => "utf8",
+ "name" => "production"}, spec)
end
def test_url_sub_key
@@ -36,7 +37,8 @@ module ActiveRecord
assert_equal({
"adapter" => "abstract",
"host" => "foo",
- "encoding" => "utf8" }, spec)
+ "encoding" => "utf8",
+ "name" => "production"}, spec)
end
def test_url_sub_key_merges_correctly
@@ -46,7 +48,8 @@ module ActiveRecord
"adapter" => "abstract",
"host" => "foo",
"encoding" => "utf8",
- "pool" => "3" }, spec)
+ "pool" => "3",
+ "name" => "production"}, spec)
end
def test_url_host_no_db
@@ -113,7 +116,8 @@ module ActiveRecord
assert_equal({
"adapter" => "sqlite3",
"database" => "foo",
- "encoding" => "utf8" }, spec)
+ "encoding" => "utf8",
+ "name" => "production"}, spec)
end
def test_spec_name_on_key_lookup
diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/date_test.rb
index 426a350379..f112081c14 100644
--- a/activerecord/test/cases/invalid_date_test.rb
+++ b/activerecord/test/cases/date_test.rb
@@ -1,7 +1,19 @@
require 'cases/helper'
require 'models/topic'
-class InvalidDateTest < ActiveRecord::TestCase
+class DateTest < ActiveRecord::TestCase
+ def test_date_with_time_value
+ time_value = Time.new(2016, 05, 11, 19, 0, 0)
+ topic = Topic.create(last_read: time_value)
+ assert_equal topic, Topic.find_by(last_read: time_value)
+ end
+
+ def test_date_with_string_value
+ string_value = '2016-05-11 19:00:00'
+ topic = Topic.create(last_read: string_value)
+ assert_equal topic, Topic.find_by(last_read: string_value)
+ end
+
def test_assign_valid_dates
valid_dates = [[2007, 11, 30], [1993, 2, 28], [2008, 2, 29]]
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index a3f8d26100..3c58b6ad09 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -5,23 +5,6 @@ require 'models/parrot'
require 'models/person' # For optimistic locking
require 'models/aircraft'
-class Pirate # Just reopening it, not defining it
- attr_accessor :detected_changes_in_after_update # Boolean for if changes are detected
- attr_accessor :changes_detected_in_after_update # Actual changes
-
- after_update :check_changes
-
-private
- # after_save/update and the model itself
- # can end up checking dirty status and acting on the results
- def check_changes
- if self.changed?
- self.detected_changes_in_after_update = true
- self.changes_detected_in_after_update = self.changes
- end
- end
-end
-
class NumericData < ActiveRecord::Base
self.table_name = 'numeric_data'
end
@@ -621,7 +604,7 @@ class DirtyTest < ActiveRecord::TestCase
jon = Person.create! first_name: 'Jon'
end
- assert ActiveRecord::SQLCounter.log_all.none? { |sql| sql =~ /followers_count/ }
+ assert ActiveRecord::SQLCounter.log_all.none? { |sql| sql.include?('followers_count') }
jon.reload
assert_equal 'Jon', jon.first_name
@@ -734,6 +717,17 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal "arr", pirate.catchphrase
end
+ test "attributes assigned but not selected are dirty" do
+ person = Person.select(:id).first
+ refute person.changed?
+
+ person.first_name = "Sean"
+ assert person.changed?
+
+ person.first_name = nil
+ assert person.changed?
+ end
+
private
def with_partial_writes(klass, on = true)
old = klass.partial_writes?
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index babacd1ee9..e781b60464 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -421,4 +421,8 @@ class EnumTest < ActiveRecord::TestCase
book = Book.new
assert book.hard?
end
+
+ test "data type of Enum type" do
+ assert_equal :integer, Book.type_for_attribute('status').type
+ end
end
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index 64dfd86ce2..6409290e8d 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -46,11 +46,8 @@ if ActiveRecord::Base.connection.supports_explain?
end
def test_exec_explain_with_binds
- object = Struct.new(:name)
- cols = [object.new('wadus'), object.new('chaflan')]
-
sqls = %w(foo bar)
- binds = [[[cols[0], 1]], [[cols[1], 2]]]
+ binds = [[bind_param('wadus', 1)], [bind_param('chaflan', 2)]]
queries = sqls.zip(binds)
stub_explain_for_query_plans(["query plan foo\n", "query plan bar\n"]) do
@@ -83,5 +80,8 @@ if ActiveRecord::Base.connection.supports_explain?
end
end
+ def bind_param(name, value)
+ ActiveRecord::Relation::QueryAttribute.new(name, value, ActiveRecord::Type::Value.new)
+ end
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 374a8ba199..6eaaa30cd0 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -173,11 +173,9 @@ class FinderTest < ActiveRecord::TestCase
end
end
- def test_exists_fails_when_parameter_has_invalid_type
- assert_raises(ActiveModel::RangeError) do
- assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int
- end
+ def test_exists_returns_false_when_parameter_has_invalid_type
assert_equal false, Topic.exists?("foo")
+ assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int
end
def test_exists_does_not_select_columns_without_alias
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 9455d4886c..df53bbf950 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -622,6 +622,46 @@ class TransactionalFixturesOnCustomConnectionTest < ActiveRecord::TestCase
end
end
+class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase
+ self.use_transactional_tests = true
+ self.use_instantiated_fixtures = false
+
+ def test_transaction_created_on_connection_notification
+ connection = stub(:transaction_open? => false)
+ connection.expects(:begin_transaction).with(joinable: false)
+ fire_connection_notification(connection)
+ end
+
+ def test_notification_established_transactions_are_rolled_back
+ # Mocha is not thread-safe so define our own stub to test
+ connection = Class.new do
+ attr_accessor :rollback_transaction_called
+ def transaction_open?; true; end
+ def begin_transaction(*args); end
+ def rollback_transaction(*args)
+ @rollback_transaction_called = true
+ end
+ end.new
+ fire_connection_notification(connection)
+ teardown_fixtures
+ assert(connection.rollback_transaction_called, "Expected <mock connection>#rollback_transaction to be called but was not")
+ end
+
+ private
+
+ def fire_connection_notification(connection)
+ ActiveRecord::Base.connection_handler.stubs(:retrieve_connection).with('book').returns(connection)
+ message_bus = ActiveSupport::Notifications.instrumenter
+ payload = {
+ spec_name: 'book',
+ config: nil,
+ connection_id: connection.object_id
+ }
+
+ message_bus.instrument('!connection.active_record', payload) {}
+ end
+end
+
class InvalidTableNameFixturesTest < ActiveRecord::TestCase
fixtures :funny_jokes
# Set to false to blow away fixtures cache and ensure our fixtures are loaded
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index 08a186ae07..97a0cdb5db 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -29,10 +29,30 @@ class IntegrationTest < ActiveRecord::TestCase
assert_equal '4-flamboyant-software', firm.to_param
end
+ def test_to_param_class_method_truncates_words_properly
+ firm = Firm.find(4)
+ firm.name << ', Inc.'
+ assert_equal '4-flamboyant-software', firm.to_param
+ end
+
+ def test_to_param_class_method_truncates_after_parameterize
+ firm = Firm.find(4)
+ firm.name = "Huey, Dewey, & Louie LLC"
+ # 123456789T123456789v
+ assert_equal '4-huey-dewey-louie-llc', firm.to_param
+ end
+
+ def test_to_param_class_method_truncates_after_parameterize_with_hyphens
+ firm = Firm.find(4)
+ firm.name = "Door-to-Door Wash-n-Fold Service"
+ # 123456789T123456789v
+ assert_equal '4-door-to-door-wash-n', firm.to_param
+ end
+
def test_to_param_class_method_truncates
firm = Firm.find(4)
firm.name = 'a ' * 100
- assert_equal '4-a-a-a-a-a-a-a-a-a', firm.to_param
+ assert_equal '4-a-a-a-a-a-a-a-a-a-a', firm.to_param
end
def test_to_param_class_method_truncates_edge_case
@@ -41,10 +61,16 @@ class IntegrationTest < ActiveRecord::TestCase
assert_equal '4-david', firm.to_param
end
+ def test_to_param_class_method_truncates_case_shown_in_doc
+ firm = Firm.find(4)
+ firm.name = 'David Heinemeier Hansson'
+ assert_equal '4-david-heinemeier', firm.to_param
+ end
+
def test_to_param_class_method_squishes
firm = Firm.find(4)
firm.name = "ab \n" * 100
- assert_equal '4-ab-ab-ab-ab-ab-ab', firm.to_param
+ assert_equal '4-ab-ab-ab-ab-ab-ab-ab', firm.to_param
end
def test_to_param_class_method_multibyte_character
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index e030f6c588..aba854820b 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -151,6 +151,14 @@ module ActiveRecord
end
end
+ class RevertCustomForeignKeyTable < SilentMigration
+ def change
+ change_table(:horses) do |t|
+ t.references :owner, foreign_key: { to_table: :developers }
+ end
+ end
+ end
+
setup do
@verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false
end
@@ -353,6 +361,13 @@ module ActiveRecord
ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = ''
end
+ def test_migrations_can_handle_foreign_keys_to_specific_tables
+ migration = RevertCustomForeignKeyTable.new
+ InvertibleMigration.migrate(:up)
+ migration.migrate(:up)
+ migration.migrate(:down)
+ end
+
# MySQL 5.7 and Oracle do not allow to create duplicate indexes on the same columns
unless current_adapter?(:Mysql2Adapter, :OracleAdapter)
def test_migrate_revert_add_index_with_name
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index 29546525f3..2f71ba870d 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -156,14 +156,7 @@ module ActiveRecord
assert_equal String, bob.bio.class
assert_kind_of Integer, bob.age
assert_equal Time, bob.birthday.class
-
- if current_adapter?(:OracleAdapter)
- # Oracle doesn't differentiate between date/time
- assert_equal Time, bob.favorite_day.class
- else
- assert_equal Date, bob.favorite_day.class
- end
-
+ assert_equal Date, bob.favorite_day.class
assert_instance_of TrueClass, bob.male?
assert_kind_of BigDecimal, bob.wealth
end
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index 01162dcefe..4237e89731 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -232,6 +232,10 @@ module ActiveRecord
t.column :city_id, :integer
end
add_foreign_key :houses, :cities, column: "city_id"
+
+ # remove and re-add to test that schema is updated and not accidentally cached
+ remove_foreign_key :houses, :cities
+ add_foreign_key :houses, :cities, column: "city_id", on_delete: :cascade
end
end
@@ -243,6 +247,15 @@ module ActiveRecord
silence_stream($stdout) { migration.migrate(:down) }
end
+ def test_foreign_key_constraint_is_not_cached_incorrectly
+ migration = CreateCitiesAndHousesMigration.new
+ silence_stream($stdout) { migration.migrate(:up) }
+ output = dump_table_schema "houses"
+ assert_match %r{\s+add_foreign_key "houses",.+on_delete: :cascade$}, output
+ ensure
+ silence_stream($stdout) { migration.migrate(:down) }
+ end
+
class CreateSchoolsAndClassesMigration < ActiveRecord::Migration::Current
def change
create_table(:schools)
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index 70c64f3e71..5dddb35c4c 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -35,7 +35,7 @@ module ActiveRecord
assert_not index_exists?(table_name, :user_id)
end
- def test_create_reference_id_index_even_if_index_option_is_passed
+ def test_create_reference_id_index_even_if_index_option_is_not_passed
add_reference table_name, :user
assert index_exists?(table_name, :user_id)
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 36b6662820..d499aadbae 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -445,21 +445,6 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migrator.migrations_paths = old_path
end
- def test_rename_internal_metadata_table
- original_internal_metadata_table_name = ActiveRecord::Base.internal_metadata_table_name
-
- ActiveRecord::Base.internal_metadata_table_name = "active_record_internal_metadatas"
- Reminder.reset_table_name
-
- ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name
- Reminder.reset_table_name
-
- assert_equal "ar_internal_metadata", ActiveRecord::InternalMetadata.table_name
- ensure
- ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name
- Reminder.reset_table_name
- end
-
def test_proper_table_name_on_migration
reminder_class = new_isolated_reminder_class
migration = ActiveRecord::Migration.new
diff --git a/activerecord/test/cases/mixin_test.rb b/activerecord/test/cases/mixin_test.rb
index 7ebdcac711..06af75af0f 100644
--- a/activerecord/test/cases/mixin_test.rb
+++ b/activerecord/test/cases/mixin_test.rb
@@ -41,13 +41,12 @@ class TouchTest < ActiveRecord::TestCase
old_updated_at = stamped.updated_at
- travel 5.minutes do
- stamped.lft_will_change!
- stamped.save
+ travel 5.minutes
+ stamped.lft_will_change!
+ stamped.save
- assert_equal Time.now, stamped.updated_at
- assert_equal old_updated_at, stamped.created_at
- end
+ assert_equal Time.now, stamped.updated_at
+ assert_equal old_updated_at, stamped.created_at
end
def test_create_turned_off
diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb
index ae18573126..59c340ceb7 100644
--- a/activerecord/test/cases/multiparameter_attributes_test.rb
+++ b/activerecord/test/cases/multiparameter_attributes_test.rb
@@ -11,15 +11,13 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
topic.attributes = attributes
# note that extra #to_date call allows test to pass for Oracle, which
# treats dates/times the same
- assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
+ assert_equal Date.new(2004, 6, 24), topic.last_read.to_date
end
def test_multiparameter_attributes_on_date_with_empty_year
attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "24" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -27,8 +25,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "24" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -36,8 +32,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -45,8 +39,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -54,8 +46,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -63,8 +53,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "24" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -214,6 +202,20 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
Topic.reset_column_information
end
+ def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_and_invalid_time_params
+ with_timezone_config aware_attributes: true do
+ Topic.reset_column_information
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "", "written_on(3i)" => ""
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_nil topic.written_on
+ end
+ ensure
+ Topic.reset_column_information
+ end
+
def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false
with_timezone_config default: :local, aware_attributes: false, zone: -28800 do
attributes = {
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 11fb164d50..8a08056bbe 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -642,13 +642,13 @@ module NestedAttributesOnACollectionAssociationTests
def test_should_not_overwrite_unsaved_updates_when_loading_association
@pirate.reload
@pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
- assert_equal 'Grace OMalley', @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.name
+ assert_equal 'Grace OMalley', @pirate.send(@association_name).load_target.find { |r| r.id == @child_1.id }.name
end
def test_should_preserve_order_when_not_overwriting_unsaved_updates
@pirate.reload
@pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
- assert_equal @child_1.id, @pirate.send(@association_name).send(:load_target).first.id
+ assert_equal @child_1.id, @pirate.send(@association_name).load_target.first.id
end
def test_should_refresh_saved_records_when_not_overwriting_unsaved_updates
@@ -657,13 +657,13 @@ module NestedAttributesOnACollectionAssociationTests
@pirate.send(@association_name) << record
record.save!
@pirate.send(@association_name).last.update!(name: 'Polly')
- assert_equal 'Polly', @pirate.send(@association_name).send(:load_target).last.name
+ assert_equal 'Polly', @pirate.send(@association_name).load_target.last.name
end
def test_should_not_remove_scheduled_destroys_when_loading_association
@pirate.reload
@pirate.send(association_setter, [{ :id => @child_1.id, :_destroy => '1' }])
- assert @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.marked_for_destruction?
+ assert @pirate.send(@association_name).load_target.find { |r| r.id == @child_1.id }.marked_for_destruction?
end
def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 52eac4a124..ea36c199f4 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -174,6 +174,14 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_equal '2', dashboard.id
end
+ def test_create_without_primary_key_no_extra_query
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'dashboards'
+ end
+ klass.create! # warmup schema cache
+ assert_queries(3, ignore_none: true) { klass.create! }
+ end
+
if current_adapter?(:PostgreSQLAdapter)
def test_serial_with_quoted_sequence_name
column = MixedCaseMonkey.columns_hash[MixedCaseMonkey.primary_key]
@@ -247,6 +255,7 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
+ @connection.schema_cache.clear!
@connection.create_table(:barcodes, primary_key: ["region", "code"], force: true) do |t|
t.string :region
t.integer :code
@@ -262,10 +271,15 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
end
def test_primary_key_issues_warning
+ model = Class.new(ActiveRecord::Base) do
+ def self.table_name
+ "barcodes"
+ end
+ end
warning = capture(:stderr) do
- assert_nil @connection.primary_key("barcodes")
+ assert_nil model.primary_key
end
- assert_match(/WARNING: Rails does not support composite primary key\./, warning)
+ assert_match(/WARNING: Active Record does not support composite primary key\./, warning)
end
def test_collectly_dump_composite_primary_key
@@ -275,18 +289,6 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
end
if current_adapter?(:Mysql2Adapter)
- class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase
- self.use_transactional_tests = false
-
- def test_primary_key_method_with_ansi_quotes
- con = ActiveRecord::Base.connection
- con.execute("SET SESSION sql_mode='ANSI_QUOTES'")
- assert_equal "id", con.primary_key("topics")
- ensure
- con.reconnect!
- end
- end
-
class PrimaryKeyBigintNilDefaultTest < ActiveRecord::TestCase
include SchemaDumpingHelper
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 406643d6fb..085636553e 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -6,6 +6,8 @@ require 'models/post'
require 'rack'
class QueryCacheTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
fixtures :tasks, :topics, :categories, :posts, :categories_posts
teardown do
@@ -39,18 +41,6 @@ class QueryCacheTest < ActiveRecord::TestCase
assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on'
end
- def test_exceptional_middleware_assigns_original_connection_id_on_error
- connection_id = ActiveRecord::Base.connection_id
-
- mw = middleware { |env|
- ActiveRecord::Base.connection_id = self.object_id
- raise "lol borked"
- }
- assert_raises(RuntimeError) { mw.call({}) }
-
- assert_equal connection_id, ActiveRecord::Base.connection_id
- end
-
def test_middleware_delegates
called = false
mw = middleware { |env|
@@ -133,7 +123,6 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_is_flat
Task.cache do
- Topic.columns # don't count this query
assert_queries(1) { Topic.find(1); Topic.find(1); }
end
@@ -175,6 +164,22 @@ class QueryCacheTest < ActiveRecord::TestCase
ActiveRecord::Base.configurations = conf
end
+ def test_cache_is_not_available_when_using_a_not_connected_connection
+ spec_name = Task.connection_specification_name
+ conf = ActiveRecord::Base.configurations['arunit'].merge('name' => 'test2')
+ ActiveRecord::Base.connection_handler.establish_connection(conf)
+ Task.connection_specification_name = "test2"
+ refute Task.connected?
+
+ Task.cache do
+ Task.connection # warmup postgresql connection setup queries
+ assert_queries(2) { Task.find(1); Task.find(1) }
+ end
+ ensure
+ ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name)
+ Task.connection_specification_name = spec_name
+ end
+
def test_query_cache_doesnt_leak_cached_results_of_rolled_back_queries
ActiveRecord::Base.connection.enable_query_cache!
post = Post.first
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index c01c82f4f5..225e23bc83 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -149,5 +149,21 @@ module ActiveRecord
assert_equal "1800", @quoter.quote(30.minutes)
end
end
+
+ class QuoteBooleanTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_quote_returns_frozen_string
+ assert_predicate @connection.quote(true), :frozen?
+ assert_predicate @connection.quote(false), :frozen?
+ end
+
+ def test_type_cast_returns_frozen_value
+ assert_predicate @connection.type_cast(true), :frozen?
+ assert_predicate @connection.type_cast(false), :frozen?
+ end
+ end
end
end
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index ffb2da7a26..36cb898010 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -44,8 +44,8 @@ module ActiveRecord
end
test "#_select!" do
- assert relation.public_send("_select!", :foo).equal?(relation)
- assert_equal [:foo], relation.public_send("select_values")
+ assert relation._select!(:foo).equal?(relation)
+ assert_equal [:foo], relation.select_values
end
test '#order!' do
diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb
index dec01dfa76..83dc5347e0 100644
--- a/activerecord/test/cases/result_test.rb
+++ b/activerecord/test/cases/result_test.rb
@@ -22,6 +22,16 @@ module ActiveRecord
], result.to_hash
end
+ test "first returns first row as a hash" do
+ assert_equal(
+ {'col_1' => 'row 1 col 1', 'col_2' => 'row 1 col 2'}, result.first)
+ end
+
+ test "last returns last row as a hash" do
+ assert_equal(
+ {'col_1' => 'row 3 col 1', 'col_2' => 'row 3 col 2'}, result.last)
+ end
+
test "each with block returns row hashes" do
result.each do |row|
assert_equal ['col_1', 'col_2'], row.keys
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 9dc1f5e2c2..12f4196724 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -218,12 +218,17 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.boolean\s+"has_fun",.+default: false}, output
end
- if current_adapter?(:Mysql2Adapter)
- def test_schema_dump_should_add_default_value_for_mysql_text_field
- output = standard_dump
- assert_match %r{t\.text\s+"body",\s+limit: 65535,\s+null: false$}, output
- end
+ def test_schema_dump_does_not_include_limit_for_text_field
+ output = standard_dump
+ assert_match %r{t\.text\s+"params"$}, output
+ end
+ def test_schema_dump_does_not_include_limit_for_binary_field
+ output = standard_dump
+ assert_match %r{t\.binary\s+"data"$}, output
+ end
+
+ if current_adapter?(:Mysql2Adapter)
def test_schema_dump_includes_length_for_mysql_binary_fields
output = standard_dump
assert_match %r{t\.binary\s+"var_binary",\s+limit: 255$}, output
@@ -233,11 +238,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
output = standard_dump
assert_match %r{t\.blob\s+"tiny_blob",\s+limit: 255$}, output
- assert_match %r{t\.binary\s+"normal_blob",\s+limit: 65535$}, output
+ assert_match %r{t\.binary\s+"normal_blob"$}, output
assert_match %r{t\.binary\s+"medium_blob",\s+limit: 16777215$}, output
assert_match %r{t\.binary\s+"long_blob",\s+limit: 4294967295$}, output
assert_match %r{t\.text\s+"tiny_text",\s+limit: 255$}, output
- assert_match %r{t\.text\s+"normal_text",\s+limit: 65535$}, output
+ assert_match %r{t\.text\s+"normal_text"$}, output
assert_match %r{t\.text\s+"medium_text",\s+limit: 16777215$}, output
assert_match %r{t\.text\s+"long_text",\s+limit: 4294967295$}, output
end
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index dcd09b6973..6679f9415b 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -5,6 +5,7 @@ require 'models/developer'
require 'models/computer'
require 'models/vehicle'
require 'models/cat'
+require 'active_support/core_ext/regexp'
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts, :comments
@@ -201,7 +202,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_order_to_unscope_reordering
scope = DeveloperOrderedBySalary.order('salary DESC, name ASC').reverse_order.unscope(:order)
- assert !(scope.to_sql =~ /order/i)
+ assert !/order/i.match?(scope.to_sql)
end
def test_unscope_reverse_order
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 0e277ed235..f0dac07acc 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -46,6 +46,13 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal Topic.average(:replies_count), Topic.base.average(:replies_count)
end
+ def test_calling_merge_at_first_in_scope
+ Topic.class_eval do
+ scope :calling_merge_at_first_in_scope, Proc.new { merge(Topic.replied) }
+ end
+ assert_equal Topic.calling_merge_at_first_in_scope.to_a, Topic.replied.to_a
+ end
+
def test_method_missing_priority_when_delegating
klazz = Class.new(ActiveRecord::Base) do
self.table_name = "topics"
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index c15d57460b..ef46fd5d9a 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -228,6 +228,13 @@ class RelationScopingTest < ActiveRecord::TestCase
assert SpecialComment.all.any?
end
end
+
+ def test_circular_joins_with_current_scope_does_not_crash
+ posts = Post.joins(comments: :post).scoping do
+ Post.current_scope.first(10)
+ end
+ assert_equal posts, Post.joins(comments: :post).first(10)
+ end
end
class NestedRelationScopingTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 8e480bbaee..70e406038f 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -289,7 +289,7 @@ module ActiveRecord
def test_structure_dump
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db").returns(true)
+ Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db").returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
end
@@ -297,7 +297,7 @@ module ActiveRecord
def test_warn_when_external_structure_dump_command_execution_fails
filename = "awesome-file.sql"
Kernel.expects(:system)
- .with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db")
+ .with("mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db")
.returns(false)
e = assert_raise(RuntimeError) {
@@ -308,7 +308,7 @@ module ActiveRecord
def test_structure_dump_with_port_number
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--port=10000", "--result-file", filename, "--no-data", "--routines", "test-db").returns(true)
+ Kernel.expects(:system).with("mysqldump", "--port=10000", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db").returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(
@configuration.merge('port' => 10000),
@@ -317,7 +317,7 @@ module ActiveRecord
def test_structure_dump_with_ssl
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--ssl-ca=ca.crt", "--result-file", filename, "--no-data", "--routines", "test-db").returns(true)
+ Kernel.expects(:system).with("mysqldump", "--ssl-ca=ca.crt", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db").returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(
@configuration.merge("sslca" => "ca.crt"),
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index 6a0c7fbcb5..99d73e91a4 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -288,14 +288,14 @@ module ActiveRecord
def test_structure_load
filename = "awesome-file.sql"
- Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true)
+ Kernel.expects(:system).with('psql', '-v', 'ON_ERROR_STOP=1', '-q', '-f', filename, @configuration['database']).returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
def test_structure_load_accepts_path_with_spaces
filename = "awesome file.sql"
- Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true)
+ Kernel.expects(:system).with('psql', '-v', 'ON_ERROR_STOP=1', '-q', '-f', filename, @configuration['database']).returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 87299c0dab..fcb6552e5b 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -1,5 +1,6 @@
require 'active_support/test_case'
require 'active_support/testing/stream'
+require 'active_support/core_ext/regexp'
module ActiveRecord
# = Active Record Test Case
@@ -12,10 +13,6 @@ module ActiveRecord
SQLCounter.clear_log
end
- def assert_date_from_db(expected, actual, message = nil)
- assert_equal expected.to_s, actual.to_s, message
- end
-
def capture_sql
SQLCounter.clear_log
yield
@@ -119,7 +116,7 @@ module ActiveRecord
return if 'CACHE' == values[:name]
self.class.log_all << sql
- self.class.log << sql unless ignore =~ sql
+ self.class.log << sql unless ignore.match?(sql)
end
end
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index b8307d6665..5b5307489a 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -41,10 +41,8 @@ class I18nValidationTest < ActiveRecord::TestCase
[ "given custom message", {:message => "custom"}, {:message => "custom"}],
[ "given if condition", {:if => lambda { true }}, {}],
[ "given unless condition", {:unless => lambda { false }}, {}],
- [ "given option that is not reserved", {:format => "jpg"}, {:format => "jpg" }]
- # TODO Add :on case, but below doesn't work, because then the validation isn't run for some reason
- # even when using .save instead .valid?
- # [ "given on condition", {on: :save}, {}]
+ [ "given option that is not reserved", {:format => "jpg"}, {:format => "jpg" }],
+ [ "given on condition", {on: [:create, :update] }, {}]
]
COMMON_CASES.each do |name, validation_options, generate_message_options|
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 56909a8630..d1c9a00786 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -109,6 +109,16 @@ class YamlSerializationTest < ActiveRecord::TestCase
assert_equal("Have a nice day", topic.content)
end
+ def test_yaml_encoding_keeps_mutations
+ author = Author.first
+ author.name = "Sean"
+ dumped = YAML.load(YAML.dump(author))
+
+ assert_equal "Sean", dumped.name
+ assert_equal author.name_was, dumped.name_was
+ assert_equal author.changes, dumped.changes
+ end
+
private
def yaml_fixture(file_name)
diff --git a/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb
index 607113b091..76734bcd7d 100644
--- a/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb
+++ b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveHobbies < ActiveRecord::Migration::Current
def self.up
add_column "people", "hobbies", :text
end
diff --git a/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb
index d4cbddab50..7f883dbb45 100644
--- a/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb
+++ b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveDescriptions < ActiveRecord::Migration::Current
def self.up
add_column "people", "description", :text
end
diff --git a/activerecord/test/migrations/to_copy2/2_create_comments.rb b/activerecord/test/migrations/to_copy2/2_create_comments.rb
index 2e9f5ec6bc..d361847d4b 100644
--- a/activerecord/test/migrations/to_copy2/2_create_comments.rb
+++ b/activerecord/test/migrations/to_copy2/2_create_comments.rb
@@ -1,4 +1,4 @@
-class CreateArticles < ActiveRecord::Migration::Current
+class CreateComments < ActiveRecord::Migration::Current
def self.up
end
diff --git a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
index 8f81805fe1..1a863367dd 100644
--- a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
+++ b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveHobbies < ActiveRecord::Migration::Current
def self.up
add_column "people", "hobbies", :string
end
diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
index 607113b091..76734bcd7d 100644
--- a/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
+++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveHobbies < ActiveRecord::Migration::Current
def self.up
add_column "people", "hobbies", :text
end
diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
index d4cbddab50..7f883dbb45 100644
--- a/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
+++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveDescriptions < ActiveRecord::Migration::Current
def self.up
add_column "people", "description", :text
end
diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb
index afe4b3d707..3338aaf7e1 100644
--- a/activerecord/test/models/customer.rb
+++ b/activerecord/test/models/customer.rb
@@ -64,7 +64,12 @@ class Fullname
def self.parse(str)
return nil unless str
- new(*str.to_s.split)
+
+ if str.is_a?(Hash)
+ new(str[:first], str[:last])
+ else
+ new(*str.to_s.split)
+ end
end
def initialize(first, last = nil)
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 176bc79dc7..21c23ed2a2 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -43,12 +43,6 @@ class Topic < ActiveRecord::Base
before_create :default_written_on
before_destroy :destroy_children
- # Explicitly define as :date column so that returned Oracle DATE values would be typecasted to Date and not Time.
- # Some tests depend on assumption that this attribute will have Date values.
- if current_adapter?(:OracleEnhancedAdapter)
- set_date_columns :last_read
- end
-
def parent
Topic.find(parent_id)
end
diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb
index f5dc93e994..97107a35a0 100644
--- a/activerecord/test/models/user.rb
+++ b/activerecord/test/models/user.rb
@@ -1,6 +1,12 @@
+require 'models/job'
+
class User < ActiveRecord::Base
has_secure_token
has_secure_token :auth_token
+
+ has_and_belongs_to_many :jobs_pool,
+ class_name: Job,
+ join_table: 'jobs_pool'
end
class UserWithNotification < User
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 24713f722a..030ad73621 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -88,7 +88,7 @@ _SQL
FOR EACH ROW EXECUTE PROCEDURE partitioned_insert_trigger();
_SQL
rescue ActiveRecord::StatementInvalid => e
- if e.message =~ /language "plpgsql" does not exist/
+ if e.message.include?('language "plpgsql" does not exist')
execute "CREATE LANGUAGE 'plpgsql';"
retry
else
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 628a59c2e3..a5155e9cdb 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -390,6 +390,11 @@ ActiveRecord::Schema.define do
t.integer :ideal_reference_id
end
+ create_table :jobs_pool, force: true, id: false do |t|
+ t.references :job, null: false, index: true
+ t.references :user, null: false, index: true
+ end
+
create_table :keyboards, force: true, id: false do |t|
t.primary_key :key_number
t.string :name
@@ -409,6 +414,14 @@ ActiveRecord::Schema.define do
t.references :student
end
+ create_table :students, force: true do |t|
+ t.string :name
+ t.boolean :active
+ t.integer :college_id
+ end
+
+ add_foreign_key :lessons_students, :students, on_delete: :cascade
+
create_table :lint_models, force: true
create_table :line_items, force: true do |t|
@@ -777,12 +790,6 @@ ActiveRecord::Schema.define do
t.integer :lock_version, null: false, default: 0
end
- create_table :students, force: true do |t|
- t.string :name
- t.boolean :active
- t.integer :college_id
- end
-
create_table :subscribers, force: true, id: false do |t|
t.string :nick, null: false
t.string :name
@@ -1002,7 +1009,6 @@ ActiveRecord::Schema.define do
end
add_foreign_key :fk_test_has_fk, :fk_test_has_pk, column: "fk_id", name: "fk_name", primary_key: "pk_id"
- add_foreign_key :lessons_students, :students
end
create_table :overloaded_types, force: true do |t|