-
Notifications
You must be signed in to change notification settings - Fork 21.9k
Closed
Closed
Copy link
Labels
Description
Summary
Given a has_one :through
association with dependent: :destroy
, destroying the parent destroys the through record and the far end. However if the association is bi-directional, destroying only works from one end – and the end which succeeds depends on the order of belongs_to
declarations in the join model. I believe it should work from both ends.
Steps to reproduce
# frozen_string_literal: true
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails"
gem "sqlite3"
end
require "active_record"
require "minitest/autorun"
require "logger"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table :lefts, force: true do |t|
end
create_table :rights, force: true do |t|
end
create_table :middles, force: true do |t|
t.references :left, foreign_key: true
t.references :right, foreign_key: true
end
end
class Left < ActiveRecord::Base
has_one :middle, dependent: :destroy
has_one :right, through: :middle
end
class Middle < ActiveRecord::Base
belongs_to :left, dependent: :destroy
belongs_to :right, dependent: :destroy
end
class Right < ActiveRecord::Base
has_one :middle, dependent: :destroy
has_one :left, through: :middle
end
class BugTest < Minitest::Test
def test_destroying_left_destroys_right
left = Left.create!
right = Right.create!
middle = Middle.create! left: left, right: right
left.destroy
assert right.destroyed?
end
def test_destroying_right_destroys_left
left = Left.create!
right = Right.create!
middle = Middle.create! left: left, right: right
right.destroy
assert left.destroyed?
end
end
Expected behavior
I expect:
left.destroy
to also destroy itsmiddle
and itsright
right.destroy
to also destroy itsmiddle
and itsleft
Actual behavior
right.destroy
destroys itsmiddle
and itsleft
left.destroy
destroys itsmiddle
but does not destroy itsright
However if I reverse the order of Middle
's belongs_to
declarations, right.destroy
stops working and left.destroy
starts working.
Patch
This patch fixes the behaviour and does not break any existing tests (via bundle exec rake test:sqlite3
):
diff --git i/activerecord/lib/active_record/callbacks.rb w/activerecord/lib/active_record/callbacks.rb
index 29c72d1024..6e9c68b747 100644
--- i/activerecord/lib/active_record/callbacks.rb
+++ w/activerecord/lib/active_record/callbacks.rb
@@ -418,7 +418,7 @@ module ClassMethods
def destroy # :nodoc:
@_destroy_callback_already_called ||= false
- return if @_destroy_callback_already_called
+ return true if @_destroy_callback_already_called
@_destroy_callback_already_called = true
_run_destroy_callbacks { super }
rescue RecordNotDestroyed => e
Credit for the patch belongs to Alex.
System configuration
Rails version: 7.1.3
Ruby version: 3.3.0