我的 rails 嵌套模型无法识别 _destroy 属性

Posted

技术标签:

【中文标题】我的 rails 嵌套模型无法识别 _destroy 属性【英文标题】:My rails nested model isn't recognizing _destroy attribute 【发布时间】:2020-08-08 04:57:35 【问题描述】:

我有一个父模型(鸡尾酒)和一个孩子(剂量)。

我有一个嵌套表单,我希望能够在其中创建/删除剂量对象。

创建部分在强参数中没有_destroy 属性的情况下工作,但是当我将_destroy 添加到属性哈希以便能够删除时,我收到错误unknown attribute '_destroy' for Dose。我不确定我错在哪里。

我还认为我的代码过于复杂了,应该有一种比我正在做的更简单的方法来创建剂量(add_doses 方法是否必要?)。如果我能得到一些反馈,我将不胜感激。

鸡尾酒:

class Cocktail < ApplicationRecord
  validates :name, uniqueness: true, presence: true

  has_many :doses, dependent: :destroy
  has_many :ingredients, through: :doses
  accepts_nested_attributes_for :doses, allow_destroy: true
end

剂量:

class Dose < ApplicationRecord
  validates :description, presence: true
  belongs_to :cocktail
  belongs_to :ingredient
  validates :cocktail, uniqueness:  scope: :ingredient 
 end

控制器:

class CocktailsController < ApplicationController
  before_action :set_task, only: %i[show edit update]

  def index
    @cocktails = Cocktail.all
  end

  def show; end

  def new
    @cocktail = Cocktail.new
    @cocktail.doses.build
  end

  def create
    @cocktail = Cocktail.new(cocktail_params)
    @cocktail.save
    add_doses
    redirect_to cocktails_path
  end

  def edit; end

  def update
    @cocktail.update(cocktail_params)
    add_doses
    redirect_to cocktail_path(@cocktail)
  end

  private

  def set_task
    @cocktail = Cocktail.find(params[:id])
  end

  def cocktail_params
    params[:cocktail][:name] = params[:cocktail][:name].downcase.titleize
    params.require(:cocktail).permit(:name)
  end

  def add_doses
    @cocktail.doses.destroy_all
    strong_params = params.require(:cocktail).permit(doses_attributes: [:description, :ingredient_id, :_destroy, :id])
    params[:cocktail][:doses_attributes].each_key do |key|
      @cocktail.doses.create(strong_params[:doses_attributes][key])
    end
  end
end

我的主窗体视图:

<%= simple_form_for @cocktail do |f| %>
  <%= f.input :name, required: true %>
  <%= f.nested_fields_for :doses do |dose| %>
    <%= render '/cocktails/partials/doses_fields', f: dose %>
  <% end %>
  <div class="btn-group" role="group" aria-label="Basic example">
    <%= link_to_add_association 'add dose', f, :doses, partial: '/cocktails/partials/doses_fields', class: "btn btn-secondary" %>
    <%= f.button :submit, class: "btn btn-secondary" %>
  </div>
<% end %>

我对添加新剂量的部分看法:

<div class='nested-fields'>
  <div class="field">
    <%= f.text_field :description %>
  </div>
    <%= f.association :ingredient, collection: Ingredient.all %>
    <%= link_to_remove_association "remove dose", f %>
</div>

如果您还想指出我的代码中的所有问题,请不要退缩,做最坏的人;)

【问题讨论】:

_destroy 参数是一个特殊的标志,它被传递到嵌套对象的属性参数中,因此关联的对象将被销毁。它不是 Dose 的属性,因此当您尝试使用 @cocktail.doses.create(strong_params[:doses_attributes][key]) 创建新 Dose 时它会中断@ 另外我认为你不需要_delete,因为你已经用@cocktail.doses.destroy_all删除了所有相关的剂量? 我添加了@cocktail.doses.destroy_all 以使其正常工作。而且它仍然没有删除它们,这很奇怪。它只会不断添加 【参考方案1】:

首先修复您的#create#update 方法,以便它们在您重定向之前检查记录是否实际保留!

然后将嵌套参数添加到白名单中并砍掉add_doses 方法,这是一种创造性但极有缺陷的尝试复制嵌套属性提供的功能。

class CocktailsController < ApplicationController
  before_action :set_cocktail, only: %i[show edit update]

  def index
    @cocktails = Cocktail.all
  end

  def show; end

  def new
    @cocktail = Cocktail.new
    @cocktail.doses.build
  end

  def create
    @cocktail = Cocktail.new(cocktail_params)
    if @cocktail.save
      redirect_to cocktails_path
    else
      render :new
    end
  end

  def edit; end

  def update
    if @cocktail.update(cocktail_params)
      redirect_to @cocktail
    else
      render :edit
    end
  end

  private

  def set_cocktail
    @cocktail = Cocktail.find(params[:id])
  end

  def cocktail_params
    # this should be done in the model
    params[:cocktail][:name] = params[:cocktail][:name].downcase.titleize
    params.require(:cocktail)
          .permit(
             :name,
             doses_attributes: [
               :id,
               :_destroy,
               :description,
               :ingredient_id
             ]
          )
  end
end

如果您想让用户删除嵌套记录,只需创建一个复选框:

<div class='nested-fields'>
  <div class="field">
    <%= f.text_field :description %>
  </div>
    <%= f.association :ingredient, collection: Ingredient.all %>
    <%= f.input :_destroy, as: :boolean, label: 'Remove' %>
</div>

您还需要修复您的验证:

class Dose < ApplicationRecord
  validates :description, presence: true
  belongs_to :cocktail
  belongs_to :ingredient
  validates :cocktail_id, uniqueness:  scope: :ingredient_id 
end

创建唯一性验证时,您需要将其设置为验证实际列的唯一性,而不是关联。

【讨论】:

很棒的东西,谢谢!只是我不清楚的一件事,通过仅将cocktail_params 发送到鸡尾酒对象,这是否会在默认情况下在参数中创建所有(几个)嵌套剂量?是不是我一个个去创建某种循环来创建? 不。这就是嵌套参数为您所做的。我建议阅读文档api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/…

以上是关于我的 rails 嵌套模型无法识别 _destroy 属性的主要内容,如果未能解决你的问题,请参考以下文章

Rails 嵌套模型和虚拟属性初始化

将用户与 Rails 中的嵌套模型相关联

Rails 中嵌套资源视图的最佳实践?

Rails视图中的嵌套模型

如何将 Rails 脚本相互嵌套?

具有多个嵌套模型的 Rails 表单会导致无线电组出现问题