【问题标题】:Rails nested form on many-to-many: how to prevent duplicates?Rails 在多对多上嵌套表单:如何防止重复?
【发布时间】:2012-05-23 10:11:38
【问题描述】:

我在我的 rails 3.2.3 应用程序中设置了一个嵌套表单,它工作正常,我的模型是:

class Recipe < ActiveRecord::Base
  attr_accessible :title, :description, :excerpt, :date, :ingredient_lines_attributes

  has_and_belongs_to_many :ingredient_lines
  accepts_nested_attributes_for :ingredient_lines
end

和:

class IngredientLine < ActiveRecord::Base
  attr_accessible :ingredient_id, :measurement_unit_id, :quantity

  has_and_belongs_to_many :recipes
  belongs_to :measurement_unit
  belongs_to :ingredient
end

如上所述,一个食谱可以有多个成分行,反之亦然。

我要避免的是 IngredienLine 表上的记录重复。

例如,假设对于 recipe_1,与 {"measurement_unit_id" => 1, "ingredient_id" => 1, "quantity" => 3.5} 关联的成分线,如果配方_5 的成分线子表单由用户编译相同的值,我不想在 IngredientLine 表上创建新记录,而只希望在连接表 ingredients_lines_recipes 中有新的关联记录。

请注意,目前我没有任何成分线控制器,因为保存和更新成分线是由嵌套表单例程处理的。甚至我的食谱控制器也是简单而标准的:

class RecipesController < ApplicationController
  respond_to :html

  def new
    @recipe = Recipe.new
  end

  def create
    @recipe = Recipe.new(params[:recipe])
    flash[:notice] = 'Recipe saved.' if @recipe.save  
    respond_with(@recipe)
  end

  def destroy
    @recipe = Recipe.find(params[:id])
    @recipe.destroy
    respond_with(:recipes)
  end

  def edit
    respond_with(@recipe = Recipe.find(params[:id]))
  end

  def update
    @recipe = Recipe.find(params[:id])
    flash[:notice] = 'Recipe updated.' if @recipe.update_attributes(params[:recipe])
    respond_with(@recipe)
  end

end

我的猜测是,应该足以用 find_or_create 覆盖 IngredientLine 的标准 create 行为,但我不知道如何实现。

但是还有一点需要注意,想象一个存在一些成分线的子表单的编辑,如果我添加另一个成分线,它已经存储在成分线表中,轨道当然不应该在成分线表上写任何东西,但还应该区分已经与父关联的子记录,以及需要为其创建关系的新子记录,在连接表上写入新记录。

谢谢!

【问题讨论】:

    标签: ruby ruby-on-rails-3 many-to-many nested-forms nested-attributes


    【解决方案1】:

    在Recipe模型中重新定义方法

    def ingredient_lines_attributes=(attributes)
       self.ingredient_lines << IngredientLine.where(attributes).first_or_initialize
    end
    

    【讨论】:

    • 谢谢,试过了,但是一旦创建就不能销毁孩子,每次保存父母时关联也会重复。我越来越接近覆盖assign_nested_attributes_for_collection_association(association_name, attributes_collection, assignment_opts = {}),但问题似乎是association.build 总是想保存记录,而association.add_to_target 没有添加获取的对象,不知道为什么。 github.com/rails/rails/blob/…
    • 所以唯一的选择是删除accepts_nested_att...,并编写自己的getter和setter,在setter中你必须检查self.new_record?然后...
    • 好吧,我想.. 但根本不是一个优雅的解决方案,因为您无法利用 Rails 核心中的嵌套表单代码。我得出的结论是,这个问题与无法正确处理多对多关系直接相关,这也是不允许复合主键的结果。如果我尝试以多对多的方式插入和关联记录,则框架应检查记录是否存在,如果存在,则仅在连接表中创建与记录的关系。这是不可能的,因为通用 ID pk 通常不是表示唯一约束的好选择
    【解决方案2】:

    老问题,但我有同样的问题。忘记使用 rails 4 strong_parameters 将 :id 添加到白名单。

    例如:

    widgets_controller.rb

    def widget_params
      params.require(:widget).permit(:name, :foos_attributes => [:id, :name, :_destroy],)
    end
    

    widget.rb

    class Widget < ActiveRecord::Base
      has_many :foos, dependent: :destroy
      accepts_nested_attributes_for :foos, allow_destroy: true
    end
    

    foo.rb

    class Foo < ActiveRecord::Base
      belongs_to :widget
    end
    

    【讨论】:

      【解决方案3】:

      我也遇到过类似的情况,在this answer 中找到了灵感。简而言之,在节省时间之前,我不担心嵌套模型的重复。

      翻译为您的示例,我将autosave_associated_records_for_ingredient_lines 添加到Recipe。正如您的直觉所说,它遍历ingredient_lines 并执行find_or_create。如果成分线很复杂,Yuri 的first_or_initialize 方法可能更简洁。

      我相信这具有您正在寻找的行为:嵌套模型永远不会重复,但编辑模型会导致新记录而不是更新共享模型。孤立的ingredient_lines 的可能性很大,但如果这是一个严重的问题,您可以选择更新,如果该模型只有一个recipe,其 id 与当前的匹配。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-02-22
        • 2015-12-30
        • 2020-02-21
        • 2011-10-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多