はじめに
あけましておめでとうございます🐄
Railsで定番の検索機能。
検索機能に関しての記事はQiita上でもたくさんあるが、どれも一つのテーブルの中の一つのカラムから検索する方法ばかり。(あとは、もう古くなったform_tagが使われていたり)
でも実際は一つのテーブルの中の複数のカラムから検索したいケースもあるはず。
例えば、
ActiveRecord::Schema.define(version: 2020_12_18_025546) do
create_table "agents", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "last_name", null: false
t.string "first_name", null: false
t.string "last_name_kana", null: false
t.string "first_name_kana", null: false
t.string "company_name", null: false
t.string "company_location", null: false
t.bigint "user_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["user_id"], name: "index_agents_on_user_id"
end
end
上記のagentsテーブルは「氏」と「名」と「氏のフリガナ」と「名のフリガナ」が独立しているので、agentを特定する際に一つのカラムから検索することはできません。
ましてやこの場合は一つの検索フォームから会社名(company_name)だったり会社の所在地(company_location)からagentを特定できればもっと便利になるはずです。
順序
- Viewに検索フォームを配置する
- searchアクションのルーテインングを設定する
- 検索するためのメソッド(searchメソッド)をモデルに定義する ←ココ重要
- searchアクションをコントローラーに定義する
- 検索結果画面のViewを作成する
では早速やっていきましょう。
1. Viewに検索フォームを配置する
検索フォームを作りたいViewの中に検索フォームを書いていきます。
<%= form_with(url: search_agents_path, local: true, method: :get, class: "field has-addons") do |form| %>
<%= form.text_field :keyword, placeholder: "営業マンを検索する", class: "input" %>
<%= form.submit "検索", class: "button is-info" %>
<% end %>
※ class名はCSSフレイムワークのBULMAを使っています。
↓以下のように検索ボックスが作れました!
2. searchアクションのルーテインングを設定する
searchアクションはRailsのデフォルトである7つのアクション(index, show, new, create, edit, update, destroy)以外なので”search”と言う名前でアクションを定義します。
自分で定義したアクションにはmemberまたはcollectionというオプションを使う必要があります。
詳細は ココで確認してみてください。
今回は:idを関連付ける必要が無いので、collectionを使います。
Rails.application.routes.draw do
devise_for :users
root to: 'homes#top'
resources :agents, only: [:index, :show, :new, :create] do
resources :reviews, only: [:index, :create, :edit, :update, :destroy]
collection do #←ココ
get 'search' #←ココ
end #←ココ
end
end
3. 検索するためのメソッド(searchメソッド)をモデルに定義する ココ重要
whereメソッドとLike句を使います。
whereメソッドとは
モデルが使用できる、ActiveRecordメソッドの1つ。
**モデル名.where(条件)**のの形で引数部分に条件を指定すること、テーブル内の「条件に一致したレコードのインスタンス」を配列の形で取得できます。引数の条件には、「検索対象となるカラム」を必ず含めなければなりません。
Like句とは
曖昧な文字列で検索しても、テーブルの対象のカラムから検索したいもの探し出せるようにするために使用するSQLのクエリです。whereメソッドの条件の中で使います。
一つのカラムから検索する場合は、以下のような構文をモデル内で用います。
モデル名.where('探し出したいカラム名 LIKE(?)', "%#{search}%")
複数のカラムから検索したい場合は、(例として3つのカラムから探したい時)
モデル名.where('探し出したいカラム名 LIKE(?) OR 探し出したいカラム名 LIKE(?) OR 探し出したいカラム名 LIKE(?)', "%#{search}%", "%#{search}%", "%#{search}%")
のような構文になります。
「探し出したいカラム名 LIKE(?)」を「OR」で繋いで、カラムの数の分だけ「"%#{search}%"」を書くことによって複数のカラムから検索をすることができます。
具体的には、以下のようにsearchメソッドをモデル内で定義しました。
class Agent < ApplicationRecord
with_options presence: true do
validates :user_id
validates :company_name
validates :company_location
with_options format: { with: /\A(?:\p{Hiragana}|\p{Katakana}|[ー-]|[一-龠々])+\z/ } do
validates :first_name
validates :last_name
end
with_options format: { with: /\A[ァ-ヶー-]+\z/ } do
validates :last_name_kana
validates :first_name_kana
end
end
def avg_score
if reviews.empty?
0.0
else
reviews.average(:score).round(1).to_f
end
end
def review_score_percentage
if reviews.empty?
0.0
else
reviews.average(:score).round(1).to_f / 5 * 100
end
end
#↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ココ
def self.search(search)
if search != ""
Agent.where('last_name LIKE(?) OR first_name LIKE(?) OR last_name_kana LIKE(?) OR first_name_kana LIKE(?) OR company_name LIKE(?) OR company_location LIKE(?)', "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%")
else
Agent.all
end
end
#↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ココ
has_many :reviews, dependent: :destroy
belongs_to :user
end
if文を用いて、検索結果が一つも無い場合は全てのagentを出力するようにしました。
4. searchアクションをコントローラーに定義する
以下のようにsearchアクションをコントローラーに追加します。
def search
@agents = Agent.search(params[:keyword])
end
検索フォームから送られて来ているパラメーターである**:keyword**を引数にします。
5. 検索結果画面のViewを作成する
新しくsearch.html.erbを作成することで、検索結果を表示するviewを作成します。
コントローラーで定義した@agentsをeach文を用いて全て表示していきます。
<div class="section pb-0">
<div class="container">
<div class="columns is-centered">
<div class="column is-6">
<%= form_with(url: search_agents_path, local: true, method: :get, class: "field has-addons") do |form| %>
<%= form.text_field :keyword, placeholder: "営業マンを検索する", class: "input" %>
<%= form.submit "検索", class: "button is-info" %>
<% end %>
<div>
次の検索結果を表示しています:<span class="has-text-weight-bold is-size-4"> <%= params[:keyword] %></span>
</div>
</div>
</div>
</div>
</div>
<section class="section">
<div class="container">
<div class="columns is-centered">
<div class="column is-5">
<% @agents.each do |agent| %>
<div class="card mb-6">
<header class="card-header">
<p class="card-header-title">
<%= agent.last_name %> <%= agent.first_name %>
<span class="has-text-weight-light is-italic is-size-7">(<%= agent.last_name_kana %> <%= agent.first_name_kana %>)</span>
</p>
</header>
<div class="card-content">
所属会社: <%= agent.company_name %><br>
場所: <%= agent.company_location %><br>
<div class="content">
<div class="content average-score">
<div class="star-rating mb-2">
<div class="star-rating-front" style="width: <%= agent.review_score_percentage %>%">★★★★★</div>
<div class="star-rating-back">★★★★★</div>
</div>
<div class="average-score-display ml-3 pt-2">
<%= agent.avg_score %>点(<%= agent.reviews.count %>件のレビュー)
</div>
</div>
</div>
</div>
<footer class="card-footer">
<%= link_to agent_reviews_path(agent), class: "button card-footer-item" do %>
レビューを見る
<% end %>
<%= link_to agent_path(agent), class: "button card-footer-item" do %>
レビューを書く
<% end %>
</footer>
</div>
<% end %>
<div class="is-centered">
<%= link_to new_agent_path, class: "button is-primary mt-3" do %>
新しい営業マンを追加する
<% end %>
</div>
</div>
</div>
</div>
※ class名はCSSフレイムワークのBULMAを使っています。
↓以下のような検索結果画面ができました。
これは検索ボックスに「木」と入力して出た検索結果です。
最後に
自分がRailsで検索機能を実装していてあったら良いなと思うものを書いてみました。
いかがでしたでしょうか。
間違っている点やわかりにくい点があったらお気軽に教えてください!
では次回の投稿でお会いしましょう。