LoginSignup
5
4

More than 3 years have passed since last update.

【Rails】タグ機能を実装する方法(gem未使用)

Posted at

はじめに

Railsアプリのタグ機能実装においてgemを使用するか迷いましたが、
なるべくブラックボックスは少ない方がよいと思い、gemなしで実装しました。
最低限の機能であれば意外と少ない工程で実装ができたので、今回記事にします。

ユーザーが投稿する「本」(Bookモデル)について、「ジャンルタグ」(Genreモデル)を設け、
タグをクリックするだけでそのタグに該当する本を一覧で表示させるところまでです。

誤った処理や冗長な処理がありましたら是非コメントお願いします!

モデルの実装

「本」は複数の「ジャンルタグ」を持ち、逆に「ジャンルタグ」も複数の「本」を持つ
【多対多】の関係であるため、中間テーブル(BookGenreモデル)を設定します。

各モデルに下記のとおり記載し各テーブルの関連付けをします。

book.rb(抜粋)
    has_many :book_genres, dependent: :destroy
    has_many :genres, through: :book_genres
genre.rb(抜粋)
    has_many :book_genres, dependent: :destroy
    has_many :books, through: :book_genres
    validates :name, presence: true

↑nameカラムにジャンルの名前(「小説」等)が入ります。

book_genre.rb
class BookGenre < ApplicationRecord
    belongs_to :book
    belongs_to :genre
    validates :book_id, presence: true
    validates :genre_id, presence: true
end

「本」と「ジャンルタグ」は中間テーブルを介して関連付けを行っております。

ビューの実装

本投稿時にジャンルをチェックする形とします。

new.html.slim(抜粋)
= 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]を処理します。

books_contrller.rb(抜粋)
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メソッドについては、モデルに処理を記載しています。

book.rb(抜粋)
  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となり、エラーとなります。

上記実装により、本投稿時にチェックしたジャンルタグと紐づけて本を保存することができます。

タグをクリックすると該当する本一覧を表示する機能

次は、本詳細ページに表示されたタグをクリックすると、
そのタグに該当する本の一覧を表示する機能を実装します。

show.html.slim(抜粋)
- @book.genres.each do |genre|
  = link_to genre.name, books_path(genre_id: genre.id)

本詳細ページにジャンル名を表示させ、本一覧画面へのリンクとします。
その際、params[:genre_id]にそのジャンルのidを格納して渡します。

次にコントローラーを編集し、
indexアクションにおいてparams[:genre_id]がある場合は
ビューに渡す@booksの対象を絞り込むようにします。

books_contrller.rb(抜粋)
def index
    if params[:genre_id]
      @genre = Genre.find(params[:genre_id])
      @books = @genre.books
    else
      @books = Book.all
    end
end

あとは、ビュー上で下記のように題名が変わるようにすれば、完成です。

index.html.slim(抜粋)
- if @genre.present?
  h2 ジャンル「#{@genre.name}」の本一覧
- else
  h2  本一覧

注意

本記事は投稿時にジャンルを設定するのみの最低限の実装となっておりますが、
ジャンルの更新処理を行いたい場合は上記のsave_genres(genre_ids)メソッドを
工夫する必要があります。
→参考:Railsでタグ機能をgemを使わずに実装した際のメモ

参考

Railsでタグ機能をgemを使わずに実装した際のメモ

5
4
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
5
4