#はじめに
Railsアプリのタグ機能実装においてgemを使用するか迷いましたが、
なるべくブラックボックスは少ない方がよいと思い、gemなしで実装しました。
最低限の機能であれば意外と少ない工程で実装ができたので、今回記事にします。
ユーザーが投稿する「本」(Bookモデル)について、「ジャンルタグ」(Genreモデル)を設け、
タグをクリックするだけでそのタグに該当する本を一覧で表示させるところまでです。
誤った処理や冗長な処理がありましたら是非コメントお願いします!
#モデルの実装
「本」は複数の「ジャンルタグ」を持ち、逆に「ジャンルタグ」も複数の「本」を持つ
【多対多】の関係であるため、中間テーブル(BookGenreモデル)を設定します。
各モデルに下記のとおり記載し各テーブルの関連付けをします。
has_many :book_genres, dependent: :destroy
has_many :genres, through: :book_genres
has_many :book_genres, dependent: :destroy
has_many :books, through: :book_genres
validates :name, presence: true
↑nameカラムにジャンルの名前(「小説」等)が入ります。
class BookGenre < ApplicationRecord
belongs_to :book
belongs_to :genre
validates :book_id, presence: true
validates :genre_id, presence: true
end
「本」と「ジャンルタグ」は中間テーブルを介して関連付けを行っております。
#ビューの実装
本投稿時にジャンルをチェックする形とします。
= form_with model: @book, local: true do |f|
= f.collection_check_boxes :genre_ids, Genre.All, :id, :name,
include_hidden: false do |b|
.check-box
= b.check_box
= b.label { b.text }
= f.submit '投稿する'
チェックされたGenreオブジェクトのidが配列となって[:book][:genre_ids]
に格納されます。
include_hidden: false
のオプションを指定することにより、
中身が空の値""
が配列に含まれないようにします。
(これが含まれると、後の工程でエラーとなります。)
#コントローラーの実装
ビューより送られた[:book][:genre_ids]
を処理します。
def create
@book = current_user.books.build(book_params)
if !params[:book][:genre_ids].nil? && @book.save
@book.save_genres(params[:book][:genre_ids])
flash[:success] = "本を投稿しました"
redirect_to book_path(@book.id)
else
flash.now[:danger] = "投稿に失敗しました"
render 'new'
end
end
ジャンル選択を必須にしたい為、params[:book][:genre_ids]
がnil
であった場合は
投稿ができないようにしております。(もう少しスマートなやり方があると思います。。)
save_genres
メソッドについては、モデルに処理を記載しています。
def save_genres(genre_ids)
genre_ids.each do |genre_id|
book_genre = Genre.find_by(id: genre_id)
self.genres << book_genre
end
end
引数として受け取ったidの配列に対してeach
メソッドをかけて
それぞれのidに該当するGenreオブジェクトを探し出し、
self.genres
に格納(登録)していきます。
この時、genre_ids(=params[:book][:genre_ids])
の配列の中に空の値""
が含まれると、
Genre.find_by(id: "") => nil
となり、エラーとなります。
上記実装により、本投稿時にチェックしたジャンルタグと紐づけて本を保存することができます。
#タグをクリックすると該当する本一覧を表示する機能
次は、本詳細ページに表示されたタグをクリックすると、
そのタグに該当する本の一覧を表示する機能を実装します。
- @book.genres.each do |genre|
= link_to genre.name, books_path(genre_id: genre.id)
本詳細ページにジャンル名を表示させ、本一覧画面へのリンクとします。
その際、params[:genre_id]
にそのジャンルのidを格納して渡します。
次にコントローラーを編集し、
index
アクションにおいてparams[:genre_id]
がある場合は
ビューに渡す@books
の対象を絞り込むようにします。
def index
if params[:genre_id]
@genre = Genre.find(params[:genre_id])
@books = @genre.books
else
@books = Book.all
end
end
あとは、ビュー上で下記のように題名が変わるようにすれば、完成です。
- if @genre.present?
h2 ジャンル「#{@genre.name}」の本一覧
- else
h2 本一覧
#注意
本記事は投稿時にジャンルを設定するのみの最低限の実装となっておりますが、
ジャンルの更新処理を行いたい場合は上記のsave_genres(genre_ids)
メソッドを
工夫する必要があります。
→参考:Railsでタグ機能をgemを使わずに実装した際のメモ