Rails:删除嵌套连接模型记录时如何触发after_destroy回调

问题描述 投票:0回答:2

我有 3 个型号

User
Feature
UserFeature

class User < ActiveRecord::Base
  has_many :user_features
  has_many :features, through: :user_features
end


class Feature < ActiveRecord::Base
  has_many :user_features
  has_many :users, through: :user_features
end


class UserFeature < ActiveRecord::Base
  belongs_to :user
  belongs_to :feature
end

我已经在数据库中创建了许多功能,并在使用以下代码创建用户时将功能与用户相关联

<%= form_for @user do |f| %>
  // some user_fields
  <% Feature.all.each do |feature| %>
     <%= check_box "user[feature_ids][], feature.id %>
  <% end %>
  // submit button here
<% end %>

在我的

UserController
我有这个代码

class UserController < ApplicationController
  def create
    @user = User.new(permit_params)
    @user.save
  end

  def update
    @user = User.find(params[:id])
    @user.update_attributes(permit_params)
  end 

  private
    def permit_params
      params.require(:user).permit(:name, :email, user_feature_ids: [])
    end
end

当我提交时,它将创建和更新用户,并在

UserFeature
表中为我检查过的那些功能进行条目。

更新用户时,如果我取消选中任何功能,那么它将从

UserFeature

中删除相关记录

这里没有任何问题,一切都按预期进行。

但现在我想在删除 user_feature 时执行一些活动。

为此我在 UserFeature 中编写了一个回调

after_destroy :some_activity

class UserFeature < ActiveRecord::Base
  belongs_to :user
  belongs_to :feature

  after_destroy :some_activity

  def some_activity
     // some code
  end
end

但它不起作用,当我检查为什么它在删除 user_feature 时不调用 destroy 调用时,我发现它会调用 SQL 查询,而不是在未选中的 user_feature 上调用

destroy

这就是为什么

after
before
destroy
回调不起作用。

任何人都可以告诉我,我如何在删除时执行任何活动

UserFeature

ruby-on-rails ruby nested-attributes
2个回答
1
投票

after_destroy
回调不会在
delete
之后触发,因为这就是
delete
的实现方式。引用自docs

Active Record 对象未实例化,因此不会执行对象的回调,包括任何 :dependent 关联选项。 [...] 注意:虽然它通常比替代方案 #destroy 快得多,但跳过回调可能会绕过应用程序中确保引用完整性或执行其他重要工作的业务逻辑。

您可以在关联定义上使用

after_destroy
回调,而不是关联类上的
after_remove
回调,如 Rails Guide about Association Callbacks 中所述。


0
投票

解决方案是将代码设置为使用“自动删除连接模型”模式(因为此模式不会触发删除的回调)。

相反,使用

accepts_nested_attributes_for
声明将代码设置为标准嵌套资源,这触发删除回调。

模型是:

class User < ActiveRecord::Base
  has_many :user_features
  accepts_nested_attributes_for :user_features, allow_destroy: true
end

class Feature < ActiveRecord::Base
  has_many :user_features
end

class UserFeature < ActiveRecord::Base
  belongs_to :user
  belongs_to :feature

  after_destroy :some_activity

  def some_activity
    # Some code here to be executed after the Object destruction
  end
end

注:

  • 不需要
    through:
    声明中需要
    has_many
    ,因为您不会使用它附带的getter/setter
    .feature_ids
  • 确实需要带有
    accepts_nested_attributes_for
    选项的
    allow_destroy: true
    声明。

表格为:

<%= form_for @user do |user_form| %>

  // Any User fields here

  <%= user_form.fields_for :user_features, @user_features do |user_feature_form| %>

    // The UserFeature check_box field:
    <%= user_feature_form.check_box :_destroy,
                                    { checked: user_feature_form.object.persisted?,
                                      class: 'my_class' },
                                       checked_value = false,
                                     unchecked_value = true %>

    // Any other UserFeature fields here, i.e.:
    <%= user_feature_form.number_field :quantity,
                                        class: 'my_class' %>

  <% end %>

  // Submit button here

<% end %>

注:

  • 使用
    .fields_for
    方法生成
    UserFeature
    字段。
  • check_box 必须具有方法
    :_destroy
  • checked_value
    必须是
    false
    (如果选中,则不要删除)
  • unchecked_value
    必须是
    true
    (如果未选中,则do删除)

(基本)控制器是:

class UserController < ApplicationController
  def create
    @user = User.new(permit_params)
    @user.save
  end

  def new # or: def edit
    @user = User.find(params[:id])
    Feature.all.each do |feature|
      user_feature = @user.user_features.select {|u_f| u_f.feature_id == feature.id}[0]

      # If no corresponding UserFeature exists, build it (= non-persisted, which will determine if the check_box is checked or not)
      if user_feature.nil?
        @user.user_features.build(feature_id: feature.id, quantity: nil)
      end
    end

    # Optional: Sort (i.e. by Feature.name) to keep the list order fixed (pass this array to the form)
    @user_features = @user.user_features.sort_by {|u_f| u_f.feature.name}
  end

  def update
    @user = User.find(params[:id])
    @user.update_attributes(permit_params)
  end 

  private
    def permit_params
      params.require(:user).permit(:name, :email, user_features_attributes: [
                                    :id, :quantity, :_destroy
                                  ])
    end
end
© www.soinside.com 2019 - 2024. All rights reserved.