概要
先日、こちらの記事を参考にさせていただき、投稿に紐づいた複数タグ機能を実装しました。
そこで今回、投稿編集機能を実装していたとき、タグも編集したいと思い、タグ編集機能を実装しましたので共有させていただきます。
私の実装では、Post(投稿)に対し、Genre(タグ機能)を実装しています。
以下、タグのことをジャンル、もしくはGenreと述べさせていただきます。
開発環境
- OS:Amazon Linux2
- Rails 6.1.6.1
実装
アソシエーション
私が行った実装では、タグのことをGenreとして実装していてわかりずらいかと思いますので、アソシエーションを記述させていただきます。
Postモデルです。
class Post < ApplicationRecord
has_many :post_genres, dependent: :destroy
has_many :genres, through: :post_genres
end
Genreモデルです。
class Genre < ApplicationRecord
has_many :post_genres, dependent: :destroy, foreign_key: 'genre_id'
has_many :posts, through: :post_genres
end
中間テーブルのPostGenreモデルです。
class PostGenre < ApplicationRecord
belongs_to :post
belongs_to :genre
end
postsコントローラ
Genreを編集するのは、投稿を編集する画面でまとめて行いたいため、postsコントローラに処理を記述します。
:
#省略
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アクションでは、
@genre_list = @post.genres.map { |genre| genre.name }
という記述で、投稿編集画面に投稿に紐づいたgenreを表示します。mapメソッドを用いて、postに紐づいたgenreを配列に格納します。
投稿編集ページ
続いて投稿編集ページに、先ほどeditアクションの@genre_list = @post.genres.map { |genre| genre.name }
で取得したgenreを表示するための記述を行います。
:
<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モデルに作成していきます。
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も同一のコントローラで編集することに成功しました。