如何以嵌套的Rails形式引用模型的现有实例? [英] How do I reference an existing instance of a model in a nested Rails form?

查看:48
本文介绍了如何以嵌套的Rails形式引用模型的现有实例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用以下三种主要模型构建一个食谱管理器应用程序:

I'm attempting to build a recipe-keeper app with three primary models:

食谱-特定菜肴的食谱
成分-通过唯一性验证的成分列表
数量-成分和配方之间的联接表,还反映了特定配方所需的特定成分的数量.

Recipe - The recipe for a particular dish
Ingredient - A list of ingredients, validated on uniqueness
Quantity - A join table between Ingredient and Recipe that also reflects the amount of a particular ingredient required for a particular recipe.

我使用的是嵌套表格(请参见下文),该表格是通过嵌套表格上的棒Railscast构建的(第2部分)以获取灵感.(由于这种特定模式的需要,我的表单在某种程度上比本教程要复杂,但是我能够使它以类似的方式工作.)

I'm using a nested form (see below) that I constructed using an awesome Railscast on Nested Forms (Part 1, Part 2) for inspiration. (My form is in some ways more complex than the tutorial due to the needs of this particular schema, but I was able to make it work in a similar fashion.)

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

However, when my form is submitted, any and all ingredients listed are created anew—and if the ingredient already exists in the DB, it fails the uniqueness validation and prevents the recipe from being created. Total drag.

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

So my question is: Is there a way to submit this form so that if an ingredient exists whose name matches one of my ingredient-name fields, it references the existing ingredient instead of attempting to create a new one with the same name?

下面的代码详细信息...

Code specifics below...

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 :

Here's my nested form that displays at 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_to link_to_function 可以即时添加和删除数量/成分对,它们是从前面提到的Railscast改编而来的.他们可以使用一些重构,但是可以按需工作或多或少.

The link_to and link_to_function are there to allow the addition and removal of quantity/ingredient pairs on the fly, and were adapted from the Railscast mentioned earlier. They could use some refactoring, but work more or less as they should.

更新:根据Leger的要求,以下是 recipes_controller.rb 中的相关代码.在 Recipes#new 路线中, 3.times {@ recipe.quantities.build} 为任何给定的配方设置三个空白数量/成分对.您可以使用上面提到的添加成分"和删除"链接将其删除或添加.

Update: Per Leger's request, here's the relevant code from recipes_controller.rb. In the Recipes#new route, 3.times { @recipe.quantities.build } sets up three blank quantity/ingredient pairs for any given recipe; these can be removed or added to on the fly using the "Add ingredient" and "remove" links mentioned above.

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 的职责是创建适当的对象em到Model.请分享控制器的相关代码

You shouldn't put the logic of ingredients match into view - it's duty of Recipe#create to create proper objects before passing 'em to Model. Pls share the relevant code for controller

在编写代码之前很少注意:

Few notes before coming to code:

  1. 我使用Rails4@ruby2.0,但尝试编写与Rails3兼容的代码.
  2. 在Rails 4中不推荐使用
  3. attr_acessible ,因此使用了更强的参数.如果您想升级您的应用,只需从一开始就使用强大的参数即可.
  4. 建议使 Ingredient 小写,以在不区分大小写的基础上提供统一的外观
  1. I use Rails4@ruby2.0, but tried to write Rails3-compatible code.
  2. attr_acessible was deprecated in Rails 4, so strong parameters are used instead. If you ever think to upgrade your app, just go with strong parameters from the beginning.
  3. Recommend to make Ingredient low-cased to provide uniform appearance on top of case-insensitivity

好的,我们开始:

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

不区分大小写的小写 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'> 部分,以创建/更新配方:


<div id='ingredients'> part of adjusted form to create/update Recipe:

<%= 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' %> 

我们应该使用 Quantity nested_attributes中的:ingredient ,Rails在创建 params 时会添加 _attributes -part-哈希以进行进一步的批量分配.它允许在新动作和更新动作中使用相同的形式.为使此部分正常工作,应提前定义关联.请参见下面的调整后的 Recipe#new .

We should use :ingredient from Quantity nested_attributes and Rails will add up _attributes-part while creating params-hash for further mass assignment. It allows to use same form in both new and update actions. For this part works properly association should be defined in advance. See adjusted Recipe#new bellow.

,最后是 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 中,我们执行以下操作:

In prepare_recipe we do the following things:

  1. 查找具有给定名称的配料的ID
  2. 将Foreign_key quantity.ingredient_id 设置为ID
  3. quantity.ingredient.id 设置为ID(想想如果不这样做会发生什么情况,并在配方中更改成分名称会发生​​什么情况)
  1. Find ID of ingredient with given name
  2. Set foreign_key quantity.ingredient_id to ID
  3. Set quantity.ingredient.id to ID (think what happens if you don't do that and change ingredient name in Recipe)

享受!

这篇关于如何以嵌套的Rails形式引用模型的现有实例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆