2

3 つの主要なモデルを使用してレシピ キーパー アプリを構築しようとしています。

Recipe - 特定の料理のレシピ
Ingredient - 一意性が検証された材料のリスト
Quantity - 特定のレシピに必要な特定の材料の量も反映する Ingredient と Recipe の間の結合テーブル。

ネストされたフォーム (パート 1パート 2 ) で素晴らしい Railscast を使用して作成したネストされたフォーム (以下を参照) を使用して、インスピレーションを得ています。(私のフォームは、この特定のスキーマの必要性により、いくつかの点でチュートリアルよりも複雑ですが、同様の方法で機能させることができました。)

ただし、フォームが送信されると、リストされているすべての材料が新たに作成されます。また、材料が DB に既に存在する場合は、一意性の検証に失敗し、レシピが作成されません。総抗力。

私の質問は次のとおりです。このフォームを送信して、成分名フィールドのいずれかに名前が一致する成分が存在する場合、同じ名前で新しい成分を作成するのではなく、既存の成分を参照する方法はありますか?

以下のコード仕様...


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_toとは、そのlink_to_function場で量/成分のペアを追加および削除できるようにするためにあり、前述の Railscast から採用されました。一部のリファクタリングを使用することもできますが、多かれ少なかれ正常に機能します。


更新: Leger の要求に従って、関連するコードをrecipes_controller.rb. Recipes#newルートでは、3.times { @recipe.quantities.build }特定のレシピに対して 3 つの空白の数量/材料のペアを設定します。これらは、上記の「材料を追加」および「削除」リンクを使用して、その場で削除または追加できます。

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
4

1 に答える 1

2

成分の一致のロジックを視野に入れるべきではありません -Recipe#createモデルに渡す前に適切なオブジェクトを作成する義務があります。Plsはコントローラーの関連コードを共有します

コードに入る前のいくつかの注意事項:

  1. Rails4@ruby2.0 を使っていますが、Rails3 互換のコードを書いてみました。
  2. attr_acessibleRails 4 で廃止されたため、代わりに強力なパラメーターが使用されます。アプリのアップグレードを考えている場合は、最初から強力なパラメーターを使用してください。
  3. Ingredient大文字と小文字を区別しない上に均一な外観を提供するために、小文字にすることをお勧めします

では、どうぞ:

、およびattr_accessibleの文字列を削除します。Recipe.rbQuantity.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' %> 

fromnested_attributesを使用する必要が:ingredientありQuantity、Rails は -part を追加しながら、さらに大量の割り当てを行うために_attributes-hash を作成します。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. foreign_keyquantity.ingredient_idを ID に設定
  3. IDに設定quantity.ingredient.id(そうしないとどうなるかを考えてレシピの材料名を変更)

楽しみ!

于 2013-10-24T21:36:13.883 に答える