提交 19b168e6 编写于 作者: A Agis-

Only nullify persisted has_one target associations

Since after 87d1aba3 `dependent: :destroy` callbacks on has_one
assocations run *after* destroy, it is possible that a nullification is
attempted on an already destroyed target:

    class Car < ActiveRecord::Base
      has_one :engine, dependent: :nullify
    end

    class Engine < ActiveRecord::Base
      belongs_to :car, dependent: :destroy
    end

    > car = Car.create!
    > engine = Engine.create!(car: car)
    > engine.destroy! # => ActiveRecord::ActiveRecordError: cannot update a
    >   destroyed record

In the above case, `engine.destroy!` deletes `engine` and *then* triggers the
deletion of `car`, which in turn triggers a nullification of `engine.car_id`.
However, `engine` is already destroyed at that point.

Fixes #21223.
上级 ef8d09d9
* Only try to nullify has_one target association if the record is persisted.
Fixes #21223.
*Agis Anastasopoulos*
* Uniqueness validator raises descriptive error when running on a persisted * Uniqueness validator raises descriptive error when running on a persisted
record without primary key. record without primary key.
......
...@@ -65,7 +65,7 @@ def delete(method = options[:dependent]) ...@@ -65,7 +65,7 @@ def delete(method = options[:dependent])
when :destroy when :destroy
target.destroy target.destroy
when :nullify when :nullify
target.update_columns(reflection.foreign_key => nil) target.update_columns(reflection.foreign_key => nil) if target.persisted?
end end
end end
end end
......
...@@ -107,6 +107,14 @@ def test_nullification_on_association_change ...@@ -107,6 +107,14 @@ def test_nullification_on_association_change
assert_nil Account.find(old_account_id).firm_id assert_nil Account.find(old_account_id).firm_id
end end
def test_nullification_on_destroyed_association
developer = Developer.create!(name: "Someone")
ship = Ship.create!(name: "Planet Caravan", developer: developer)
ship.destroy
assert !ship.persisted?
assert !developer.persisted?
end
def test_natural_assignment_to_nil_after_destroy def test_natural_assignment_to_nil_after_destroy
firm = companies(:rails_core) firm = companies(:rails_core)
old_account_id = firm.account.id old_account_id = firm.account.id
......
...@@ -50,6 +50,7 @@ def find_least_recent ...@@ -50,6 +50,7 @@ def find_least_recent
has_many :firms, :through => :contracts, :source => :firm has_many :firms, :through => :contracts, :source => :firm
has_many :comments, ->(developer) { where(body: "I'm #{developer.name}") } has_many :comments, ->(developer) { where(body: "I'm #{developer.name}") }
has_many :ratings, through: :comments has_many :ratings, through: :comments
has_one :ship, dependent: :nullify
scope :jamises, -> { where(:name => 'Jamis') } scope :jamises, -> { where(:name => 'Jamis') }
......
...@@ -3,6 +3,7 @@ class Ship < ActiveRecord::Base ...@@ -3,6 +3,7 @@ class Ship < ActiveRecord::Base
belongs_to :pirate belongs_to :pirate
belongs_to :update_only_pirate, :class_name => 'Pirate' belongs_to :update_only_pirate, :class_name => 'Pirate'
belongs_to :developer, dependent: :destroy
has_many :parts, :class_name => 'ShipPart' has_many :parts, :class_name => 'ShipPart'
has_many :treasures has_many :treasures
......
...@@ -673,6 +673,7 @@ def except(adapter_names_to_exclude) ...@@ -673,6 +673,7 @@ def except(adapter_names_to_exclude)
create_table :ships, force: true do |t| create_table :ships, force: true do |t|
t.string :name t.string :name
t.integer :pirate_id t.integer :pirate_id
t.belongs_to :developer
t.integer :update_only_pirate_id t.integer :update_only_pirate_id
# Conventionally named column for counter_cache # Conventionally named column for counter_cache
t.integer :treasures_count, default: 0 t.integer :treasures_count, default: 0
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册