検索機能の実装
検索対象は ユーザーか本の投稿か選べる。 検索方法も、完全一致、前方一致、後方一致、部分一致と選べる。コントローラーの作成
rails g controller Searches search
ルーティング記述
get '/search', to: 'searches#search'
検索フォーム作成
ヘッダーに入れたいので、部分テンプレートにします。
<% if user_signed_in? %>
<%= render 'layouts/search' %>
<% end %>
<div class="serch_form">
<%= form_with url: search_path,method: :get, local: true do |f| %>
<%= f.text_field :content %>
<%= f.select :model,options_for_select({"User"=>"user","Book"=>"book"})%>
<%= f.select :method,options_for_select({"完全一致"=>"perfect","前方一致"=>"forward",
"後方一致"=>"backward","部分一致"=>"partial"})%>
<%= f.submit'検索' %>
<% end %>
</div>
serch_pathルーティングに検索内容を渡しています。
ユーザーの入力した検索ワードはcontent
としてコントローラーのアクションに送られます。
初めましてな記述はf.select~でしょうか。この記述でプルダウン(セレクトボックス)が作成できます。
({"完全一致"=>"perfect","前方一致"=>"forward"...
この部分の記述ですが、
"選択肢で表示する文字"=>DBに渡す値(保存させる内容)となっています。
ちなみに...
<%=f.select:model,options_for_select({"User"=>"user","Book"=>"book"}),include_blank:"選択"%>
のように最後にinclude_blank:"選択"
を追加するとこんな感じになります。今回の場合だと見にくいですね。
セレクトボックスの作成方法は以下のページで理解できました。
まとめ:検索ワードはcontentで、検索対象はmodelで、検索方法はmethodでコントローラーに渡される。
コントローラー記述
class SearchesController < ApplicationController
before_action :authenticate_user!
def search
@model=params[:model]
@content=params[:content]
@method=params[:method]
if @model == 'user'
@records=User.search_for(@content,@method)
else
@records=Book.search_for(@content,@method)
end
end
end
入力、選択された値をparamsで受け取って@~に代入しています。
paramsとは、フォームなどによって送られてきた情報(パラメーター)を取得するメソッドです。
その後に、@model
の値がuser
だった場合と、book
だった場合で条件分岐しています。
そして@records
に入れているのは検索結果です。
モデルファイルに検索条件の分岐を書く。
ちなみにコントローラーの記述を少し変更すれば、
別にモデルファイルに書かなくても、コントローラー内に記載することも可能です。それについては後述します。
じゃあ!なんで!わざわざモデルファイルに書くのか!?
class Book < ApplicationRecord
# 以下3行は検索機能と関係なしです。
belongs_to :user
validates :title, presence: true
validates :body, presence: true, length: {maximum: 200}
def self.search_for(content, method)
if method == 'perfect'
Book.where(title: content)
elsif method == 'forward'
Book.where('title LIKE ?', content+'%')
elsif method == 'backward'
Book.where('title LIKE ?', '%'+content)
else
Book.where('title LIKE ?', '%'+content+'%')
end
end
end
method == 'perfect'
は、完全一致です。(viewファイルでそう定義してます。)
forward
は、前方一致。あとはわかりますよね。。
Like句で検索
[前方一致]
モデル名.where('カラム名 like ?','検索したい文字列%')
[後方一致]
モデル名.where('カラム名 like ?','%検索したい文字列')
以下の記事で詳しくわかります。
user.rbにも同様に記載してください。
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :books
attachment :profile_image, destroy: false
validates :name, length: { minimum: 2, maximum: 20 }, uniqueness: true
validates :introduction, length: { maximum: 50 }
def self.search_for(content, method)
if method == 'perfect'
User.where(name: content)
elsif method == 'forward'
User.where('name LIKE ?', content + '%')
elsif method == 'backward'
User.where('name LIKE ?', '%' + content)
else
User.where('name LIKE ?', '%' + content + '%')
end
end
end
viewページ作成
部分テンプレート使わないとこんな感じ...長くて申し訳ないです。
<div class="container">
<div class="col-xs-12">
<% if @model == "user" %>
<h2>Users search for '<%= @content %>'</h2>
<table class="table">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Introduction</th>
</tr>
</thead>
<% @records.each do |user| %>
<tbody>
<tr>
<th>
<%= attachment_image_tag(user, :profile_image, :fill, 40, 40, fallback: "no_image.jpg", size:'40x40') %>
</th>
<th>
<%= user.name %>
</th>
<th>
<%= user.introduction %>
</th>
</tr>
</tbody>
<% end %>
<% elsif @model == "book" %>
<h2>Books search for '<%= @content %>'</h2>
<table class="table">
<thead>
<tr>
<th></th>
<th>Title</th>
<th>Opinion</th>
</tr>
</thead>
<% @records.each do |book| %>
<tbody>
<tr>
<th>
<%= attachment_image_tag(book.user, :profile_image, :fill, 40, 40, fallback: "no_image.jpg", size:'40x40') %>
</th>
<th>
<%= book.title %>
</th>
<th>
<%= book.body %>
</th>
</tr>
</tbody>
<% end %>
<% end %>
</table>
</div>
</div>
部分テンプレートを使う場合。
<% if @model == 'user' %>
<h3>Users search for "<%= @content %>"</h3>
<%= render 'users/index', users: @records %>
<% elsif @model == 'book' %>
<h3>Books search for "<%= @content %>"</h3>
<%= render 'books/index', books: @records %>
<% end %>
<table class='table'>
<thead>
<tr>
<th>image</th>
<th>name</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% users.each do |user| %>
<tr>
<td><%= attachment_image_tag(user, :profile_image, :fill, 50, 50, fallback: "no-image-icon.jpg") %></td>
<td><%= user.name %></td>
<td><%= link_to 'Show', user_path(user), class: "user_#{user.id}" %></td>
</tr>
<% end %>
</tbody>
</table>
<table class='table table-hover table-inverse'>
<thead>
<tr>
<th></th>
<th>Title</th>
<th>Opinion</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% books.each do |book| %>
<tr>
<td><%= link_to user_path(book.user) do %>
<%= attachment_image_tag(book.user, :profile_image, :fill, 50, 50, fallback: "no-image-icon.jpg") %>
<% end %>
</td>
<td><%= link_to book.title, book_path(book), class: "book_#{book.id}" %></td>
<td><%= book.body %></td>
</tr>
<% end %>
</tbody>
</table>
検索条件の分岐をモデルファイルではなくコントローラーに書きたい場合
こちらの記事では、モデルファイルではなくコントローラーに記述しています。
https://qiita.com/IsakiMatsuo/items/69f406d85dbd0ba62625
その他、参考にさせていただいた記事
https://qiita.com/hapiblog2020/items/6c2cef49df5616da9ae3