Associative
What you're looking at is nested models.
Nested models basically allow you to append attributes to an instance of one model; those attributes being sent to an associative model.
This is achieved with 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
Because your models made me confused, I rewrote the structure for you. I think you're getting confused with join models etc:
#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
This will give you the ability to create a Recipe
, add new Ingredient
s, and generally keep your model much more robust.
Here's how you can make it work with controller and views etc:
#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
This will create a basic form, but we need to include the associated fields. There are several ways to do this, including the "manual" method from RailsCasts and using Cocoon.
The important thing to note is thus:
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.
You can see more about this on an answer I wrote some time back.
For your form, you need the following:
#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!");
}
});
});