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

amrnrhlw  于 2023-01-27  发布在  Ruby
关注(0)|答案(2)|浏览(143)

我有三个型号UserFeatureUserFeature

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

我已经在DB中创建了许多特性,并在使用以下代码创建用户时将特性与用户相关联

<%= 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
这就是afterbeforedestroy回调不起作用的原因。
有谁能让我知道,如何才能在删除UserFeature时执行任何活动?

vs3odd8k

vs3odd8k1#

after_destroy回调不会在delete之后触发,因为delete是这样实现的。引用docs
活动记录对象不会示例化,因此对象的回调不会执行,包括任何:依赖关联选项。[...]尽管它通常比替代方法#destroy快得多,但跳过回调可能会绕过应用程序中确保引用完整性或执行其他基本工作的业务逻辑。
您可以在关联定义上使用after_remove回调,而不是在关联类上使用after_destroy回调,如Rails指南中关于关联回调所述。

3b6akqbq

3b6akqbq2#

解决方案是不要将代码设置为使用"Automatic deletion of a join model"模式(因为此模式不会触发删除回调)。
相反,使用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

注:

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

表格为:

<%= 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字段。
  • 复选框必须具有方法:_destroy
  • checked_value必须为false(如果选中,则删除)
  • unchecked_value必须为true(如果未选中,则执行删除)

(基本)控制器将是:

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

相关问题