ruby-on-rails - 如何在嵌套 Rails 表单中引用模型的现有实例?

标签 ruby-on-rails ruby ruby-on-rails-3 forms

我正在尝试构建一个具有三个主要模型的食谱管理应用程序:

食谱 - 特定菜肴的食谱
成分 - 成分列表,经过独特性验证
数量 - 成分和配方之间的连接表,也反射(reflect)特定配方所需的特定成分的数量。

我正在使用一个嵌套表单(见下文),该表单是我在嵌套表单( Part 1Part 2 )上使用很棒的 Railscast 构建的,以获取灵感。 (由于这个特定模式的需要,我的表单在某些方面比教程更复杂,但我能够使其以类似的方式工作。)

但是,当提交我的表单时,列出的所有成分都会重新创建 - 如果该成分已存在于数据库中,则它无法通过唯一性验证并阻止创建配方。总阻力。

所以我的问题是:有没有办法提交此表单,以便如果存在名称与我的成分名称字段之一匹配的成分,它会引用现有成分而不是尝试创建一个同名的新的?

代码细节如下...


Recipe.rb中:

class Recipe < ActiveRecord::Base
  attr_accessible :name, :description, :directions, :quantities_attributes,
                  :ingredient_attributes

  has_many :quantities, dependent: :destroy
  has_many :ingredients, through: :quantities
  accepts_nested_attributes_for :quantities, allow_destroy: true

Quantity.rb中:

class Quantity < ActiveRecord::Base
  attr_accessible :recipe_id, :ingredient_id, :amount, :ingredient_attributes

  belongs_to :recipe
  belongs_to :ingredient
  accepts_nested_attributes_for :ingredient

Ingredient.rb中:

class Ingredient < ActiveRecord::Base
  attr_accessible :name
  validates :name, :uniqueness => { :case_sensitive => false }

  has_many :quantities
  has_many :recipes, through: :quantities

这是我的嵌套表单,显示在Recipe#new:

<%= form_for @recipe do |f| %>
  <%= render 'recipe_form_errors' %>

  <%= f.label :name %><br>
  <%= f.text_field :name %><br>
  <h3>Ingredients</h3>

  <div id='ingredients'>
    <%= f.fields_for :quantities do |ff| %>
      <div class='ingredient_fields'>
        <%= ff.fields_for :ingredient_attributes do |fff| %>
          <%= fff.label :name %>
          <%= fff.text_field :name %> 
        <% end %>
        <%= ff.label :amount %>
        <%= ff.text_field :amount, size: "10" %>
        <%= ff.hidden_field :_destroy %>
        <%= link_to_function "remove", "remove_fields(this)" %><br>
      </div>
    <% end %>
    <%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %> 
  </div><br>

  <%= f.label :description %><br>
  <%= f.text_area :description, rows: 4, columns: 100 %><br>
  <%= f.label :directions %><br>
  <%= f.text_area :directions, rows: 4, columns: 100 %><br>
  <%= f.submit %>
<% end %>

link_tolink_to_function 允许动态添加和删除数量/成分对,并改编自前面提到的 Railscast。他们可以使用一些重构,但或多或​​少会按应有的方式工作。


更新:根据 Leger 的要求,以下是来自 recipes_controller.rb 的相关代码。在 Recipes#new route ,3.times { @recipe.quantities.build } 为任何给定的配方设置三个空白数量/成分对;可以使用上面提到的“添加成分”和“删除”链接即时删除或添加这些内容。

class RecipesController < ApplicationController

  def new
    @recipe = Recipe.new
    3.times { @recipe.quantities.build }
    @quantity = Quantity.new
  end

  def create
    @recipe = Recipe.new(params[:recipe])

    if @recipe.save
      redirect_to @recipe
    else
      render :action => 'new'
    end
  end

最佳答案

你不应该将成分匹配的逻辑纳入考虑 - 这是 Recipe#create 的职责在将对象传递给模型之前创建适当的对象。请分享 Controller 的相关代码

编写代码之前的一些注意事项:

  1. 我使用[email protected] ,但尝试编写与 Rails3 兼容的代码。
  2. attr_acessible Rails 4 中已弃用,因此改用强参数。如果您想升级您的应用,请从一开始就采用强大的参数。
  3. 推荐制作Ingredient低大小写,在不区分大小写的基础上提供统一的外观

好的,我们开始:

删除attr_accessible Recipe.rb 中的字符串, Quantity.rbIngredient.rb .

不区分大小写,小写 Ingredient.rb :

class Ingredient < ActiveRecord::Base
  before_save { self.name.downcase! } # to simplify search and unified view
  validates :name, :uniqueness => { :case_sensitive => false }

  has_many :quantities
  has_many :recipes, through: :quantities
end


<div id='ingredients'>用于创建/更新配方的调整表单的一部分:

<%= f.fields_for :quantities do |ff| %>
  <div class='ingredient_fields'>
    <%= ff.fields_for :ingredient do |fff| %>
      <%= fff.label :name %>
      <%= fff.text_field :name, size: "10" %>
    <% end %>
    ...
  </div>
<% end %>
<%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %> 

我们应该使用:ingredient来自Quantity nested_attributes 和 Rails 相加 _attributes -创建时的部分params -用于进一步批量分配的哈希值。它允许在新操作和更新操作中使用相同的表单。为了使该部分正常工作,应提前定义关联。查看调整后的Recipe#new如下。

最后是recipes_controller.rb :

def new
  @recipe = Recipe.new
  3.times do
    @recipe.quantities.build #initialize recipe -> quantities association
    @recipe.quantities.last.build_ingredient #initialize quantities -> ingredient association
  end
end

def create
  @recipe = Recipe.new(recipe_params)    
  prepare_recipe

  if @recipe.save ... #now all saved in proper way
end

def update
  @recipe = Recipe.find(params[:id])
  @recipe.attributes = recipe_params
  prepare_recipe    

  if @recipe.save ... #now all saved in proper way
end

private 
def prepare_recipe
  @recipe.quantities.each do |quantity|
    # do case-insensitive search via 'where' and building SQL-request
    if ingredient = Ingredient.where('LOWER(name) = ?', quantity.ingredient.name.downcase).first
      quantity.ingredient_id = quantity.ingredient.id = ingredient.id
    end
  end
end

def recipe_params
  params.require(:recipe).permit(
    :name,
    :description,
    :directions,
    :quantities_attributes => [
      :id,
      :amount,
      :_destroy,
      :ingredient_attributes => [
        #:id commented bc we pick 'id' for existing ingredients manually and for new we create it
        :name
  ]])
end

prepare_recipe我们做以下事情:

  1. 查找具有给定名称的成分 ID
  2. 设置外键 quantity.ingredient_id到身份证
  3. 设置quantity.ingredient.id到 ID(想想如果你不这样做并更改食谱中的成分名称会发生​​什么)

享受吧!

关于ruby-on-rails - 如何在嵌套 Rails 表单中引用模型的现有实例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19574130/

相关文章:

ruby-on-rails - 如果 config.assets.compile = false,为什么我的 Rails 应用程序无法在生产环境中运行?

ruby-on-rails - 如何在 Rail3App 中使用 Ruby 在 Active Directory 中存储图片?

ruby-on-rails - Rails 设计错误 "NoMethodError ... merge"

ruby-on-rails - 如何在 Rails 应用程序中使用磁盘配额?

ruby-on-rails - 修改 Rails 助手以添加 HTML 类

ruby-on-rails - 在 rspec 中编写测试用例时出错

ruby - 如何在 pry 中退出循环

HTML 和 CSS 背景图像

语法异常的 Ruby 方法

ruby-on-rails-3 - 如何在simple_form中的标签标签后插入<br>?