Есть ли цикличность Rails 4:: разрушить обходное решение?
В качестве примера для круговой dependent: :destroy
проблемы:
class User < ActiveRecord::Base
has_one: :staff, dependent: :destroy
end
class Staff < ActiveRecord::Base
belongs_to :user, dependent: :destroy
end
Если я вызываю user.destroy
, связанный с ним staff
также должен быть уничтожен. И наоборот, вызов staff.destroy
должен также уничтожить связанный user
.
Это отлично работало в Rails 3.x, но поведение изменилось в Rails 4.0 (и продолжается в 4.1), так что цикл формируется и, в конце концов, вы получаете сообщение об ошибке "слишком высокий уровень стека". Одним из очевидных способов решения проблемы является создание пользовательского обратного вызова с помощью before_destroy
или after_destroy
для ручного уничтожения связанных объектов вместо использования механизма dependent: :destroy
. Даже проблема в GitHub, открытая для этого, имела пару человек, рекомендующих это обходное решение.
К сожалению, я даже не могу заставить это обходное решение работать. Это то, что у меня есть:
class User < ActiveRecord::Base
has_one: :staff
after_destroy :destroy_staff
def destroy_staff
staff.destroy if staff and !staff.destroyed?
end
end
Причина, по которой это не работает, заключается в том, что staff.destroyed?
всегда возвращает false
. Таким образом, он образует цикл.
Ответы
Ответ 1
Если одна сторона цикла имеет только один обратный вызов, вы можете заменить один из dependent: :destroy
на dependent: :delete
class User < ActiveRecord::Base
# delete prevents Staff :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
Работал отлично для меня, если одной стороне не нужны другие обратные вызовы.
Ответ 2
Я тоже столкнулся с этой проблемой и придумал решение, которое не очень красиво, но работает. По сути, вы просто используете destroy_user
, который похож на destroy_staff
.
class User < ActiveRecord::Base
has_one: :staff
after_destroy :destroy_staff
def destroy_staff
staff.destroy if staff && !staff.destroyed?
end
end
class Staff < ActiveRecord::Base
belongs_to :user
after_destroy :destroy_user
def destroy_user
user.destroy if user && !user.destroyed?
end
end