如何在嵌套的Rails表单中引用模型的现有实例?
我正在尝试使用三个主要模型构建一个配方管理器应用程序:
食谱 – 特定菜肴的配方
成分 – 成分列表,经过独特性validation
数量 – 成分和配方之间的连接表,也反映了特定配方所需的特定成分的数量。
我正在使用嵌套表单(见下文),我在Nested Forms( 第1 部分 , 第2部分 )上使用了很棒的Railscast来构建灵感。 (由于这个特定模式的需要,我的表单在某些方面比教程更复杂,但我能够以类似的方式使它工作。)
但是,当我的表单被提交时,列出的任何和所有成分都会重新创建 – 如果成分已经存在于DB中,则它将无法通过唯一性validation并阻止创建配方。 总阻力。
所以我的问题是:有没有办法提交这个表格,以便如果存在一个名称与我的成分名称字段匹配的成分,它会引用现有成分而不是尝试创建一个具有相同名称的新成分?
以下代码细节……
在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 { :case_sensitive => false } has_many :quantities has_many :recipes, through: :quantities
这是我在Recipe#new
上显示的嵌套表单:
Ingredients
link_to
和link_to_function
用于允许动态添加和删除数量/成分对,并根据前面提到的Railscast进行了改编。 他们可以使用一些重构,但他们应该或多或少地工作。
更新: Per Leger的请求,这是recipes_controller.rb
的相关代码。 在Recipes#new
3.times { @recipe.quantities.build }
途径中,3 3.times { @recipe.quantities.build }
为任何给定的食谱设置三个空白数量/成分对; 这些可以使用上面提到的“添加成分”和“删除”链接在运行中删除或添加。
class RecipesController 'new' end end
你不应该将成分匹配的逻辑放入视图中 – 在将模式传递给模型之前, Recipe#create
需要创建适当的对象。 请分享控制器的相关代码
来代码前几点注意事项:
- 我使用Rails4@ruby2.0,但尝试编写与Rails3兼容的代码。
- 在Rails 4中不推荐使用
attr_acessible
,因此使用了强参数。 如果您曾想过要升级您的应用,请从一开始就使用强大的参数。 - 建议使用低
Ingredient
在不区分情况下提供均匀的外观
好的,我们走了:
删除attr_accessible
, 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
调整表格的一部分来创建/更新食谱:
<%= f.fields_for :quantities do |ff| %> <%= ff.fields_for :ingredient do |fff| %> <%= fff.label :name %> <%= fff.text_field :name, size: "10" %> <% end %> ... <% end %> <%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %>
我们应该使用:ingredient
来自Quantity
nested_attributes和Rails的:ingredient
将在创建params
-hash时添加_attributes
-part以进一步进行质量分配。 它允许在新操作和更新操作中使用相同的表单。 对于这部分,应该事先定义正确的关联。 请参阅调整后的Recipe#new
#new below。
最后是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
我们执行以下操作:
- 找到具有给定名称的成分的ID
- 将foreign_key
quantity.ingredient_id
设置为ID - 将
quantity.ingredient.id
设置为ID(想想如果你不这样做会发生什么,并在食谱中更改成分名称)
请享用!