2
3

More than 1 year has passed since last update.

【Rails】Cocoonを用いて親子テーブルの内容を任意の数だけ保存する。

Last updated at Posted at 2021-09-22

この記事/メモについて

本記事では"cocoon"というgemを用いて、モデルに紐づいた子モデルの内容を同時に保存できるようにします。

具体的には以下のようにして、
menuの投稿時にhas_manyで紐づいたfoodモデルを、
またfoodモデルにhas_manyで紐づいたmaterialモデルを任意の数だけ保存できるようにします。

ezgif.com-gif-maker.gif

ここでは、表のような情報を開発者が入れる初期データとして保存していこうと思います。

menuモデル

ID name   plan
1 献立①   朝ごはん用 
2 献立②   昼ごはん用 

foodモデル

ID name   menu_id
1 味噌汁   1 
2 納豆ご飯   1 
3 オムライス   2 

materialモデル

ID name   food_id
1 味噌   1 
2 ねぎ   1 
3 納豆   2 
4 ご飯  2 
5 卵  3 
6 ケチャップ  3 

前提

rubyのバージョン

$ ruby -v
=> ruby 2.7.2p137

railsのバージョン

$ rails -v
=> Rails 6.1.3.2

手順

rails newは済んでいる前提です!

⓪gemのインストールなどの下準備

1、必要なgemのインストール

Gemfile
gem 'cocoon'
gem 'jquery-rails'
$ bundle install

2、app/javascript/packs/application.jsに以下を追記

app/javascript/packs/application.js
require('jquery')
import "cocoon";

//= require cocoon
//= require jquery

3、jqueryの導入

$ yarn add jquery 

4、config/webpack/environment.jsの編集

ファイル全体を以下のように変更。

config/webpack/environment.js
const { environment } = require('@rails/webpacker')

const webpack = require('webpack')
environment.plugins.prepend('Provide',
    new webpack.ProvidePlugin({
        $: 'jquery/src/jquery',
        jQuery: 'jquery/src/jquery'
    })
)

module.exports = environment

5、cocoonの導入

$ yarn add github:nathanvda/cocoon#c24ba53

package.jsonに以下が追記されていることを確認

package.json
"cocoon": "github:nathanvda/cocoon#c24ba53",

①menuの大枠を作成。

$ rails g scaffold menu name:string plan:string

②menuの子モデルとしてfoodモデルを作成

$ rails g model food name:string menu_id:integer

③foodの子モデルとしてmaterialモデルを作成

$ rails g model material name:string food_id:integer

④マイグレーション

$ rails db:migrate

全体のDBは以下の通り。

db/schema.rb
create_table "foods", force: :cascade do |t|
    t.string "name"
    t.integer "menu_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "materials", force: :cascade do |t|
    t.string "name"
    t.integer "food_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "menus", force: :cascade do |t|
    t.string "name"
    t.string "plan"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

④アソシエーションの整理

models/menu.rb
 has_many :foods, inverse_of: :menu
 accepts_nested_attributes_for :foods, allow_destroy: true, update_only: true
models/food.rb
 belongs_to :menu,inverse_of: :foods
 has_many :materials, inverse_of: :food
 accepts_nested_attributes_for :materials, allow_destroy: true
models/material.rb
 belongs_to :food,inverse_of: :materials

⑤ViewやController周りの整理

1、投稿フォームの作成
*今回はscaffoldを用いているので、記述場所を_form.html.erbにしています。
ご自身のフォルダに合わせてください!

(特にscaffoldを用いているのでform_withのmenuが@menuではなくなっています。)

またedit.html.erbとnew.hrml.erbの内容は一緒で大丈夫です!

app/views/menus/_form.html.erb
<%= form_with(model: menu, local: true)  do |f| %>

  <div class="menu_field">
    メニューのname<%= f.text_field :name %>
    メニューのplan名<%= f.text_field :plan %>
  </div>

## 以下の部分によってmenuの子モデルであるfoodのフォームを非同期で追加することが可能です。
## renderによってこれから作成する別ファイルの内容を引っ張ってきています。

  <div id="foods">
      <%= f.fields_for :foods do |food| %>
          <%= render 'food_fields',f: food %>
      <% end %>

     <div class="links">
          <%= link_to_add_association "Add foods",f,:foods %>
     </div>
  </div>

##  

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

app/views/menusの中に_food_fields.html.erbを作成します。(上記のrenderで用いるため)

app/views/menus/_food_fields.html.erb
 <div class="nested-fields">
    フードのname<%= f.text_field :name %>

 ## 以下の部分によってfoodの子モデルであるmaterialのフォームを非同期で追加することが可能です。
        <%= f.fields_for :materials do |material| %>
            <%= material.text_field :name %>
            <%= render "material_fields", f: material %>
        <% end %>
        <div class="links">
            <%= link_to_add_association "Add materials",f,:materials %>
        </div>

## 以下のコードは追加したfoodのフォームを削除するためのもの。
 <%= link_to_remove_association "Delete", f %>
 </div>

孫モデルを作りたい時はapp/views/menusの中に_material_fields.html.erbを作成します。(上記のrenderで用いるため)

app/views/menus/_material_fields.html.erb
<div class="nested-fields">
   <%= f.hidden_field :_destroy %>
   マテリアルのname<%= f.text_field :name %>

## 以下のコードは追加したmaterialのフォームを削除するためのもの。
   <%= link_to_remove_association "Delete", f %>
</div> 

2、コントローラーの修正

private以下のストロングパラメーターの記述について変更します。

app/controllers/menus_controller.rb

 private
    # menu_paramsのストロングパラメータの記述を変更。

    def menu_params
      params.require(:menu).permit(:name, :plan,
        foods_attributes: [:id, :name, :_destroy,
        materials_attributes: [:id, :name, :_destroy]])
    end

参考

Rails6でのcocoon
https://qiita.com/ashketcham/items/87e3db665ca9c66ce673

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3