多対多の関係で【collection_check_boxes】を使用し複数のカラムを表示・保存・更新・削除できる様にする。

##　はじめに

フォーム関連の**`collection_check_boxes`**を使用する際に
わからないことがたくさんあったので記録のためにこちらに記述して残しておきます。
多対多の関係を構築するモデルの作成から潜影蛇手していきます。
##　環境
ruby 2.5.7
Rails 5.2.4.2
## やりたいこと
shopに対して複数のgenre（和食・洋食など）を保存・更新できる様にする。
<img width="150" alt="スクリーンショット 2020-05-11 14.58.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635558/39bd29a2-1be8-d76c-cd7e-a162644cc8cc.png">
##多対多の関係（モデル）
今回はジャンル検索でお店を検索するという機能を作りたいため、
shopに様々なgenreを持たせる。
お店の情報をもつ`Shop`モデルとジャンルの情報を持つ`Genre`モデル、その2つを繋ぐ中間テーブルとして`ShopGenre`モデルを作成します。
<img width="689" alt="スクリーンショット 2020-05-11 15.13.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635558/f5098628-7751-f03a-fc62-a94b67630501.png">
##モデル作成
```ruby:model
$rails g model Shop shop_name:string
$rails g model Genre name:string

# 中間テーブル
$rails g model ShopGenre shop:references genre:references
```
`:references`これは外部キーを追加する際に使用します。
上図のように作成を行えば自動的に以下のようにそれぞれのモデルとの紐付けされた状態で作成されます。

```ruby:models/shop_genre.rb
class ShopGenre < ApplicationRecord
  belongs_to :shop
  belongs_to :genre
end
```

```ruby:db/migrate/create_shop_genres.rb
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モデルには自分で記述する必要があります。

```ruby:models/shop.rb
class Shop < ApplicationRecord
    has_many :shop_genres
    has_many :genres, through: :genres
end

```
```ruby:models/genre.rb
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は作られており、後から編集でジャンルを追加できるようにしていますので
以下のようになっております。予めご理解ください。


```views/shops/edit.html.erb
<%= 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記事](https://qiita.com/sho012b/items/3a595fde14516081dff5)を見ていただければ分かりやすいかと思います。

今回の実装ではcheckboxのデータを配列で受けとるが、以下のように
`[""]`が入ってしまうため
オプションとして`include_hidden: false`を設けました。

<img width="149" alt="スクリーンショット 2020-05-11 17.02.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635558/e6bcb1ce-d7be-c23b-4dcf-b7319050cbf8.png">

```ruby:console
[１] pry(#<Admins::ShopsController>)> shop_params[:genre_ids]
=> ["", "1", "2", "3"]
```
⬆️このようになってしまう。

##コントローラー実装
中間テーブルにもデータを保存できるようにコードを書いていきます。

```controllers/shops/shops_controller.rb
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モデルに以下の記述を追記

```ruby:models/shop.rb
attr_accessor :genre_ids
```
`attr_accessor :`　（インスタンス変数)　とすれば、指定されたインスタンス変数が外部からでも変更できるようになるので必要に応じて設定してください。

ただ、上記controllerの記述だけでは同じid（ジャンル）は保存されないようになっていますが
不十分なところが複数あります。

`・ 既に保存済みのジャンルをチェックボックス(view/edit.html.erb)に反映させる`
`・ チェックを外した状態で更新すると消されるようにするなど、、`

その他動作させる上でジャンルを全て削除した状態など色々考慮しコードを追記。
※もう少し工夫できるところはあると思いますが以下が最終結果です。

```controllers/shops/shops_controller.rb
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】を使用し複数のカラムを
表示・保存・更新・削除できる様になりました。