我正在尝试使用三种主要模型构建配方管理器应用程序:
食谱 - 特定菜肴的食谱
成分 - 成分列表,经过独特性验证
数量 - 成分和配方之间的连接表,也反映了特定配方所需的特定成分的数量。
我正在使用嵌套表单(见下文),我在嵌套表单(Part 1,Part 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进行改编。他们可以使用一些重构,但应该或多或少地工作。
更新:根据Le Leger的要求,这是recipes_controller.rb
的相关代码。在Recipes#new
路线中,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
答案 0 :(得分:2)
你不应该将成分匹配的逻辑放入视图中 - Recipe#create
的责任是在将em传递给Model之前创建正确的对象。请分享控制器的相关代码
来代码前几点注意事项:
attr_acessible
,因此使用了强参数。如果你想过升级你的应用程序,只需从头开始使用强大的参数。Ingredient
低限以便在不区分大小写的基础上提供统一的外观好的,我们走了:
删除attr_accessible
,Recipe.rb
和Quantity.rb
中的Ingredient.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
nested_attributes中的Quantity
,并且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
中,我们执行以下操作:
quantity.ingredient_id
设置为ID quantity.ingredient.id
设置为ID(想想如果不这样做会发生什么,并在食谱中更改成分名称)享受!