Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a Rails 4 circular dependent: :destroy workaround?

As an example for the circular dependent: :destroy issue:

class User < ActiveRecord::Base
  has_one: :staff, dependent: :destroy
end

class Staff < ActiveRecord::Base
  belongs_to :user, dependent: :destroy
end

If I call user.destroy, the associated staff should be destroyed as well. Conversely, calling staff.destroy should destroy the associated user as well.

This worked great in Rails 3.x, but the behavior changed in Rails 4.0 (and continues in 4.1) such that a loop forms and eventually you get an error, "stack level too deep." One obvious workaround is to create a custom callback using before_destroy or after_destroy to manually destroy the associated objects instead of using the dependent: :destroy mechanism. Even the issue in GitHub opened for this situation had a couple people recommending this workaround.

Unfortunately, I can't even get that workaround to work. This is what I have:

class User < ActiveRecord::Base
  has_one: :staff

  after_destroy :destroy_staff

  def destroy_staff
    staff.destroy if staff and !staff.destroyed?
  end
end

The reason this doesn't work is that staff.destroyed? always returns false. So it forms a cycle.

like image 614
at. Avatar asked Apr 22 '14 01:04

at.


People also ask

What does dependent destroy mean rails?

Dependent is an option of Rails collection association declaration to cascade the delete action. The :destroy is to cause the associated object to also be destroyed when its owner is destroyed.


1 Answers

If one side of the cycle only has that one callback, you can replace one of the dependent: :destroy with dependent: :delete

class User < ActiveRecord::Base
  # delete prevents Staff's :destroy callback from happening
  has_one: :staff, dependent: :delete
  has_many :other_things, dependent: :destroy
end

class Staff < ActiveRecord::Base
  # use :destroy here so that other_things are properly removed
  belongs_to :user, dependent: :destroy
end

Worked great for me, as long as one side doesn't need other callbacks to fire.

like image 56
Ryan Ahearn Avatar answered Sep 27 '22 23:09

Ryan Ahearn