[Rails]タグ 2/2
はじめに
ヘッダーの検索フォームからタグを検索できるように実装していきます。
また、タグをクリックすると、そのタグが付けられた投稿を絞り込んで表示させていきます。
環境:
Rails 6.1.7.3
ruby 3.0.0
検索用のコントローラーを作成する
Articles
コントローラーにてsearch
アクションを作成しましたが、検索用のSearch
コントローラーに移します。
class ArticlesController < ApplicationController
- def search
- @q = Article.ransack(params[:q])
- @articles = @q.result(distinct: true).includes(:user).page(params[:page]).per(20)
- end
end
class SearchController < ApplicationController
def search
@q = params[:q]
@articles = Article.ransack(title_or_body_cont: @q).result(distinct: true).includes(:user).page(params[:page]).per(20)
@tags = Tag.ransack(name_cont: @q).result(distinct: true)
end
end
routes.rb
を編集する
サーチのURLを変更します。
Rails.application.routes.draw do
+ get '/search', to: 'search#search', as: :search
resources :articles do
collection do
- get 'search', to: 'articles#search', as: :search
end
end
end
search GET /search(.:format) search#search
検索するカラムと関連付けのモデルを追加する
class Tag < ApplicationRecord
def self.ransackable_attributes(auth_object = nil)
["name"]
end
def self.ransackable_associations(auth_object = nil)
%w[article_tags articles]
end
end
class Article < ApplicationRecord
def self.ransackable_attributes(auth_object = nil)
%w[title body]
end
def self.ransackable_associations(auth_object = nil)
%w[article_tags tags]
end
end
検索URLを設定する
ヘッダーにある検索フォームのURLを変えます。
<li class="nav-item">
<%= render 'shared/search_form', url: search_path %>
</li>
検索パーシャルを作成する
search_form_for
を使ってましたが、検索オブジェクトを複数にもあるため、form_with
を使用しURL
オプションを指定します。
<%= form_with url: url, method: :get do |form| %>
<div class="input-group">
<%= form.text_field :q, class: 'form-control', placeholder: t('defaults.search_word') %>
<%= form.submit t('defaults.search_word'), class: 'btn btn-primary' %>
</div>
<% end %>
URLが短くなりましたね。
form_with
で作成された検索フォームです。
検索結果用ビューを作成する
articles/search.html.erb
でしたが、Search
コントローラーを作成したため、search/search.html.erb
に移動します。
<div class="row">
<% if @articles.present? %>
<h2>「<%= params[:q] if params[:q].present? %>」の投稿(<%= @articles.length %>)</h2>
<div class="row row-cols-1 row-cols-md-3 g-4">
<% @articles.each do |article| %>
<%= render 'articles/article', article: article %>
<% end %>
<div>
<%= paginate @articles %>
</div>
<% else %>
<h2>一致する投稿がありませんでした。</h2>
<% end %>
</div>
<% if @tags.present? %>
<h2>「<%= params[:q] if params[:q].present? %>」のタグ(<%= @tags.length %>)</h2>
<div class="row row-cols-1 row-cols-md-6 g-4">
<% @tags.each do |tag| %>
<span class="badge rounded-pill text-bg-dark me-2"><%= tag.name %></span>
<% end %>
<% else %>
<h2>一致するタグがありませんでした。</h2>
<% end %>
</div>
</div>
タグ絞り込み検索
タグでの投稿の絞り込み検索を実装していきます。
Articles
モデルにタグスコープを使う方法とTag
コントローラーを使う方法両方書いてみました。
スコープとは
scope
メソッドは、モデルクラス内でクエリを定義するために使用されます。
User
モデルがあるとします。このモデルにはname
とadmin
という2つの属性があります。name
は文字列型で、admin
は真偽値型です。
class User < ApplicationRecord
scope :admins, -> { where(admin: true) }
scope :name_starts_with, ->(prefix) { where("name LIKE ?", "#{prefix}%") }
end
上記のコードでは、2つの異なるスコープが定義されています。
-
admins
スコープは、admin
属性がtrue
であるユーザーをフィルタリングします。 -
name_starts_with
スコープは、name
属性が指定されたプレフィックスで始まるユーザーをフィルタリングします。このスコープでは、引数としてprefix
を取り、SQLのLIKE
演算子を使用してデータベースクエリを構築しています。
これらのスコープを使用することで、簡潔なクエリを実行することができます。以下は、これらのスコープを使用した例です。
# adminsスコープを使用して管理者ユーザーを取得する例
admins = User.admins
# name_starts_withスコープを使用して"John"で始まるユーザーを取得する例
john_users = User.name_starts_with("John")
admins
スコープはUser.admins
として呼び出され、管理者ユーザーのコレクションを返します。name_starts_with
スコープはUser.name_starts_with("John")
として呼び出され、名前が"John"で始まるユーザーのコレクションを返します。
scope
メソッドにより、再利用可能なクエリの定義が可能になり、コードの可読性と保守性が向上します。
タグスコープを作成する
投稿モデルにタグスコープを作成します。
class Article < ActiveRecord
scope :with_tag, ->(tag_name) { joins(:tags).where(tags: {name: tag_name}) }
コントローラーでスコープを使う
Articles
コントローラーでスコープを使った投稿一覧を取得できるようにします。
class ArticlesController < ApplicationController
def index
articles = if (tag_name = params[:tag_name])
Article.with_tag(tag_name)
else
Article.all
end
@articles = articles.all.includes(:user).order(created_at: :desc).page(params[:page]).per(10)
end
end
タグパーシャルにリンクを追加する
<% article.tags.each do |tag| %>
<%= link_to articles_path(tag_name: tag.name) do %>
<span class="badge rounded-pill text-bg-dark"><%= tag.name %></span>
<% end %>
<% end %>
スコープを使ってタグでの投稿の絞り込みを実装しました。
こちらの方法ではタグの独自のビューがないのとタグ関連の機能を増やしたい時にTag
コントローラーでロジックをまとめたいため以下の方法でもう一度実装してみました。
routes.rb
を編集する
Rails.application.routes.draw do
resources :tags, only: %i[index show]
end
Tagコントローラーを作成する
class TagsController < ApplicationController
def index
@tags = Tag.all
end
def show
@tag = Tag.find(params[:id])
end
end
タグにリンクを追加する
<% article.tags.each do |tag| %>
<%= link_to tag_path(tag) do %>
<span class="badge rounded-pill text-bg-dark"><%= tag.name %></span>
<% end %>
<% end %>
タグ詳細のビューを作成する
<%= content_for(:title, @tag.name) %>
<h1>タグ:<%= @tag.name %>(<%= @tag.articles.size %>)</h1>
<div class="row row-cols-1 row-cols-md-3 g-4">
<% @tag.articles.each do |article| %>
<%= render 'articles/article', article: article %>
<% end %>
</div>
終わりに
gemを使わずにタグ機能を実装してみました。
タグに便利なgemがたくさんありますので今度gemも使ってみたいと思います。
Discussion