はじめに。今回はrailsの検索機能についてまとめます。
"ransack"とういう"Gem"でも検索機能を実装できるようですが今回はコントローラを使って実装していきます。
背景
:ECサイトを作成中
/customer
/admin
検索するのはどちらも商品一覧。
/customer
/admin
によって検索結果後のビューが違う。
開発環境
AWS Cloud9
Ruby version : ruby 3.1.2p20
Rails version: Rails 6.1.7.3
導入済み
/Item.rb
BootStrap
Devise
ルーティング定義
scope module: :public do
...
get "search" => "searches#search"
end
...
namespace :admin do
...
get "search" => "searches#search"
end
ちなみに、"scope module: :public do ~ end"間に定義するとURLは通常通り"/about"や"/search"と表示されるが、
"namespace :admin do ~ end"間だとURLの先頭に"admin"と表示されるようになる。
EX) "/admin/items", "/admin/homes"
まず、コントローラの作成。
(admin側)
rails generate controller Admin::Search search
(public側)
rails generate controller Public::Search search
次に、searchアクションを定義します。
Admin,Publicともに同様の記述
def search
@word = params[:word]
@search = params[:search]
@range = params[:range]
if @range == "商品"
@items = Item.looks(@search, @word)
@genres = Genre.all
end
end
end
解説
アクション内で行われていること。
★ "params[:word]", "params[:search]", "params[:range]"から値を取得し、それぞれのインスタンス変数 "@word", "@search", "@range"に代入。
これらの値は、ユーザーが検索フォームに入力した検索クエリの情報。
★ if ~ "@range"の値が"商品"と一致する場合(==)、検索範囲が商品であることを示している。この場合"Item.looks(@search,@word)"というメソッドが呼び出され、検索条件にヒットする商品を取得し、"@items"というインスタンス変数に代入している。
また、"Genre.all"を使い全てのジャンルを取得し、"@genres"というインスタンス変数に代入している。
"search"アクションの処理が終了すると、Railsはデフォルトで
"views/(admin or public)/searches/search.html.erb"というビューファイルを使い、ブラウザに送信します。このビューファイルでは"@items"や"@genres"などのインスタンス変数を使って検索結果を表示することができます。
次はモデルに記述を追加しましょう。
# 検索方法分岐
def self.looks(search, word)
if search == "partial"
@item = Item.where("name LIKE?","%#{word}%")
end
解説
(今回は部分一致{partical}のみ使用してます。その他、完全一致{perfect_match}, 前方一致{forward_match}, 後方一致{backword_match}などがあるので状況に応じでモデルに追記していきましょう。)
参考記事 https://qiita.com/hapiblog2020/items/6c2cef49df5616da9ae3
★ 引数"search"が"partical"(部分一致)に一致するかどうかをチェックします。
もし(if)一致する場合は次の処理に進みます。
★ "Item.where("name LIKE?", "%{word}%")"というコードがあります。これはItemモデル(商品)のデータベーステーブルから、検索名が"word"の部分一致する商品を検索するための指定をしています。"LIKE"は、部分一致検索を行うためのSQLの演算子、"%"は任意の文字列を表すワイルドカードです。このコードは、"word"を含む商品名を持つ全ての商品を取得できます。
※ワイルドカードとは、検索やパターンマッチングで使用される特殊な文字や記号のことで、一定の規則に従って複数の文字や文字列にマッチさせることができます。要するに、ワイルドカードはある種の「プレースホルダ」の役割を果たし、代わりになる任意の文字や文字列を表現します。
特にデータベース検索では、ワイルドカードを使うことで部分一致検索や前方一致検索、後方一致検索などを行うことができます。SQLのLIKE演算子とワイルドカード(%)を組み合わせて使用することで、指定した文字列を含むデータを検索することができます。
例えば、%は、0文字以上の任意の文字列を表します。以下は、いくつかのワイルドカードの使用例です。
"apple%": "apple"で始まる任意の文字列にマッチします(例: "apples", "apple pie")。
"%apple": "apple"で終わる任意の文字列にマッチします(例: "pineapple", "green apple")。
"%apple%": "apple"を含む任意の文字列にマッチします(例: "pineapple", "apple pie", "green apple")。
★ 検索条件に一致する商品が見つかったら、それらの商品を"@item"というインスタンス変数に代入。その後、これの"@item"が繰り返し呼び出し元のコントローラに渡され、さらにビューで表示されます。
次に"layout_header.html.erb"に検索フォームを作りましょう!今回は部分テンプレートにしてます!!
<% if admin_signed_in? %>
<li class="nav-item col-md-4">
<div class="search_form">
<%= form_with url: admin_search_path, local: true, method: :get, class: "d-flex" do |f| %>
<%= f.text_field :word, class: "form-control me-2" %>
<%= f.hidden_field :range, value: "商品"%> <!-- 商品しか検索できないように設定-->
<%= f.hidden_field :search, value: "partial"%> <!-- 部分一致しかできないように設定-->
<%= f.submit "検索", class: "btn btn-outline-success" %>
<% end %>
</div>
</li>
<% else %>
...
<li class="nav-item col-md-4">
<div class="search_form">
<%= form_with url: search_path, local: true, method: :get, class: "d-flex" do |f| %>
<%= f.text_field :word, class: "form-control me-2" %>
<%= f.hidden_field :range, value: "商品"%> <!-- 商品しか検索できないように設定-->
<%= f.hidden_field :search, value: "partial"%> <!-- 部分一致しかできないように設定-->
<%= f.submit "検索", class: "btn btn-outline-success" %>
<% end %>
</div>
</li>
解説
★ まず<%= form_with url: %>はadminとpublicで分ける必要があります。そうしないと毎回どちらかの検索結果しか表示されません。
★ "form_with"ヘルパーを使用し、フォームを作成しています。このフォームの送信先URLは""search_path"or"admin_search_path"で指定されています。
★ <%= f.hidden_field :range, value: "商品" %>
非表示の入力フィールドを作成、"range"という名前をつけ、その値に"商品"を設定しています。
これにより検索範囲が商品に限定させるようになります
この隠しフィールドの値は、フォームが送信されると、コントローラに渡され、コントローラ内で使用されます。
最後に検索結果を表示させるビューを作成しましょう。
<% if @items.present? %>
<% @items.each do |item| %>
<tr></tr>
<tr>
<td rowspan="6"><%= image_tag item.get_image(200,200) %> </td>
<td>商品名</td>
<td><%= item.name %></td>
</tr>
<tr>
<td>商品説明</td>
<td><%= item.introduction %></td>
</tr>
<tr>
<td>ジャンル</td>
<td><%= item.genre ? item.genre.name : 'ジャンルが設定されていません' %></td>
</tr>
<tr>
<td>税込価格<br>(税抜価格)</td>
<td>
<%= item.with_tax_price.to_s(:delimited) %> (<%= item.price.to_s(:delimited) %>) 円
</td>
</tr>
<tr>
<td>販売ステータス</td>
<td><%= item.is_active_i18n %></td>
</tr>
<tr>
<td>
<%= link_to "編集する", edit_admin_item_path(item), class: "btn btn-success" %>
</td>
</tr>
<% end %>
<% else %>
<tr>
<td>アイテムが見つかりませんでした。</td>
</tr>
<% end %>
解説
searchesコントローラ内で、検索結果を代入したインスタンス変数に対して、each文をつかって1つずつ取り出していきましょう。
以上で検索機能の実装が完了しました。
今回はItemモデルの情報のみ検索結果に表示させるようにしました!
閲覧ありがとうございます!
何かご指摘等ございましたら是非コメント頂けたら幸いです!