#はじめに
記事の検索・閲覧ができるアプリケーションを作成しています。
今回は検索機能を'ransack'というgemを使用して実装したため、備忘録・復習のため記述します。
#環境
Ruby on Rails '6.0.0'
Ruby '2.6.5'
#前提
記事の投稿(articleテーブル)は作成済みで、ユーザーは記事の閲覧ができる状態。
#ransackとは
シンプルな検索フォームを実装できると共に、高度な検索フォームも作成することができるgemです。
このransackを導入することで、以下のメソッドが使用できるようになります。
- _eqメソッド:条件にあった検索を行う
- _contメソッド:部分一致
- _iteqメソッド:「〜以下」という検索条件
他にも様々な検索に関するメソッドがありますが、今回は割愛します。
公式GitHubはこちらです。
#①gemの導入
gem 'ransack'
% bundle install
上記コマンドでgemを導入します。
#②ルーティングの記述
(中略)
get 'articles/search'
今回は、記事検索の機能を実装しているので、「articlesコントローラー」とします。
また検索機能は、「searchアクション」と命名しています。
#③コントローラー(articles)の記述
まず、全体を記載して、その後部分的に解説をしていきます。
class ArticlesController < ApplicationController
before_action :search_article, only: [:index, :search]
def index
@articles = Article.includes(:user).order('created_at DESC').limit(4)
@ranks = Article.find(Like.group(:article_id).order('count(article_id) DESC').limit(4).pluck(:article_id))
end
・・・(中略)・・・
def search
@results = @a.result(distinct: true).page(params[:page]).per(8).order('created_at DESC')
end
private
def search_article
@a = Article.ransack(params[:q])
end
end
private
def search_article
@a = Article.ransack(params[:q])
end
こちらの記述で、キー(:q)を使って、articlesテーブルから記事情報を探しています。そして、「@a」という名前の検索オブジェクトを生成しています。
この処理を行うメソッド名を「search_article」とし、index,searchアクションのみで使用するため、before_actionでの「only」で限定しています
def search
@results = @a.result(distinct: true).page(params[:page]).per(8).order('created_at DESC')
end
そして、この@aに対して、「.result」とすることで、検索結果を取得しています。
- 「distinct: true」:重複するデータは除外して取得できる
- .page(params[:page]).per(8).order('created_at DESC')」:kaminariというgemのページネーションを使用し、1つのページで8つの記事が作成順に表示されるようにしています。
#④viewファイルの記述
#####検索フォーム編
<%= search_form_for @a, url: articles_search_path do |f| %>
<div class="search-wrapper">
<div class="search-content col-12">
<%= f.label :type_id_eq, '種類を選択', class: 'label' %>
<div class="radio-btn">
<%= f.radio_button :type_id_eq, '', {checked: true} %>指定なし
<%= f.radio_button :type_id_eq, 1 %>補助金・助成金
<%= f.radio_button :type_id_eq, 2 %>制度融資
</div>
</div>
<div class="search-content col-12">
<%= f.label :area_id_eq, '地域を選択', class: 'label' %>
<%= f.collection_select(:area_id_eq, Area.all, :id, :name, {include_blank: "---"}, {class:"search-select-box float-right"}) %>
</div>
・・・(中略)・・・
<div class="search-content col-12">
<%= f.label :title_or_infomation_cont, 'キーワード検索', class: 'label' %>
<%= f.search_field :title_or_information_cont, placeholder: 'キーワードを入力', class:"search-form-control float-right" %>
</div>
<div class="search-content col-12 text-center">
<%= f.submit 'この条件で検索する', class:"btn btn-outline-success search-btn" %>
</div>
</div>
<% end %>
部分的に解説していきます。
<%= search_form_for @a, url: articles_search_path do |f| %>
1行目部分です。search_form_forの引数に「@a(検索オブジェクト)」を渡すことで検索フォームを生成しています。
<div class="search-content col-12">
<%= f.label :type_id_eq, '種類を選択', class: 'label' %>
<div class="radio-btn">
<%= f.radio_button :type_id_eq, '', {checked: true} %>指定なし
<%= f.radio_button :type_id_eq, 1 %>補助金・助成金
<%= f.radio_button :type_id_eq, 2 %>制度融資
</div>
</div>
- type_id_eqとすることで、ラジオボタンで選択したものと一致(=equal)するものを探します。type_idは記事のカラムとして含んでいます。
- 「checked: true」とすることでdefaltで選択済みになります。
<div class="search-content col-12">
<%= f.label :area_id_eq, '地域を選択', class: 'label' %>
<%= f.collection_select(:area_id_eq, Area.all, :id, :name, {include_blank: "---"}, {class:"search-select-box float-right"}) %>
</div>
- area_id_eqも同様に、areaが一致するものを探しています。
- <%= f.collection_select ~ %>でプルダウンが生成されます。今回は記事投稿機能で事前にActiveHashを使用しAreaモデルを作成済みです。
<div class="search-content col-12">
<%= f.label :title_or_infomation_cont, 'キーワード検索', class: 'label' %>
<%= f.search_field :title_or_information_cont, placeholder: 'キーワードを入力', class:"search-form-control float-right" %>
</div>
- 「title_or_information_cont」:今回記事のカラムの中で、titleとinformationの情報があり、その一部分の中でキーワードが合致するもの(_cont)を探しています。
#####検索結果編
<div class="container">
<h5 class="article-title"><i class="fas fa-poll-h fa-2x my-orange"></i> 検索結果一覧</h5>
<div class="row">
<% if @results.length !=0 %>
<% @results.each do |result| %>
<div class="card-group col-md-6 col-lg-3">
<%= link_to article_path(result.id), class: "article-link" do%>
<span class="article-info"><%= result.genre.name %></span>
<div class="card articles-chart">
<%= image_tag result.image, class:"card-img-top" if result.image.attached? %>
<div class="card-body">
<h5 class="card-title"><%= result.title %></h5>
<p class="card-text"><%= result.information.truncate(30) %></p>
<p class="like-button"><i class="far fa-heart fa-2x" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= result.likes.count %></span></p>
</div>
</div>
<% end %>
</div>
<% end %>
<% end %>
<% else %>
<h5 class="alert">該当する投稿はありません!</h5>
<% end %>
</div>
</div>
- <% if @results.length !=0 %>:検索結果があるかどうかで条件分岐を行い、あれば記事を表示、なければ「該当する投稿はありません!」を表示するようにしています。
以上で実装は完了です。
#おわりに
実装の一部分のため、わかりにくい表現があるかと思いますが、
不明点などあればご指摘ください。