2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-10-12

概要

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

そこで今回、投稿編集機能を実装していたとき、タグも編集したいと思い、タグ編集機能を実装しましたので共有させていただきます。
私の実装では、Post(投稿)に対し、Genre(タグ機能)を実装しています。
以下、タグのことをジャンル、もしくはGenreと述べさせていただきます。
スクリーンショット 2022-10-12 13 40 17

開発環境

  • OS:Amazon Linux2
  • Rails 6.1.6.1

実装

アソシエーション

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

Postモデルです。

app/models/post.rb
class Post < ApplicationRecord
  has_many :post_genres, dependent: :destroy
  has_many :genres, through: :post_genres
end

Genreモデルです。

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モデルです。

app/models/PostGenre.rb
class PostGenre < ApplicationRecord
  belongs_to :post
  belongs_to :genre
end

postsコントローラ

Genreを編集するのは、投稿を編集する画面でまとめて行いたいため、postsコントローラに処理を記述します。

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アクションでは、

@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モデルに作成していきます。

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も同一のコントローラで編集することに成功しました。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?