こんなものを作る記事です💪
やりたいこと
- 2つのモデルが紐付いたフォームをつくりたい
- ネストしたフォームをつくりたい
- 複数のデータを一度に追加したい
動画だとこんな感じです👇
「とりあえずソースコード見せて!」という方はこちらへ。
GitHub - kotakanazawa/cocoon-test: set up cocoon gem on Rails 6
- 環境
- テストアプリを作る
- Rails 6 に jQuery を入れる
- Cocoon を導入する
- モデルの修正
- ストロングパラメーターの設定
- フォーム作成
- 完成画面
- ネストした場合のバリデーションエラーメッセージを修正する
- リポジトリ
- 参考
環境
$ sw_vers ProductName: Mac OS X ProductVersion: 10.14.6
cocoon gem を使う
cocoon という、ネストしたフォームを簡単に実現する gem を使う。
テストアプリを作る
まずは cocoon-test というアプリをrails new
する。
$ rails new cocoon-test $ cd cocoon-test
念の為、rails s
をして「Yay! You’re on Rails!」が表示されるか確認しておく。
slim を入れる
テンプレートエンジンは slim を使う。
Gemfile.rb
# Gemfileの一番下に書く gem "slim-rails" gem "html2slim"
rails new
のときにデフォルトで生成された erb ファイルを slim に変換する。
$ bundle exec erb2slim app/views/ --delete
これで、以降生成される view ファイルは slim になる。
Rails で erb を slim に変換する方法 - Just do IT
モデルやコントローラーを作成
次に、以下のscaffold
コマンドを実行する。
$ bin/rails g scaffold Project name:string
マイグレーションファイルにnull: false
を追加する。(テストアプリにこれをする必要はないかもしれないが、念の為)
class CreateProjects < ActiveRecord::Migration[6.0] def change create_table :projects do |t| t.string :name, null: false #追加 t.timestamps end end end
マイグレーションを実行。
$ bin/rails db:migrate
次に、Project モデルに紐づく Task モデルもscaffold
コマンドで作成する。
$ bin/rails g scaffold Task name:string project:belongs_to
こちらもnull: false
を追加。
class CreateTasks < ActiveRecord::Migration[6.0] def change create_table :tasks do |t| t.string :name, null: false #追加 t.belongs_to :project t.timestamps end end end
マイグレーションを実行。
$ bin/rails db:migrate
やっていること
- Project と Task を
scaffold
で作成する - Project と Task は一対多の関係にする
project:belongs_to
で Project と Task のアソシエーションをつくる
テストアプリなので、どちらもname:string
だけとシンプルな構成にしている。
routes.rb を修正
root "projects#index"
をしてホーム画面をプロジェクトの一覧画面にしておく。
Rails.application.routes.draw do root "projects#index" resources :tasks resources :projects end
現時点で、こんな状態になっていると思う。
Rails 6 に jQuery を入れる
今回は Webpacker を使って導入するため、jQuery をyarn add
する。
$ yarn add jquery
{ "name": "cocoon_test", "private": true, "dependencies": { "@rails/actioncable": "^6.0.0", "@rails/activestorage": "^6.0.0", "@rails/ujs": "^6.0.0", "@rails/webpacker": "4.2.2", "jquery": "^3.5.1", #追記される "turbolinks": "^5.2.0" }, "version": "0.1.0", "devDependencies": { "webpack-dev-server": "^3.11.0" } }
今までは jquery-rails といった gem を使っていたが、JavaScript は Webpacker で管理できるようになった。
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
本家の webpack だと webpack.config.js に設定を書くが、webpacker では webpacker/environment.js に設定を書く。
application.js
app/javascript/packs/application.js で jQuery をrequire
する。
require("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels") # 追記 require("jquery")
これで jQuery の導入は完了。
Cocoon を導入する
jQuery を入れたら、次に Cocoon gem をインストールする。
Gemfile に以下を追記。
gem "cocoon"
次に、以下コマンドを実行。
$ yarn add github:nathanvda/cocoon#c24ba53
本来ならば普通にyarn add cocoon
などとしたいところだが、cocoon は開発が止まってるようで、Rails 6 に今のところ対応していない💦(2020年7月4日)
よって、
よって、上記のやり方でyarn add
する。
package.json を確認すると cocoon が追記されている。
{ "name": "cocoon_test", "private": true, "dependencies": { "@rails/actioncable": "^6.0.0", "@rails/activestorage": "^6.0.0", "@rails/ujs": "^6.0.0", "@rails/webpacker": "4.2.2", "cocoon": "github:nathanvda/cocoon#c24ba53", #追記される "jquery": "^3.5.1", "turbolinks": "^5.2.0" }, "version": "0.1.0", "devDependencies": { "webpack-dev-server": "^3.11.0" } }
次に、app/javascript/packs/application.js で require をしておく。
require("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels") require("jquery") # 追記 require("cocoon")
これで Cocoon を使う準備は完了💪
モデルの修正
モデルに設定を記述していく。
Project モデル
Project モデルに設定を記入する。
class Project < ApplicationRecord has_many :tasks accepts_nested_attributes_for :tasks, reject_if: :all_blank, allow_destroy: true validates :name, presence: true end
has_many :tasks
で、Project と Task モデルを一体多の関係に設定。
accepts_nested_attributes_for :tasks
で、ネストの子モデル側を指定する。これで Project に紐付いた Task を同時に保存できる。
reject_if: :all_blank
とallow_destroy: true
は、accepts_nested_attributes_for
でサポートされてるオプション。
reject_if: :all_blank
:入力がすべて空の場合は登録できないallow_destroy: true
:削除できるようにする
ActiveRecord::NestedAttributes::ClassMethods
Task モデル
Task モデルに以下を追記。
class Task < ApplicationRecord belongs_to :project validates :name, presence: true end
ストロングパラメーターの設定
app/controllers/projects_controller.rb に、tasks_attributes: [:id, :name, :_destroy]
を追記する。
def project_params params.require(:project).permit(:name, tasks_attributes: [:id, :name, :_destroy]) end
これで、Project に関するフォームでも tasks テーブルの属性が取得できるようになる。ここでは id, name, _destroy を記述している。:_destroy
を記入することで属性の削除ができるようになる。
もし、Project に紐づくテーブルが members だった場合、members_attributes
となる。
フォーム作成
いよいよフォームをつくっていく。
app/views/projects/_form.html.slim
= form_for @project do |f| - if @project.errors.any? #error_explanation h2 = "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:" ul - @project.errors.full_messages.each do |message| li = message .field = f.label :name = f.text_field :name #追記 h3 Tasks #tasks.field = f.fields_for :tasks do |task| = render 'task_fields', f: task .links = link_to_add_association 'add task', f, :tasks #ここまで .actions = f.submit
次に、_task_fields.html.slim というファイルを projects ディレクトリ直下に作成する。cocoon を使う上で、子モデルに登録するフォームはモデル_fields
といったように名前を付けるルールになっている。
$ touch app/views/projects/_task_fields.html.slim
app/views/projects/_task_fields.html.slim
#追記 .nested-fields .field = f.label :name = f.text_field :name = link_to_remove_association "remove task", f
これでプロジェクトに紐づくタスクが登録ができるようになった。
詳細ページにタスクを表示する
このままではタスクが表示されないため、プロジェクトの詳細ページを修正する。
app/views/projects/show.html.slim
p#notice = notice p strong Name: = @project.name #追記 h2 Tasks ul - @project.tasks.each do |task| li = task.name #ここまで => link_to 'Edit', edit_project_path(@project) '| =< link_to 'Back', projects_path
完成画面
詳細画面からタスクの更新もできるようになっていることを確認してみてください。
ネストした場合のバリデーションエラーメッセージを修正する
実際の開発になるとバリデーションのエラーメッセージを設定することがある。その場合、ロケールファイルには以下のように書くと期待通りに表示される。
ja:
activerecord:
attributes:
project/tasks:
name: 名前
※i18n
などで日本語化がされている前提。
リポジトリ
ソースコードはこちらです。
GitHub - kotakanazawa/cocoon-test: set up cocoon gem on Rails 6
cocoon は使うべきか?
フロントとバックエンドを提供していて、だいぶ密結合なgem。フロントとバックエンドを切り離すのが当たり前になっている現代で、cocoon を使うのがベストプラクティスなのかは議論が必要。
とはいえ、個人開発目的で、技術力不足を補うために使うならアリだと思う。
参考
- How to use cocoon gem in Rails 6 - Stack Overflow
- ネストしたフォームを簡潔に実装できるcocoon gemをwebpack環境でセットアップする - Qiita
- Introducing jQuery in Rails 6 Using Webpacker - BoTree Technologies
- ActiveRecord::NestedAttributes::ClassMethods
- nathanvda/cocoon: Dynamic nested forms using jQuery made easy; works with formtastic, simple_form or default forms