aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb16
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb5
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb20
-rw-r--r--activerecord/lib/active_record/core.rb9
-rw-r--r--activerecord/lib/active_record/insert_all.rb6
-rw-r--r--activerecord/lib/active_record/integration.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb4
-rw-r--r--activerecord/lib/active_record/migration.rb7
-rw-r--r--activerecord/lib/active_record/persistence.rb15
-rw-r--r--activerecord/lib/active_record/railties/databases.rake42
-rw-r--r--activerecord/lib/active_record/relation.rb8
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb6
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb5
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb23
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb14
-rw-r--r--activerecord/lib/active_record/transactions.rb9
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb4
-rw-r--r--activerecord/test/cases/base_test.rb12
-rw-r--r--activerecord/test/cases/primary_keys_test.rb8
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb6
24 files changed, 172 insertions, 76 deletions
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index af7e46e649..220043c061 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -24,7 +24,7 @@ module ActiveRecord
RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
- class GeneratedAttributeMethodsBuilder < Module #:nodoc:
+ class GeneratedAttributeMethods < Module #:nodoc:
include Mutex_m
end
@@ -35,7 +35,7 @@ module ActiveRecord
end
def initialize_generated_modules # :nodoc:
- @generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethodsBuilder.new)
+ @generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new)
private_constant :GeneratedAttributeMethods
@attribute_methods_generated = false
include @generated_attribute_methods
@@ -89,7 +89,7 @@ module ActiveRecord
# If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
# defines its own attribute method, then we don't want to overwrite that.
defined = method_defined_within?(method_name, superclass, Base) &&
- ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethodsBuilder)
+ ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
defined || super
end
end
@@ -197,7 +197,7 @@ module ActiveRecord
"Dangerous query method (method whose arguments are used as raw " \
"SQL) called with non-attribute argument(s): " \
"#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
- "arguments will be disallowed in Rails 6.0. This method should " \
+ "arguments will be disallowed in Rails 6.1. This method should " \
"not be called with user-provided values, such as request " \
"parameters or model attributes. Known-safe values can be passed " \
"by wrapping them in Arel.sql()."
@@ -465,7 +465,7 @@ module ActiveRecord
end
def pk_attribute?(name)
- name == self.class.primary_key
+ name == @primary_key
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index affcf2a4db..3d917ec9b1 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -46,7 +46,7 @@ module ActiveRecord
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
def read_attribute_before_type_cast(attr_name)
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
@attributes[attr_name.to_s].value_before_type_cast
end
@@ -61,7 +61,7 @@ module ActiveRecord
# task.attributes_before_type_cast
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
def attributes_before_type_cast
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
@attributes.values_before_type_cast
end
@@ -73,7 +73,7 @@ module ActiveRecord
end
def attribute_came_from_user?(attribute_name)
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
@attributes[attribute_name].came_from_user?
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 942fe48635..444568562b 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -157,12 +157,12 @@ module ActiveRecord
private
def mutations_from_database
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
super
end
def mutations_before_last_save
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
super
end
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index feaef72a30..b4f5e6e75a 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -16,34 +16,32 @@ module ActiveRecord
# Returns the primary key column's value.
def id
- primary_key = self.class.primary_key
- _read_attribute(primary_key) if primary_key
+ _read_attribute(@primary_key)
end
# Sets the primary key column's value.
def id=(value)
- primary_key = self.class.primary_key
- _write_attribute(primary_key, value) if primary_key
+ _write_attribute(@primary_key, value)
end
# Queries the primary key column's value.
def id?
- query_attribute(self.class.primary_key)
+ query_attribute(@primary_key)
end
# Returns the primary key column's value before type cast.
def id_before_type_cast
- read_attribute_before_type_cast(self.class.primary_key)
+ read_attribute_before_type_cast(@primary_key)
end
# Returns the primary key column's previous value.
def id_was
- attribute_was(self.class.primary_key)
+ attribute_was(@primary_key)
end
# Returns the primary key column's value from the database.
def id_in_database
- attribute_in_database(self.class.primary_key)
+ attribute_in_database(@primary_key)
end
private
@@ -116,7 +114,7 @@ module ActiveRecord
#
# Project.primary_key # => "foo_id"
def primary_key=(value)
- @primary_key = value && value.to_s
+ @primary_key = value && -value.to_s
@quoted_primary_key = nil
@attributes_builder = nil
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 84b1ec2fea..c787fb6a94 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -31,15 +31,14 @@ module ActiveRecord
name = self.class.attribute_alias(name)
end
- primary_key = self.class.primary_key
- name = primary_key if name == "id" && primary_key
+ name = @primary_key if name == "id" && @primary_key
_read_attribute(name, &block)
end
# This method exists to avoid the expensive primary_key check internally, without
# breaking compatibility with the read_attribute API
def _read_attribute(attr_name, &block) # :nodoc
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
@attributes.fetch_value(attr_name.to_s, &block)
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index d1cfe43bb2..6a54bd2fc2 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -35,22 +35,21 @@ module ActiveRecord
name = self.class.attribute_alias(name)
end
- primary_key = self.class.primary_key
- name = primary_key if name == "id" && primary_key
+ name = @primary_key if name == "id" && @primary_key
_write_attribute(name, value)
end
# This method exists to avoid the expensive primary_key check internally, without
# breaking compatibility with the write_attribute API
def _write_attribute(attr_name, value) # :nodoc:
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
@attributes.write_from_user(attr_name.to_s, value)
value
end
private
def write_attribute_without_type_cast(attr_name, value)
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
@attributes.write_cast_value(attr_name.to_s, value)
value
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 7041d92586..2b64e96450 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -770,6 +770,17 @@ module ActiveRecord
# CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
#
# Note: only supported by MySQL.
+ #
+ # ====== Creating an index with a specific algorithm
+ #
+ # add_index(:developers, :name, algorithm: :concurrently)
+ # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
+ #
+ # Note: only supported by PostgreSQL.
+ #
+ # Concurrently adding an index is not supported in a transaction.
+ #
+ # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
def add_index(table_name, column_name, options = {})
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
@@ -793,6 +804,15 @@ module ActiveRecord
#
# remove_index :accounts, name: :by_branch_party
#
+ # Removes the index named +by_branch_party+ in the +accounts+ table +concurrently+.
+ #
+ # remove_index :accounts, name: :by_branch_party, algorithm: :concurrently
+ #
+ # Note: only supported by PostgreSQL.
+ #
+ # Concurrently removing an index is not supported in a transaction.
+ #
+ # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
def remove_index(table_name, options = {})
index_name = index_name_for_remove(table_name, options)
execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 04b21b4d00..068ebf3c09 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -101,7 +101,6 @@ module ActiveRecord
# environment where dumping schema is rarely needed.
mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
- mattr_accessor :database_selector, instance_writer: false
##
# :singleton-method:
# Specifies which database schemas to dump when calling db:structure:dump.
@@ -175,8 +174,7 @@ module ActiveRecord
record = statement.execute([id], connection)&.first
unless record
- raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
- name, primary_key, id)
+ raise RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
end
record
end
@@ -399,7 +397,7 @@ module ActiveRecord
##
def initialize_dup(other) # :nodoc:
@attributes = @attributes.deep_dup
- @attributes.reset(self.class.primary_key)
+ @attributes.reset(@primary_key)
_run_initialize_callbacks
@@ -466,7 +464,7 @@ module ActiveRecord
# Returns +true+ if the attributes hash has been frozen.
def frozen?
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
@attributes.frozen?
end
@@ -571,6 +569,7 @@ module ActiveRecord
end
def init_internals
+ @primary_key = self.class.primary_key
@readonly = false
@destroyed = false
@marked_for_destruction = false
diff --git a/activerecord/lib/active_record/insert_all.rb b/activerecord/lib/active_record/insert_all.rb
index 959e5bd4d7..f6577dcbc4 100644
--- a/activerecord/lib/active_record/insert_all.rb
+++ b/activerecord/lib/active_record/insert_all.rb
@@ -21,9 +21,9 @@ module ActiveRecord
end
def execute
- message = "#{model} "
- message += "Bulk " if inserts.many?
- message += (on_duplicate == :update ? "Upsert" : "Insert")
+ message = +"#{model} "
+ message << "Bulk " if inserts.many?
+ message << (on_duplicate == :update ? "Upsert" : "Insert")
connection.exec_query to_sql, message
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index c745bc1330..573a823dbc 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -162,7 +162,7 @@ module ActiveRecord
end
def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
- collection.compute_cache_key(timestamp_column)
+ collection.send(:compute_cache_key, timestamp_column)
end
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index b7eecda59e..6711ee9bf4 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -87,7 +87,7 @@ module ActiveRecord
affected_rows = self.class._update_record(
attributes_with_values(attribute_names),
- self.class.primary_key => id_in_database,
+ @primary_key => id_in_database,
locking_column => previous_lock_value
)
@@ -110,7 +110,7 @@ module ActiveRecord
locking_column = self.class.locking_column
affected_rows = self.class._delete_record(
- self.class.primary_key => id_in_database,
+ @primary_key => id_in_database,
locking_column => read_attribute_before_type_cast(locking_column)
)
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index ed0c6d48b8..f20edbeb93 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -4,6 +4,7 @@ require "benchmark"
require "set"
require "zlib"
require "active_support/core_ext/module/attribute_accessors"
+require "active_support/actionable_error"
module ActiveRecord
class MigrationError < ActiveRecordError #:nodoc:
@@ -128,6 +129,12 @@ module ActiveRecord
end
class PendingMigrationError < MigrationError #:nodoc:
+ include ActiveSupport::ActionableError
+
+ action "Run pending migrations" do
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+ end
+
def initialize(message = nil)
if !message && defined?(Rails.env)
super("Migrations are pending. To resolve this issue, run:\n\n rails db:migrate RAILS_ENV=#{::Rails.env}")
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 8bade8cd28..1ff7ee4d89 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -353,6 +353,7 @@ module ActiveRecord
end
def _insert_record(values) # :nodoc:
+ primary_key = self.primary_key
primary_key_value = nil
if primary_key && Hash === values
@@ -423,20 +424,20 @@ module ActiveRecord
# Returns true if this object hasn't been saved yet -- that is, a record
# for the object doesn't exist in the database yet; otherwise, returns false.
def new_record?
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
@new_record
end
# Returns true if this object has been destroyed, otherwise returns false.
def destroyed?
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
@destroyed
end
# Returns true if the record is persisted, i.e. it's not a new record and it was
# not destroyed, otherwise returns false.
def persisted?
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
!(@new_record || @destroyed)
end
@@ -674,7 +675,7 @@ module ActiveRecord
affected_rows = self.class._update_record(
attributes,
- self.class.primary_key => id_in_database
+ @primary_key => id_in_database
)
affected_rows == 1
@@ -874,7 +875,7 @@ module ActiveRecord
end
def _delete_row
- self.class._delete_record(self.class.primary_key => id_in_database)
+ self.class._delete_record(@primary_key => id_in_database)
end
def _touch_row(attribute_names, time)
@@ -890,7 +891,7 @@ module ActiveRecord
def _update_row(attribute_names, attempted_action = "update")
self.class._update_record(
attributes_with_values(attribute_names),
- self.class.primary_key => id_in_database
+ @primary_key => id_in_database
)
end
@@ -928,7 +929,7 @@ module ActiveRecord
attributes_with_values(attribute_names)
)
- self.id ||= new_id if self.class.primary_key
+ self.id ||= new_id if @primary_key
@new_record = false
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 64a4cb0886..e0bc5180c0 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -128,6 +128,8 @@ db_namespace = namespace :db do
# desc 'Runs the "up" for a given migration VERSION.'
task up: :load_config do
+ ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:up")
+
raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
ActiveRecord::Tasks::DatabaseTasks.check_target_version
@@ -139,8 +141,29 @@ db_namespace = namespace :db do
db_namespace["_dump"].invoke
end
+ namespace :up do
+ ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ task spec_name => :load_config do
+ raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
+
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
+
+ ActiveRecord::Base.establish_connection(db_config.config)
+ ActiveRecord::Tasks::DatabaseTasks.check_target_version
+ ActiveRecord::Base.connection.migration_context.run(
+ :up,
+ ActiveRecord::Tasks::DatabaseTasks.target_version
+ )
+
+ db_namespace["_dump"].invoke
+ end
+ end
+ end
+
# desc 'Runs the "down" for a given migration VERSION.'
task down: :load_config do
+ ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:down")
+
raise "VERSION is required - To go down one migration, use db:rollback" if !ENV["VERSION"] || ENV["VERSION"].empty?
ActiveRecord::Tasks::DatabaseTasks.check_target_version
@@ -152,6 +175,25 @@ db_namespace = namespace :db do
db_namespace["_dump"].invoke
end
+ namespace :down do
+ ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ task spec_name => :load_config do
+ raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
+
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
+
+ ActiveRecord::Base.establish_connection(db_config.config)
+ ActiveRecord::Tasks::DatabaseTasks.check_target_version
+ ActiveRecord::Base.connection.migration_context.run(
+ :down,
+ ActiveRecord::Tasks::DatabaseTasks.target_version
+ )
+
+ db_namespace["_dump"].invoke
+ end
+ end
+ end
+
desc "Display status of migrations"
task status: :load_config do
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 8eb71e6454..add95f6a0a 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -310,7 +310,7 @@ module ActiveRecord
# Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
def cache_key(timestamp_column = :updated_at)
@cache_keys ||= {}
- @cache_keys[timestamp_column] ||= @klass.collection_cache_key(self, timestamp_column)
+ @cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
end
def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
@@ -323,6 +323,7 @@ module ActiveRecord
"#{key}-#{compute_cache_version(timestamp_column)}"
end
end
+ private :compute_cache_key
# Returns a cache version that can be used together with the cache key to form
# a recyclable caching scheme. The cache version is built with the number of records
@@ -382,6 +383,7 @@ module ActiveRecord
"#{size}"
end
end
+ private :compute_cache_version
# Scope all queries to the current scope.
#
@@ -551,8 +553,8 @@ module ActiveRecord
# # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
def delete_all
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
- value = get_value(method)
- SINGLE_VALUE_METHODS.include?(method) ? value : value.any?
+ value = @values[method]
+ method == :distinct ? value : value&.any?
end
if invalid_methods.any?
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 801e312658..0be9ba7d7b 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -260,10 +260,8 @@ module ActiveRecord
def aggregate_column(column_name)
return column_name if Arel::Expressions === column_name
- if @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name)
- @klass.arel_attribute(column_name)
- else
- Arel.sql(column_name == :all ? "*" : column_name.to_s)
+ arel_column(column_name.to_s) do |name|
+ Arel.sql(column_name == :all ? "*" : name)
end
end
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 7a53a9d1c7..d59331053e 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -45,7 +45,10 @@ module ActiveRecord
private
def generated_relation_methods
- @generated_relation_methods ||= GeneratedRelationMethods.new
+ @generated_relation_methods ||= GeneratedRelationMethods.new.tap do |mod|
+ const_set(:GeneratedRelationMethods, mod)
+ private_constant :GeneratedRelationMethods
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 90b5e9a118..5d3cea6741 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -67,11 +67,13 @@ module ActiveRecord
end
class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{method_name} # def includes_values
- get_value(#{name.inspect}) # get_value(:includes)
+ default = DEFAULT_VALUES[:#{name}] # default = DEFAULT_VALUES[:includes]
+ @values.fetch(:#{name}, default) # @values.fetch(:includes, default)
end # end
def #{method_name}=(value) # def includes_values=(value)
- set_value(#{name.inspect}, value) # set_value(:includes, value)
+ assert_mutability! # assert_mutability!
+ @values[:#{name}] = value # @values[:includes] = value
end # end
CODE
end
@@ -417,7 +419,8 @@ module ActiveRecord
if !VALID_UNSCOPING_VALUES.include?(scope)
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
end
- set_value(scope, DEFAULT_VALUES[scope])
+ assert_mutability!
+ @values[scope] = DEFAULT_VALUES[scope]
when Hash
scope.each do |key, target_value|
if key != :where
@@ -1005,17 +1008,6 @@ module ActiveRecord
end
private
- # Returns a relation value with a given name
- def get_value(name)
- @values.fetch(name, DEFAULT_VALUES[name])
- end
-
- # Sets the relation value with the given name
- def set_value(name, value)
- assert_mutability!
- @values[name] = value
- end
-
def assert_mutability!
raise ImmutableRelation if @loaded
raise ImmutableRelation if defined?(@arel) && @arel
@@ -1316,7 +1308,8 @@ module ActiveRecord
def structurally_incompatible_values_for_or(other)
values = other.values
STRUCTURAL_OR_METHODS.reject do |method|
- get_value(method) == values.fetch(method, DEFAULT_VALUES[method])
+ default = DEFAULT_VALUES[method]
+ @values.fetch(method, default) == values.fetch(method, default)
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 53e7e1e6d7..c79ed8db60 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -155,6 +155,20 @@ module ActiveRecord
end
end
+ def raise_for_multi_db(environment = env, command:)
+ db_configs = ActiveRecord::Base.configurations.configs_for(env_name: environment)
+
+ if db_configs.count > 1
+ dbs_list = []
+
+ db_configs.each do |db|
+ dbs_list << "#{command}:#{db.spec_name}"
+ end
+
+ raise "You're using a multiple database application. To use `#{command}` you must run the namespaced task with a VERSION. Available tasks are #{dbs_list.to_sentence}."
+ end
+ end
+
def create_current(environment = env)
each_current_configuration(environment) { |configuration|
create configuration
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index ea288456b9..49a8206c84 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -365,7 +365,7 @@ module ActiveRecord
status = nil
self.class.transaction do
unless has_transactional_callbacks?
- sync_with_transaction_state
+ sync_with_transaction_state if @transaction_state&.finalized?
@transaction_state = self.class.connection.transaction_state
end
remember_transaction_record_state
@@ -432,9 +432,8 @@ module ActiveRecord
end
@mutations_from_database = nil
@mutations_before_last_save = nil
- pk = self.class.primary_key
- if pk && @attributes.fetch_value(pk) != restore_state[:id]
- @attributes.write_from_user(pk, restore_state[:id])
+ if @attributes.fetch_value(@primary_key) != restore_state[:id]
+ @attributes.write_from_user(@primary_key, restore_state[:id])
end
freeze if restore_state[:frozen?]
end
@@ -479,7 +478,7 @@ module ActiveRecord
# the TransactionState, and rolls back or commits the Active Record object
# as appropriate.
def sync_with_transaction_state
- if (transaction_state = @transaction_state)&.finalized?
+ if transaction_state = @transaction_state
if transaction_state.fully_committed?
force_clear_transaction_record_state
elsif transaction_state.committed?
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 9fd62dcf72..5cbe5d796d 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -1081,9 +1081,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal ["title"], model.accessed_fields
end
- test "generated attribute methods ancestors have correct class" do
+ test "generated attribute methods ancestors have correct module" do
mod = Topic.send(:generated_attribute_methods)
- assert_match %r(Topic::GeneratedAttributeMethods), mod.inspect
+ assert_equal "Topic::GeneratedAttributeMethods", mod.inspect
end
private
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 99f47cfe37..ddafa468ed 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -67,6 +67,16 @@ end
class BasicsTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :author_addresses, :categorizations, :categories, :posts
+ def test_generated_association_methods_module_name
+ mod = Post.send(:generated_association_methods)
+ assert_equal "Post::GeneratedAssociationMethods", mod.inspect
+ end
+
+ def test_generated_relation_methods_module_name
+ mod = Post.send(:generated_relation_methods)
+ assert_equal "Post::GeneratedRelationMethods", mod.inspect
+ end
+
def test_column_names_are_escaped
conn = ActiveRecord::Base.connection
classname = conn.class.name[/[^:]*$/]
@@ -1205,6 +1215,8 @@ class BasicsTest < ActiveRecord::TestCase
wr.close
assert Marshal.load rd.read
rd.close
+ ensure
+ self.class.send(:remove_const, "Post") if self.class.const_defined?("Post", false)
end
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 4759d3b6b2..511d7fc982 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -203,6 +203,14 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_queries(3, ignore_none: true) { klass.create! }
end
+ def test_assign_id_raises_error_if_primary_key_doesnt_exist
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "dashboards"
+ end
+ dashboard = klass.new
+ assert_raises(ActiveModel::MissingAttributeError) { dashboard.id = "1" }
+ end
+
if current_adapter?(:PostgreSQLAdapter)
def test_serial_with_quoted_sequence_name
column = MixedCaseMonkey.columns_hash[MixedCaseMonkey.primary_key]
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index dd4a0b0455..ffe94eee0f 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -760,7 +760,7 @@ module ActiveRecord
end
class DatabaseTasksMigrateTest < DatabaseTasksMigrationTestCase
- def test_migrate_set_and_unset_verbose_and_version_env_vars
+ def test_can_migrate_from_pending_migration_error_action_dispatch
verbose, version = ENV["VERBOSE"], ENV["VERSION"]
ENV["VERSION"] = "2"
ENV["VERBOSE"] = "false"
@@ -772,7 +772,9 @@ module ActiveRecord
ENV.delete("VERBOSE")
# re-run up migration
- assert_includes capture_migration_output, "migrating"
+ assert_includes(capture(:stdout) do
+ ActiveSupport::ActionableError.dispatch ActiveRecord::PendingMigrationError, "Run pending migrations"
+ end, "migrating")
ensure
ENV["VERBOSE"], ENV["VERSION"] = verbose, version
end