aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb1
-rw-r--r--activerecord/lib/active_record/core.rb2
-rw-r--r--activerecord/lib/active_record/database_configurations.rb10
-rw-r--r--activerecord/lib/active_record/errors.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb17
-rw-r--r--activerecord/lib/active_record/insert_all.rb6
-rw-r--r--activerecord/lib/active_record/middleware/database_selector/resolver.rb12
-rw-r--r--activerecord/lib/active_record/railtie.rb1
-rw-r--r--activerecord/lib/active_record/railties/databases.rake2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb6
-rw-r--r--activerecord/lib/active_record/serialization.rb2
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb10
-rw-r--r--activerecord/test/cases/calculations_test.rb84
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb16
-rw-r--r--activerecord/test/cases/database_selector_test.rb34
-rw-r--r--activerecord/test/cases/dirty_test.rb6
-rw-r--r--activerecord/test/cases/insert_all_test.rb18
-rw-r--r--activerecord/test/cases/relations_test.rb2
23 files changed, 216 insertions, 28 deletions
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 3346725f2d..e3886f394a 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -44,7 +44,7 @@ module ActiveRecord
def decrement_counters_before_last_save
if reflection.polymorphic?
- model_was = owner.attribute_before_last_save(reflection.foreign_type).try(:constantize)
+ model_was = owner.attribute_before_last_save(reflection.foreign_type)&.constantize
else
model_was = klass
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 fb44232dff..9a4de1a034 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/core_ext/object/try"
+
module ActiveRecord
module AttributeMethods
module TimeZoneConversion
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 36001efdd5..276d5a25d4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1020,8 +1020,8 @@ module ActiveRecord
# In some cases you may want to prevent writes to the database
# even if you are on a database that can write. `while_preventing_writes`
# will prevent writes to the database for the duration of the block.
- def while_preventing_writes
- original, @prevent_writes = @prevent_writes, true
+ def while_preventing_writes(enabled = true)
+ original, @prevent_writes = @prevent_writes, enabled
yield
ensure
@prevent_writes = original
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 dbd533b4b3..aedd8119ef 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -221,7 +221,7 @@ module ActiveRecord
end
class_methods do
- private def define_column_methods(*column_types) # :nodoc:
+ def define_column_methods(*column_types) # :nodoc:
column_types.each do |column_type|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{column_type}(*names, **options)
@@ -231,6 +231,7 @@ module ActiveRecord
RUBY
end
end
+ private :define_column_methods
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
index 8df91c988b..75213e964b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
@@ -24,7 +24,7 @@ WARNING: Rails was not able to disable referential integrity.
This is most likely caused due to missing permissions.
Rails needs superuser privileges to disable referential integrity.
- cause: #{original_exception.try(:message)}
+ cause: #{original_exception&.message}
WARNING
raise e
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index f33ba87435..d6758c9d4b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -12,6 +12,7 @@ class ::PG::Connection # :nodoc:
end
end
+require "active_support/core_ext/object/try"
require "active_record/connection_adapters/abstract_adapter"
require "active_record/connection_adapters/statement_pool"
require "active_record/connection_adapters/postgresql/column"
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 595ef4ee25..1b2c9c6754 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -45,7 +45,7 @@ module ActiveRecord
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
# @spec_name="primary", @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>,
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
- # @spec_name="primary", @config={"adapter"=>"mysql2", "database"=>"db/production.sqlite3"}>
+ # @spec_name="primary", @config={"adapter"=>"sqlite3", "database"=>"db/production.sqlite3"}>
# ]>
def self.configurations=(config)
@@configurations = ActiveRecord::DatabaseConfigurations.new(config)
diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb
index fcf13a910c..268be34b21 100644
--- a/activerecord/lib/active_record/database_configurations.rb
+++ b/activerecord/lib/active_record/database_configurations.rb
@@ -9,6 +9,8 @@ module ActiveRecord
# objects (either a HashConfig or UrlConfig) that are constructed from the
# application's database configuration hash or URL string.
class DatabaseConfigurations
+ class InvalidConfigurationError < StandardError; end
+
attr_reader :configurations
delegate :any?, to: :configurations
@@ -146,17 +148,19 @@ module ActiveRecord
build_db_config_from_string(env_name, spec_name, config)
when Hash
build_db_config_from_hash(env_name, spec_name, config.stringify_keys)
+ else
+ raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
end
end
def build_db_config_from_string(env_name, spec_name, config)
url = config
uri = URI.parse(url)
- if uri.try(:scheme)
+ if uri.scheme
ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url)
+ else
+ raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
end
- rescue URI::InvalidURIError
- ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
end
def build_db_config_from_hash(env_name, spec_name, config)
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 509f21c9a5..0f110b4536 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -106,7 +106,7 @@ module ActiveRecord
# Wraps the underlying database error as +cause+.
class StatementInvalid < ActiveRecordError
def initialize(message = nil, sql: nil, binds: nil)
- super(message || $!.try(:message))
+ super(message || $!&.message)
@sql = sql
@binds = binds
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 3df4b3c87f..e4b958f1b9 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -553,15 +553,15 @@ module ActiveRecord
end
end
- def create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base)
+ def create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base, &block)
fixture_set_names = Array(fixture_set_names).map(&:to_s)
class_names = ClassCache.new class_names, config
# FIXME: Apparently JK uses this.
- connection = block_given? ? yield : ActiveRecord::Base.connection
+ connection = block_given? ? block : lambda { ActiveRecord::Base.connection }
fixture_files_to_read = fixture_set_names.reject do |fs_name|
- fixture_is_cached?(connection, fs_name)
+ fixture_is_cached?(connection.call, fs_name)
end
if fixture_files_to_read.any?
@@ -571,9 +571,9 @@ module ActiveRecord
class_names,
connection,
)
- cache_fixtures(connection, fixtures_map)
+ cache_fixtures(connection.call, fixtures_map)
end
- cached_fixtures(connection, fixture_set_names)
+ cached_fixtures(connection.call, fixture_set_names)
end
# Returns a consistent, platform-independent identifier for +label+.
@@ -612,7 +612,11 @@ module ActiveRecord
def insert(fixture_sets, connection) # :nodoc:
fixture_sets_by_connection = fixture_sets.group_by do |fixture_set|
- fixture_set.model_class&.connection || connection
+ if fixture_set.model_class
+ fixture_set.model_class.connection
+ else
+ connection.call
+ end
end
fixture_sets_by_connection.each do |conn, set|
@@ -623,6 +627,7 @@ module ActiveRecord
table_rows_for_connection[table].unshift(*rows)
end
end
+
conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys)
# Cap primary key sequences to max(pk).
diff --git a/activerecord/lib/active_record/insert_all.rb b/activerecord/lib/active_record/insert_all.rb
index f6577dcbc4..3863b6bfea 100644
--- a/activerecord/lib/active_record/insert_all.rb
+++ b/activerecord/lib/active_record/insert_all.rb
@@ -14,7 +14,7 @@ module ActiveRecord
@returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
@returning = false if @returning == []
- @unique_by = find_unique_index_for(unique_by) if unique_by
+ @unique_by = find_unique_index_for(unique_by || model.primary_key)
@on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty?
ensure_valid_options_for_connection!
@@ -32,7 +32,7 @@ module ActiveRecord
end
def primary_keys
- Array(model.primary_key)
+ Array(connection.schema_cache.primary_keys(model.table_name))
end
@@ -61,6 +61,8 @@ module ActiveRecord
if index = unique_indexes.find { |i| match.include?(i.name) || i.columns == match }
index
+ elsif match == primary_keys
+ nil
else
raise ArgumentError, "No unique index found for #{unique_by}"
end
diff --git a/activerecord/lib/active_record/middleware/database_selector/resolver.rb b/activerecord/lib/active_record/middleware/database_selector/resolver.rb
index 3eb1039c50..b2aa453a8e 100644
--- a/activerecord/lib/active_record/middleware/database_selector/resolver.rb
+++ b/activerecord/lib/active_record/middleware/database_selector/resolver.rb
@@ -46,7 +46,7 @@ module ActiveRecord
private
def read_from_primary(&blk)
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
- ActiveRecord::Base.connection_handler.while_preventing_writes do
+ ActiveRecord::Base.connection_handler.while_preventing_writes(true) do
instrumenter.instrument("database_selector.active_record.read_from_primary") do
yield
end
@@ -64,10 +64,12 @@ module ActiveRecord
def write_to_primary(&blk)
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
- instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
- yield
- ensure
- context.update_last_write_timestamp
+ ActiveRecord::Base.connection_handler.while_preventing_writes(false) do
+ instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
+ yield
+ ensure
+ context.update_last_write_timestamp
+ end
end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index d5375390c7..f06a98bd98 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -2,6 +2,7 @@
require "active_record"
require "rails"
+require "active_support/core_ext/object/try"
require "active_model/railtie"
# For now, action_controller must always be present with
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 4d9acc911b..d699e2e21b 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -265,6 +265,8 @@ db_namespace = namespace :db do
end
abort %{Run `rails db:migrate` to update your database then try again.}
end
+ ensure
+ ActiveRecord::Base.establish_connection(ActiveRecord::Tasks::DatabaseTasks.env.to_sym)
end
namespace :abort_if_pending_migrations do
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 29e2e2cedc..2876bae302 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1114,7 +1114,7 @@ module ActiveRecord
else
join
end
- end.compact_blank!
+ end.compact_blank!.uniq!
while joins.first.is_a?(Arel::Nodes::Join)
join_node = joins.shift
@@ -1144,8 +1144,8 @@ module ActiveRecord
def build_join_query(manager, buckets, join_type, aliases)
association_joins = buckets[:association_join]
stashed_joins = buckets[:stashed_join]
- leading_joins = buckets[:leading_join].tap(&:uniq!)
- join_nodes = buckets[:join_node].tap(&:uniq!)
+ leading_joins = buckets[:leading_join]
+ join_nodes = buckets[:join_node]
join_sources = manager.join_sources
join_sources.concat(leading_joins) unless leading_joins.empty?
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 741fea43ce..9fc62a99f8 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -11,7 +11,7 @@ module ActiveRecord #:nodoc:
end
def serializable_hash(options = nil)
- options = options.try(:dup) || {}
+ options = options ? options.dup : {}
options[:except] = Array(options[:except]).map(&:to_s)
options[:except] |= Array(self.class.inheritance_column)
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index b65af4b819..166a59ec7b 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -69,6 +69,16 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
assert_equal [expected], Person.joins(string_join).joins(agents.create_join(agents, agents.create_on(constraint)))
end
+ def test_deduplicate_joins
+ posts = Post.arel_table
+ constraint = posts[:author_id].eq(Author.arel_attribute(:id))
+
+ authors = Author.joins(posts.create_join(posts, posts.create_on(constraint)))
+ authors = authors.joins(:author_address).merge(authors.where("posts.type": "SpecialPost"))
+
+ assert_equal [authors(:david)], authors
+ end
+
def test_construct_finder_sql_ignores_empty_joins_hash
sql = Author.joins({}).to_sql
assert_no_match(/JOIN/i, sql)
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index dbd1d03c4c..abce4565a4 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -952,6 +952,90 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal({ "proposed" => 2, "published" => 2 }, Book.group(:status).count)
end
+ def test_select_avg_with_group_by_as_virtual_attribute_with_sql
+ rails_core = companies(:rails_core)
+
+ sql = <<~SQL
+ SELECT firm_id, AVG(credit_limit) AS avg_credit_limit
+ FROM accounts
+ WHERE firm_id = ?
+ GROUP BY firm_id
+ LIMIT 1
+ SQL
+
+ account = Account.find_by_sql([sql, rails_core]).first
+
+ # id was not selected, so it should be nil
+ # (cannot select id because it wasn't used in the GROUP BY clause)
+ assert_nil account.id
+
+ # firm_id was explicitly selected, so it should be present
+ assert_equal(rails_core, account.firm)
+
+ # avg_credit_limit should be present as a virtual attribute
+ assert_equal(52.5, account.avg_credit_limit)
+ end
+
+ def test_select_avg_with_group_by_as_virtual_attribute_with_ar
+ rails_core = companies(:rails_core)
+
+ account = Account
+ .select(:firm_id, "AVG(credit_limit) AS avg_credit_limit")
+ .where(firm: rails_core)
+ .group(:firm_id)
+ .take!
+
+ # id was not selected, so it should be nil
+ # (cannot select id because it wasn't used in the GROUP BY clause)
+ assert_nil account.id
+
+ # firm_id was explicitly selected, so it should be present
+ assert_equal(rails_core, account.firm)
+
+ # avg_credit_limit should be present as a virtual attribute
+ assert_equal(52.5, account.avg_credit_limit)
+ end
+
+ def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_sql
+ rails_core = companies(:rails_core)
+
+ sql = <<~SQL
+ SELECT companies.*, AVG(accounts.credit_limit) AS avg_credit_limit
+ FROM companies
+ INNER JOIN accounts ON companies.id = accounts.firm_id
+ WHERE companies.id = ?
+ GROUP BY companies.id
+ LIMIT 1
+ SQL
+
+ firm = DependentFirm.find_by_sql([sql, rails_core]).first
+
+ # all the DependentFirm attributes should be present
+ assert_equal rails_core, firm
+ assert_equal rails_core.name, firm.name
+
+ # avg_credit_limit should be present as a virtual attribute
+ assert_equal(52.5, firm.avg_credit_limit)
+ end
+
+ def test_select_avg_with_joins_and_group_by_as_virtual_attribute_with_ar
+ rails_core = companies(:rails_core)
+
+ firm = DependentFirm
+ .select("companies.*", "AVG(accounts.credit_limit) AS avg_credit_limit")
+ .where(id: rails_core)
+ .joins(:account)
+ .group(:id)
+ .take!
+
+ # all the DependentFirm attributes should be present
+ assert_equal rails_core, firm
+ assert_equal rails_core.name, firm.name
+
+ # avg_credit_limit should be present as a virtual attribute
+ assert_equal(52.5, firm.avg_credit_limit)
+ end
+
def test_count_with_block_and_column_name_raises_an_error
assert_raises(ArgumentError) do
Account.count(:firm_id) { true }
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 ee2972101f..2ac249b478 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
@@ -28,6 +28,22 @@ module ActiveRecord
resolver.resolve(spec, spec)
end
+ def test_invalid_string_config
+ config = { "foo" => "bar" }
+
+ assert_raises ActiveRecord::DatabaseConfigurations::InvalidConfigurationError do
+ resolve_config(config)
+ end
+ end
+
+ def test_invalid_symbol_config
+ config = { "foo" => :bar }
+
+ assert_raises ActiveRecord::DatabaseConfigurations::InvalidConfigurationError do
+ resolve_config(config)
+ end
+ end
+
def test_resolver_with_database_uri_and_current_env_symbol_key
ENV["DATABASE_URL"] = "postgres://localhost/foo"
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
diff --git a/activerecord/test/cases/database_selector_test.rb b/activerecord/test/cases/database_selector_test.rb
index fd02d2acb4..340151e6db 100644
--- a/activerecord/test/cases/database_selector_test.rb
+++ b/activerecord/test/cases/database_selector_test.rb
@@ -123,6 +123,40 @@ module ActiveRecord
assert read
end
+ def test_preventing_writes_turns_off_for_primary_write
+ resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver.new(@session, delay: 5.seconds)
+
+ # Session should start empty
+ assert_nil @session_store[:last_write]
+
+ called = false
+ resolver.write do
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ called = true
+ end
+ assert called
+
+ # and be populated by the last write time
+ assert @session_store[:last_write]
+
+ read = false
+ write = false
+ resolver.read do
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ assert ActiveRecord::Base.connection_handler.prevent_writes
+ read = true
+
+ resolver.write do
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ assert_not ActiveRecord::Base.connection_handler.prevent_writes
+ write = true
+ end
+ end
+
+ assert write
+ assert read
+ end
+
def test_read_from_replica_with_no_delay
resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver.new(@session, delay: 0.seconds)
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index a2a501a794..38baf0509c 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -491,6 +491,7 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal 4, pirate.previous_changes.size
assert_equal [nil, "arrr"], pirate.previous_changes["catchphrase"]
+ assert_nil pirate.catchphrase_previously_was
assert_equal [nil, pirate.id], pirate.previous_changes["id"]
assert_nil pirate.previous_changes["updated_on"][0]
assert_not_nil pirate.previous_changes["updated_on"][1]
@@ -507,6 +508,7 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal 4, pirate.previous_changes.size
assert_equal [nil, "arrr"], pirate.previous_changes["catchphrase"]
+ assert_nil pirate.catchphrase_previously_was
assert_equal [nil, pirate.id], pirate.previous_changes["id"]
assert_includes pirate.previous_changes, "updated_on"
assert_includes pirate.previous_changes, "created_on"
@@ -525,6 +527,7 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal 2, pirate.previous_changes.size
assert_equal ["arrr", "Me Maties!"], pirate.previous_changes["catchphrase"]
+ assert_equal "arrr", pirate.catchphrase_previously_was
assert_not_nil pirate.previous_changes["updated_on"][0]
assert_not_nil pirate.previous_changes["updated_on"][1]
assert_not pirate.previous_changes.key?("parrot_id")
@@ -539,6 +542,7 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal 2, pirate.previous_changes.size
assert_equal ["Me Maties!", "Thar She Blows!"], pirate.previous_changes["catchphrase"]
+ assert_equal "Me Maties!", pirate.catchphrase_previously_was
assert_not_nil pirate.previous_changes["updated_on"][0]
assert_not_nil pirate.previous_changes["updated_on"][1]
assert_not pirate.previous_changes.key?("parrot_id")
@@ -551,6 +555,7 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal 2, pirate.previous_changes.size
assert_equal ["Thar She Blows!", "Ahoy!"], pirate.previous_changes["catchphrase"]
+ assert_equal "Thar She Blows!", pirate.catchphrase_previously_was
assert_not_nil pirate.previous_changes["updated_on"][0]
assert_not_nil pirate.previous_changes["updated_on"][1]
assert_not pirate.previous_changes.key?("parrot_id")
@@ -563,6 +568,7 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal 2, pirate.previous_changes.size
assert_equal ["Ahoy!", "Ninjas suck!"], pirate.previous_changes["catchphrase"]
+ assert_equal "Ahoy!", pirate.catchphrase_previously_was
assert_not_nil pirate.previous_changes["updated_on"][0]
assert_not_nil pirate.previous_changes["updated_on"][1]
assert_not pirate.previous_changes.key?("parrot_id")
diff --git a/activerecord/test/cases/insert_all_test.rb b/activerecord/test/cases/insert_all_test.rb
index d086d77081..42c623fafb 100644
--- a/activerecord/test/cases/insert_all_test.rb
+++ b/activerecord/test/cases/insert_all_test.rb
@@ -2,6 +2,7 @@
require "cases/helper"
require "models/book"
+require "models/speedometer"
class ReadonlyNameBook < Book
attr_readonly :name
@@ -225,6 +226,23 @@ class InsertAllTest < ActiveRecord::TestCase
assert_equal new_name, Book.find(1).name
end
+ def test_upsert_all_updates_existing_record_by_primary_key
+ skip unless supports_insert_on_duplicate_update?
+
+ Book.upsert_all [{ id: 1, name: "New edition" }], unique_by: :id
+
+ assert_equal "New edition", Book.find(1).name
+ end
+
+ def test_upsert_all_updates_existing_record_by_configured_primary_key
+ skip unless supports_insert_on_duplicate_update?
+
+ error = assert_raises ArgumentError do
+ Speedometer.upsert_all [{ speedometer_id: "s1", name: "New Speedometer" }]
+ end
+ assert_match "No unique index found for speedometer_id", error.message
+ end
+
def test_upsert_all_does_not_update_readonly_attributes
skip unless supports_insert_on_duplicate_update?
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 1a20fe5dc2..8b3ae02947 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1932,7 +1932,7 @@ class RelationTest < ActiveRecord::TestCase
assert_no_queries do
result = authors_count.map do |post|
- [post.num_posts, post.author.try(:name)]
+ [post.num_posts, post.author&.name]
end
expected = [[1, nil], [5, "David"], [3, "Mary"], [2, "Bob"]]