如何从父模型创建子模型
How to create a child model from a parent
几天来,我一直在用 Rails 旋转可能的轮子。我正在使用三个模型:IngredientBase
、Ingredient
和 IngredientList
。 IngredientBase
包含一个字符串,可以是 "Chicken"、"Eggs" 或 "Bacon." 成分将有一个 Ingredient
和一个数量。 IngredientList
会有很多配料。
我希望用户能够 ingredient_lists/new 并且能够在提交 IngredientList
之前创建多个成分。如果不让用户提交多个表单,我怎么能做到这一点?
处理嵌套关系的最佳方式是使用accept_nested_attributes
为了更好地理解这里是 link
要在表单中管理 ui 级别表单,这里是 gem
关联
您正在查看的是nested models。
嵌套模型基本上允许您将属性附加到一个模型的实例;这些属性被发送到关联模型。
这是通过accepts_nested_attributes_for
实现的:
#app/models/ingredient_list.rb
class IngrendientList < ActiveRecord::Base
has_many :ingredients
accepts_nested_attributes_for :ingredients
end
#app/models/ingredient.rb
class Ingredient < ActiveRecord::Base
belongs_to :ingredient_list
end
因为你的模型让我很困惑,所以我给你重写了结构。我认为您对连接模型等感到困惑:
#app/models/ingredient.rb
class Ingredient < ActiveRecord::Base
#columns id | name | weight | etc | created_at | updated_at
has_many :recipe_ingredients
has_many :recipes, through: :recipe_ingredients
end
#app/models/recipe_ingredient.rb
class RecipeIngredient < ActiveRecord::Base
#columns id | recipe_id | ingredient_id | quantity | created_at | updated_at
belongs_to :recipe
belongs_to :ingredient
accepts_nested_attributes_for :ingredient
end
#app/models/recipe.rb
class Recipe < ActiveRecord::Base
#columns id | name | etc | etc | created_at | updated_at
has_many :recipe_ingredients #-> allows extra attributes
has_many :ingredients, through: :recipe_ingredients
accepts_nested_attributes_for :recipe_ingredients
end
这将使您能够创建 Recipe
、添加新的 Ingredient
,并通常使您的模型更加稳健。
以下是如何让它与控制器和视图等一起工作:
#app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
def new
@recipe = Recipe.new
end
def create
@recipe = Recipe.new recipe_params
@recipe.save
end
private
def recipe_params
params.require(:recipe).permit(:recipe, :params, recipe_ingredients_attributes: [:quantity, ingredient_attributes:[:name]])
end
end
这将创建一个基本表单,但我们需要包括相关字段。有几种方法可以做到这一点,包括 RailsCasts and using Cocoon.
中的 "manual" 方法
因此需要注意的重要事项是:
Every time you call a nested form field, you're basically getting
Rails to add another instance of f.fields_for
to your form. The
VITAL thing to note here is you need to have the child_index of the
fields_for
block. This is what Rails uses to identify the fields,
and needs to be kept unique.
您可以在 an answer I wrote some time back 上看到更多相关信息。
对于您的表单,您需要以下内容:
#app/views/recipes/new.html.erb
<%= form_for @recipe do |f| %>
<%= f.text_field :title %>
<%= render "ingredients", locals: {f: f, child_index: Time.now.to_i} %>
<%= link_to "Add Ingredient", recipes_add_field_path, class: "ingredient" %>
<%= f.submit %>
<% end %>
#app/views/recipes/_ingredients.html.erb
<%= f.fields_for :recipe_ingredients, child_index: child_index do |ri| %>
<%= ri.text_field :quantity %>
<%= ri.fields_for :ingredient do |ingredient| %>
<%= ingredient.text_field :name %>
<%= ingredient.text_field :weight %>
<% end %>
<% end %>
#config/routes.rb
resources :recipes do
member "add_field", to: "recipes#add_field"
end
#app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
def add_field
@recipe = Recipe.new
@recipe.recipe_ingredients.build.build_ingredient
render "new"
end
end
#app/assets/javascripts/application.js
$(document).on("click", "a.ingredient", function(){
$.ajax({
url: '/recipes/add_field',
success: function(data){
el_to_add = $(data).html();
$('#recipes').append(el_to_add);
}
error: function(data){
alert("Sorry, There Was An Error!");
}
});
});
几天来,我一直在用 Rails 旋转可能的轮子。我正在使用三个模型:IngredientBase
、Ingredient
和 IngredientList
。 IngredientBase
包含一个字符串,可以是 "Chicken"、"Eggs" 或 "Bacon." 成分将有一个 Ingredient
和一个数量。 IngredientList
会有很多配料。
我希望用户能够 ingredient_lists/new 并且能够在提交 IngredientList
之前创建多个成分。如果不让用户提交多个表单,我怎么能做到这一点?
处理嵌套关系的最佳方式是使用accept_nested_attributes
为了更好地理解这里是 link
要在表单中管理 ui 级别表单,这里是 gem
关联
您正在查看的是nested models。
嵌套模型基本上允许您将属性附加到一个模型的实例;这些属性被发送到关联模型。
这是通过accepts_nested_attributes_for
实现的:
#app/models/ingredient_list.rb
class IngrendientList < ActiveRecord::Base
has_many :ingredients
accepts_nested_attributes_for :ingredients
end
#app/models/ingredient.rb
class Ingredient < ActiveRecord::Base
belongs_to :ingredient_list
end
因为你的模型让我很困惑,所以我给你重写了结构。我认为您对连接模型等感到困惑:
#app/models/ingredient.rb
class Ingredient < ActiveRecord::Base
#columns id | name | weight | etc | created_at | updated_at
has_many :recipe_ingredients
has_many :recipes, through: :recipe_ingredients
end
#app/models/recipe_ingredient.rb
class RecipeIngredient < ActiveRecord::Base
#columns id | recipe_id | ingredient_id | quantity | created_at | updated_at
belongs_to :recipe
belongs_to :ingredient
accepts_nested_attributes_for :ingredient
end
#app/models/recipe.rb
class Recipe < ActiveRecord::Base
#columns id | name | etc | etc | created_at | updated_at
has_many :recipe_ingredients #-> allows extra attributes
has_many :ingredients, through: :recipe_ingredients
accepts_nested_attributes_for :recipe_ingredients
end
这将使您能够创建 Recipe
、添加新的 Ingredient
,并通常使您的模型更加稳健。
以下是如何让它与控制器和视图等一起工作:
#app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
def new
@recipe = Recipe.new
end
def create
@recipe = Recipe.new recipe_params
@recipe.save
end
private
def recipe_params
params.require(:recipe).permit(:recipe, :params, recipe_ingredients_attributes: [:quantity, ingredient_attributes:[:name]])
end
end
这将创建一个基本表单,但我们需要包括相关字段。有几种方法可以做到这一点,包括 RailsCasts and using Cocoon.
中的 "manual" 方法因此需要注意的重要事项是:
Every time you call a nested form field, you're basically getting Rails to add another instance of
f.fields_for
to your form. The VITAL thing to note here is you need to have the child_index of thefields_for
block. This is what Rails uses to identify the fields, and needs to be kept unique.
您可以在 an answer I wrote some time back 上看到更多相关信息。
对于您的表单,您需要以下内容:
#app/views/recipes/new.html.erb
<%= form_for @recipe do |f| %>
<%= f.text_field :title %>
<%= render "ingredients", locals: {f: f, child_index: Time.now.to_i} %>
<%= link_to "Add Ingredient", recipes_add_field_path, class: "ingredient" %>
<%= f.submit %>
<% end %>
#app/views/recipes/_ingredients.html.erb
<%= f.fields_for :recipe_ingredients, child_index: child_index do |ri| %>
<%= ri.text_field :quantity %>
<%= ri.fields_for :ingredient do |ingredient| %>
<%= ingredient.text_field :name %>
<%= ingredient.text_field :weight %>
<% end %>
<% end %>
#config/routes.rb
resources :recipes do
member "add_field", to: "recipes#add_field"
end
#app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
def add_field
@recipe = Recipe.new
@recipe.recipe_ingredients.build.build_ingredient
render "new"
end
end
#app/assets/javascripts/application.js
$(document).on("click", "a.ingredient", function(){
$.ajax({
url: '/recipes/add_field',
success: function(data){
el_to_add = $(data).html();
$('#recipes').append(el_to_add);
}
error: function(data){
alert("Sorry, There Was An Error!");
}
});
});