この記事/メモについて
本記事では"cocoon"というgemを用いて、モデルに紐づいた子モデルの内容を同時に保存できるようにします。
具体的には以下のようにして、
menuの投稿時にhas_manyで紐づいたfoodモデルを、
またfoodモデルにhas_manyで紐づいたmaterialモデルを任意の数だけ保存できるようにします。
ここでは、表のような情報を開発者が入れる初期データとして保存していこうと思います。
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のインストール
gem 'cocoon'
gem 'jquery-rails'
$ bundle install
2、app/javascript/packs/application.jsに以下を追記
require('jquery')
import "cocoon";
//= require cocoon
//= require jquery
3、jqueryの導入
$ yarn add jquery
4、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に以下が追記されていることを確認
"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は以下の通り。
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
④アソシエーションの整理
has_many :foods, inverse_of: :menu
accepts_nested_attributes_for :foods, allow_destroy: true, update_only: true
belongs_to :menu,inverse_of: :foods
has_many :materials, inverse_of: :food
accepts_nested_attributes_for :materials, allow_destroy: true
belongs_to :food,inverse_of: :materials
⑤ViewやController周りの整理
1、投稿フォームの作成
*今回はscaffoldを用いているので、記述場所を_form.html.erb
にしています。
ご自身のフォルダに合わせてください!
(特にscaffoldを用いているのでform_withのmenuが@menuではなくなっています。)
またedit.html.erbとnew.hrml.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で用いるため)
<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で用いるため)
<div class="nested-fields">
<%= f.hidden_field :_destroy %>
マテリアルのname<%= f.text_field :name %>
## 以下のコードは追加したmaterialのフォームを削除するためのもの。
<%= link_to_remove_association "Delete", f %>
</div>
2、コントローラーの修正
private以下のストロングパラメーターの記述について変更します。
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