## はじめに
フォーム関連の**collection_check_boxes
**を使用する際に
わからないことがたくさんあったので記録のためにこちらに記述して残しておきます。
多対多の関係を構築するモデルの作成から潜影蛇手していきます。
## 環境
ruby 2.5.7
Rails 5.2.4.2
やりたいこと
shopに対して複数のgenre(和食・洋食など)を保存・更新できる様にする。
##多対多の関係(モデル)
今回はジャンル検索でお店を検索するという機能を作りたいため、
shopに様々なgenreを持たせる。
お店の情報をもつShop
モデルとジャンルの情報を持つGenre
モデル、その2つを繋ぐ中間テーブルとしてShopGenre
モデルを作成します。
##モデル作成
$rails g model Shop shop_name:string
$rails g model Genre name:string
# 中間テーブル
$rails g model ShopGenre shop:references genre:references
:references
これは外部キーを追加する際に使用します。
上図のように作成を行えば自動的に以下のようにそれぞれのモデルとの紐付けされた状態で作成されます。
class ShopGenre < ApplicationRecord
belongs_to :shop
belongs_to :genre
end
class CreateShopGenres < ActiveRecord::Migration[5.2]
def change
create_table :shop_genres do |t|
t.references :shop, foreign_key: true
t.references :genre, foreign_key: true
t.timestamps
end
end
end
shop_genreモデルには自動的にbelongs_toと設定されますが、
shopモデルとgenreモデルには自分で記述する必要があります。
class Shop < ApplicationRecord
has_many :shop_genres
has_many :genres, through: :genres
end
class Genre < ApplicationRecord
has_many :shop_genres
has_many :shops, through: :genres
end
Shop 1:n ShopGenre n:1 Genreの状態
今回、多対多の関係でshop_genre(中間テーブル)
を通して
shopテーブル
及び genreテーブル
を(through)、その先を参照することをしたいため
中間テーブルとのhas_many関係の記述と、
もう一つその中間テーブルを通したモデルとの紐付け、
今回だとshopとgenreモデルにhas_many :through
を使って紐付ける必要があります。
以上でモデルの実装は完了。
##collection_check_boxesの記述(view)
今回は既にshopは作られており、後から編集でジャンルを追加できるようにしていますので
以下のようになっております。予めご理解ください。
<%= form_for(@shop, url:admins_shop_path(@shop),method: :patch) do |f| %>
# 〜〜省略〜〜
<div class="form-group">
<%= f.label :genres %>
<div class="checkbox">
<%= collection_check_boxes(:shop, :genre_ids, Genre.all, :id, :name, include_hidden: false) do |g| %>
<%= g.label {g.check_box + g.text} %>
<% end %>
</div>
</div>
<% end %>
:genre_ids
は更新対象のオブジェクト、つまり@shop
が持つメソッド。チェックボックスの各チェックにIDが関連付けられます。create,updateアクションを実行する時には、ストロングパラメータの設定が必要です(※1後述)
collection_check_boxesの構造に関しての詳細は
こちらのQiita記事を見ていただければ分かりやすいかと思います。
今回の実装ではcheckboxのデータを配列で受けとるが、以下のように
[""]
が入ってしまうため
オプションとしてinclude_hidden: false
を設けました。
[1] pry(#<Admins::ShopsController>)> shop_params[:genre_ids]
=> ["", "1", "2", "3"]
⬆️このようになってしまう。
##コントローラー実装
中間テーブルにもデータを保存できるようにコードを書いていきます。
class Admins::ShopsController < ApplicationController
# 〜〜省略〜〜
def edit
@shop = Shop.find(params[:id])
end
def update
@shop = Shop.find(params[:id])
if @shop.update(shop_params)
shop_params[:genre_ids].each do | shopg |
genres = @shop.genres.pluck(:genre_id)
unless genres.include?(shopg.to_i)
genre = ShopGenre.new(genre_id: shopg)
genre.shop_id = @shop.id
genre.save
end
end
redirect_to admins_shop_path
else
render 'edit'
end
end
# 〜〜省略〜〜
private
def shop_params
params.require(:shop).permit(:name, genre_ids: [])
end
end
※1)ストロングパラメータにgenre_ids: []
を記述。(配列としてデータを抽出)
genres.include?(shopg.to_i)
と書いていますが、
配列で抽出したidは上記コンソールで見たように["", "1", "2", "3"]
と文字列として
扱われるため.to_i
を潜影蛇手してintegerに直す必要があります。
あと、shopモデルに以下の記述を追記
attr_accessor :genre_ids
attr_accessor :
(インスタンス変数) とすれば、指定されたインスタンス変数が外部からでも変更できるようになるので必要に応じて設定してください。
ただ、上記controllerの記述だけでは同じid(ジャンル)は保存されないようになっていますが
不十分なところが複数あります。
・ 既に保存済みのジャンルをチェックボックス(view/edit.html.erb)に反映させる
・ チェックを外した状態で更新すると消されるようにするなど、、
その他動作させる上でジャンルを全て削除した状態など色々考慮しコードを追記。
※もう少し工夫できるところはあると思いますが以下が最終結果です。
class Admins::ShopsController < ApplicationController
def edit
@shop = Shop.find(params[:id])
@shop.genre_ids = @shop.genres.pluck(:shop_genre_id)
end
def update
@shop = Shop.find(params[:id])
old_genres = @shop.genres.pluck(:genre_id)
if @shop.update(shop_params)
if shop_params[:genre_ids].nil?
genres = ShopGenre.where(shop_id: @shop.id)
genres.destroy_all
else
destroy_genres = old_genres - shop_params[:genre_ids].map(&:to_i)
destroy_genres.each do | destroy_genre |
genre = ShopGenre.find_by(shop_id: @shop.id, genre_id: destroy_genre)
genre.destroy
end
genres = @shop.genres.pluck(:genre_id)
shop_params[:genre_ids].each do | shopg |
unless genres.include?(shopg.to_i)
genre = ShopGenre.new(genre_id: shopg)
genre.shop_id = @shop.id
genre.save
end
end
end
redirect_to admins_shop_path
else
render 'edit'
end
end
private
def shop_params
params.require(:shop).permit(:name, genre_ids: [])
end
end
以上で【collection_check_boxes】を使用し複数のカラムを
表示・保存・更新・削除できる様になりました。