複数タグの編集機能を実装[Rails]

# 概要
先日、こちらの記事を参考にさせていただき、投稿に紐づいた複数タグ機能を実装しました。

https://qiita.com/ryutaro9595/items/042a1ec713c8c1f2c1d6

そこで今回、投稿編集機能を実装していたとき、タグも編集したいと思い、タグ編集機能を実装しましたので共有させていただきます。
私の実装では、Post(投稿)に対し、Genre(タグ機能)を実装しています。
以下、タグのことをジャンル、もしくはGenreと述べさせていただきます。
<img width="531" alt="スクリーンショット 2022-10-12 13 40 17" src="https://user-images.githubusercontent.com/106591130/195253163-51cb3963-1a40-4d23-83c6-3272ff581dc9.png">

# 開発環境
- OS：Amazon Linux2
- Rails 6.1.6.1

# 実装
## アソシエーション
私が行った実装では、タグのことをGenreとして実装していてわかりずらいかと思いますので、アソシエーションを記述させていただきます。

**Postモデル**です。
```ruby:app/models/post.rb
class Post < ApplicationRecord
  has_many :post_genres, dependent: :destroy
  has_many :genres, through: :post_genres
end
```

**Genreモデル**です。
```ruby:app/models/Genre.rb
class Genre < ApplicationRecord
  has_many :post_genres, dependent: :destroy, foreign_key: 'genre_id'
  has_many :posts, through: :post_genres
end
```

中間テーブルの**PostGenreモデル**です。
```ruby:app/models/PostGenre.rb
class PostGenre < ApplicationRecord
  belongs_to :post
  belongs_to :genre
end
```

## postsコントローラ
Genreを編集するのは、投稿を編集する画面でまとめて行いたいため、postsコントローラに処理を記述します。
```ruby:app/controllers/posts_controller.rb
 :
 #省略
 def edit
   @post = Post.find(params[:id])
   #投稿編集画面で、postに紐づいたgenreを全て表示するための記述
   @genre_list = @post.genres.map { |genre| genre.name }
 end

 def update
   @post = Post.find(params[:id])
   #投稿編集画面で入力されたgenreを、[:genre_name]として受け取る。
   @genre_list = params[:post][:genre_name].split(/[[:blank:]]+/).select(&:present?)
   #もし投稿の更新に成功したら
   if @post.update(post_params)
     #update_genresメソッド(後述)でgenreを編集する。先ほど送られてきた@genre_listをupdate_genresメソッドに引数として渡します。
     @post.update_genres(@genre_list)
     redirect_to post_path(@post.id), notice: "投稿の編集に成功しました"
   else
     render "edit"
   end
 end
 :
 #省略

 private

 def post_params
   params.require(:post).permit(:post_image, :title, :body, :status)
 end
 :
 #省略
```
editアクションでは、
```ruby:
@genre_list = @post.genres.map { |genre| genre.name }
```
という記述で、投稿編集画面に投稿に紐づいたgenreを表示します。mapメソッドを用いて、postに紐づいたgenreを配列に格納します。

## 投稿編集ページ
続いて投稿編集ページに、先ほどeditアクションの`@genre_list = @post.genres.map { |genre| genre.name }`で取得したgenreを表示するための記述を行います。

```:app/views/posts/edit.html.erb
:
 <div class="form-group">
   <%= f.label :genre_name, "ジャンル(半角スペースで複数ジャンル付けできます)" %> <i class="fa-solid fa-pen-clip"></i>
   <%= f.text_field :genre_name, placeholder:"ジャンルをここに", class: 'form-control', value: @genre_list&.join(' ') %>
 </div>
:
```
こちらでお伝えしたいのは、`value: @genre_list&.join(' ')`という記述をf.text_fieldの行に記述しているという点です。
`@genre_list`は、先ほどeditアクションに記述した、postに紐づいているgenreです。
`&.`演算子を使用しているのは、`@genre_list`がnilである可能性があるためです。`@genre_list`がnilでなければその結果を、nilの場合はnilを返します。
最後に`.join(' ')`を使用して、配列である`@genre_list`の値を半角スペースで結合して表示します。

## update_genresメソッドの作成
最後に、genreを編集するためのupdate_genresメソッドを作成します。Postモデルに作成していきます。
```ruby:app/models/post.rb
class Post < ApplicationRecord
#省略
:
  def update_genres(genre_list)
    post_genres.map(&:destroy)
    return unless genre_list
    genre_list.each do |genre|
      genre = Genre.find_or_create_by(name: genre)
      PostGenre.create!(genre: genre, post: self)
    end
  end
:
#省略
end
```
まず、`post_genres.map(&:destroy)`で投稿に関連したpost_genres(中間テーブル)を全て削除します。書き換えると次のような形です。`post_genres.map { |post_genre| post_genre.destroy }`
こちらの記述で、投稿に関連したpost_genresを一つづつ取り出し、削除していきます。
次に`return unless genre_list`として、引数にgenre_listの値が何もなければ、このメソッドから抜けて、処理を終了します。
`genre_list.each do |genre|`として、送られてきたgenre_listを一つずつ繰り返します。
`genre = Genre.find_or_create_by(name: genre)`では、find_or_create_byメソッドを用いて、genreの中身を調べ、なければ新たにgenreを作成し、もうすでに存在していれば何もしないという処理を行います。
最後に、`PostGenre.create!(genre: genre, post: self)`として、新しいgenreとpostの中間テーブルを作成します。

このメソッドを使用して、Postとは別のモデルであるGenreも同一のコントローラで編集することに成功しました。