In this article, the goal is to create the following input form.

Creating a function to save recipes and ingredients required for recipes together in the DB
Since the recipe and the ingredients of the recipe are in a parent-child relationship, the table structure is as follows. Parent: recipes Child: Ingredients for recipe (recipe_ingredients)
We will carry out in the following order.
Install jquery to be able to use cocoon with rails6.
$ yarn add jquery 
Edit config / webpack / environment.js
config/webpack/environment.js
const { environment } = require('@rails/webpacker')
#Postscript from here
const webpack = require('webpack')
environment.plugins.prepend('Provide',
    new webpack.ProvidePlugin({
        $: 'jquery/src/jquery',
        jQuery: 'jquery/src/jquery'
    })
)
#Postscript up to here
module.exports = environment
Introduction of gem
Gemfile
gem 'cocoon'
$ bundle install
Add library
$ yarn add github:nathanvda/cocoon#c24ba53
After execution, it is OK if you can clear the following two items. ・ App / assets / javascripts / cocoon.js has been created -The following description has been added to package.json
package.json
"cocoon": "github:nathanvda/cocoon#c24ba53"
Finally, add the following contents to app / javascriptspacks / application.js
app/javascriptspacks/application.js
require('jquery')
import "cocoon";
The description that is not related to the implementation contents this time is omitted.
Creating a model
$ rails g model Recipe
$ rails g model RecipeIngredient
Edit migration file
class CreateRecipes < ActiveRecord::Migration[6.0]
  def change
    create_table :recipes do |t|
      t.string     :name,     null: false
      t.timestamps
    end
  end
end
class CreateRecipeIngredients < ActiveRecord::Migration[6.0]
  def change
    create_table :recipe_ingredients do |t|
      t.references :recipe,            null: false, foreign_key: true
      t.integer    :ingredient_id,     null: false
      t.integer    :quantity,          null: false
      t.timestamps
    end
  end
end
Perform migration
$ rails db:migrate
Association settings
recipe.rb
class Recipe < ApplicationRecord
  has_many :recipe_ingredients, dependent: :destroy
  accepts_nested_attributes_for :recipe_ingredients
end
recipe_ingredient.rb
class RecipeIngredient < ApplicationRecord
  belongs_to :recipe
end
accepts_nested_attributes_for The data of the specified model can be included in the parameter as an array. In other words, you will be able to save the data for both the recipe and recipe_ingredients models together.
Creating a controller
$ rails g controller recipes new create
Edit the contents of the controller
recipes_controller.rb
class RecipesController < ApplicationController
  def new
    @recipe = Recipe.new
    @recipe_ingredients = @recipe.recipe_ingredients.build
  end
  def create
    @recipe = Recipe.new(recipe_params)
    if @recipe.save
      redirect_to root_path
    else
      render action: :new
    end
  end
  private
  def recipe_params
    params.require(:recipe).permit(:name, recipe_ingredients_attributes: [:id, :ingredient_id, :quantity, :_destroy])
  end
end
Recipe_ingredient model specified by accepts_nested_attributes_for, I added it to params as recipe_ingredients_attributes: [] and sent it.
As with the model, the description that is not related to the implementation contents this time is omitted.
ruby:recipes/new.html.erb
<%= form_with model: @recipe, url: '/recipes', method: :post, local: true do |f| %>
  <!--Recipe name-->
  <%= f.text_area :name %>
  <!--Ingredient input field-->
  <%= f.fields_for :recipe_ingredients do |t| %>
    <%= render "recipes/recipe_ingredient_fields", f: t %>
  <% end %>
  <!--Ingredient addition button-->
  <%= link_to_add_association "add to", f, :recipe_ingredients %>
<% end %>
fields_for You will be able to edit different models within form_with.
ruby:recipes/_recipe_ingredient_fields.html.erb
<div class="nested-fields">
    <%= f.collection_select(:ingredient_id, {}, :id, :name, {}) %>
    <%= f.number_field :quantity %>
    <div>Pieces</div>
    <%= link_to_remove_association "Delete", f %>
</div>
The range enclosed by the div tag specified by the nested-fields class is the area to be added / deleted.
Pay attention to the partial template name to render. If it is not "_child model_fields.html.erb", an error will occur.
Thank you for your hard work. With the above, I think that you can create a dynamic input form.
Introduction of cocoon in Rails 6 Set up a cocoon gem that can implement nested forms concisely in a webpack environment
About creating dynamic input form [Rails] How to save multiple data in parent-child relationship table at the same time using cocoon
About fields_for How to use fields_for well
Recommended Posts