はじめに
Rails6.1の環境で検索画面を作成しました。
検索はなるべく1つのフォームを使いまわしたいけど、ページによって表示を変える必要があったので、その際の設定や初心者ならではの学びについて記録します。
この記事の結論
- コントローラごとに記述を分けるときは「controller_name」を使った
- モデルのデータと配列のデータは別物
- メソッドにはRailsだけでなくRubyのメソッドもある
コントローラごとに表示を分ける
今回作成するのは商品予約サイトにおけるショップ側の検索機能です。
予約検索では「予約履歴の検索」
商品検索では「商品情報の検索」
が必要になり、検索の方法が変わってくるので、部分テンプレートを使ってセレクトボタンを分けることにしました。
<%= form_with url: shop_search_path, method: :get do |f| %>
<% if controller_name == 'items' %>
<%= render 'shop/searches/select_item', f: f %>
<% elsif controller_name == 'pre_orders' %>
<%= render 'shop/searches/select_pre_order', f: f %>
<% end %>
<%= f.text_field :content, placeholder: 'キーワードから探す' %>
<%= f.submit '検索' %>
<% end %>
<%= f.select :type, options_for_select({'お客様情報から検索' => 'order_member', '商品情報から検索' => 'order_item_all', '商品名を指定して検索' => 'order_item'}), {}, {:class => "form-control"} %>
「お客様情報から検索」ではお客様の名前や住所、電話番号なども含めてあいまい検索
「商品情報から検索」では商品名+紹介文のあいまい検索
「商品名を指定して検索」では商品名の完全一致や前方一致の検索機能が欲しいと思ったので分けています。
<%= f.select :type, options_for_select({'商品を検索' => 'item','商品名を指定して検索' => 'item_name'}), {}, {:class => "form-control"} %>
こちらも商品検索を2パターンに分けています。
検索用コントローラの記述
こちらは「全ての商品情報から検索(order_item_all)」を呼び出すための記述です。
とても長い記述になっていたので他の部分は省いています。
def search
:
if @type == 'order_member'
:
:
elsif @type == 'order_item_all'
shop_items = Item.where(shop_id: current_shop.id) #ログイン中ショップのItemを指定
order_records = PreOrder.where(['name LIKE?','%'+@content+'%']) #予約の商品名を検索
item_ids = Item.where(['name LIKE? OR introduction LIKE?',
'%'+@content+'%','%'+@content+'%'])
item_records = PreOrder.where(item_id: item_ids) #予約に紐づいた商品の検索
@records = (order_records + item_records) & shop_items
:
:
end
end
私の作成しているサイトの場合はショップが複数あるため、ログイン中のショップ情報を取得する必要があります。
また、予約には商品(items)が紐づいていますが、念のため予約(pre_orders)テーブルへ現状の商品名が登録されるような設定になっています。
※予約後に商品名が変更になることもあると考えたため。
そのため、pre_ordersの商品名(name)と紐づいたitemsの商品名(name)の両方が引っかかるようにしています。
検索結果はorder_records かitem_recordsで、尚且つshop_itemsであるデータを@recordsとして呼び出しています。
ちなみに、最後の、
@records = (order_records + item_records) & shop_items
の部分の書き方がいまいち分からずに少し苦労しました。
問題発生…商品が複数引っかかる
これで完了!
と思って検索を試してみると、なんだか数がおかしい…もしかして、同じ予約履歴が2つずつ表示されている…?
何が起こっているんだろう…と考えてみたら、そういえばpre_ordersとitemsで履歴を2回呼び出していたのでした…。
商品名が予約時と同じなら複数引っかかるのも当然です。
検索結果は1個だけ表示するように修正していきます。
def search
:
+ shop_items = Item.where(shop_id: current_shop.id)
if @type == 'order_member'
:
:
elsif @type == 'order_item_all'
order_records = PreOrder.where(['name LIKE?','%'+@content+'%'])
+ .where(item_id: shop_items.pluck(:id))
item_ids = Item.where(['name LIKE? OR introduction LIKE?','%'+@content+'%','%'+@content+'%'])
item_records = PreOrder.where(item_id: item_ids)
+ .where(item_id: shop_items.pluck(:id))
- # @records = (order_records + item_records) & shop_items
+ @records = (order_records + item_records).uniq
:
:
end
end
まず、shop_itemsは他のif文でも必要になってしまったので、外へ出すことにしました。
また、最終的な表示結果ではuniqメソッドを使用して重複データを省く設定にしています。
このuniqを使う記述に関してはAIチャットで取得した情報だったので、どんなメソッドなのかなと思い調べてみると…どの記事にもRails5以降は非推奨で使えなくなるとの説明が…、
uniqメソッドとは、Railsで取得した重複レコードを1つにまとめるためのメソッドです。
uniqメソッドを実行すると、Railsは内部的にSQLのDISTINCTを実行します。
その他に、uniqメソッドとまったく同じ働きをするdistinctメソッドも存在します。
両者の違いは、
uniqメソッド → Rails5以降で非推奨(または使えなくなる)
distinctメソッド → Rails5以降で正式メソッド
となっているので注意しましょう!
困ったな…と思い、記事の言うようにdistinctメソッドを試してみようとしましたが上手くいかず苦戦…。
しかもdistinctの使い方は私の書いてるuniqとは何だか使い方が違いそう…
というか、そもそも使えなくなるはずのuniqメソッドはRails6.1の私の環境で使えていたのです。
詰まってしまったので、いったん諦めて記述はuniqのままに他の作業に入り、どこかでメンターさんに伺う機会があれば聞いてみようと保留にしました。
この問題の結論…uniqは非推奨ではない
メンターさんに確認してみたところ、非推奨なのはRailsでのuniqメソッドであり、今回私が使っていたのはRubyのuniqメソッドだそうです。
どうやら、Railsのuniqメソッドはdistinctのようにモデルを参照する際に使えるもので、
Rubyでは配列を扱うためにuniqメソッドが使われるんだとか。
Railsの公式ドキュメントはよく見ていたけれど、Rubyは全然見たことありませんでした…。
確かに言われてみれば当たり前なのですが、Rubyを動かすためのメソッドをRailsが用意してくれているはずなので、おおもとのRubyのメソッドがあるのも当然です。
当然なのですが、私には目から鱗でした…笑
最後に
考えてみれば、なんでこのメソッドはこの場所で使えないんだろう?みたいなこともちょこちょこあって、なぜかは説明できないけどそういうもの。と頭の中で勝手に処理されてしまっていました。
データの形自体が同じじゃなくて、配列だから使えるメソッド、モデルだから使えるメソッド、というように用途に合わせて使い分けがされていることがよく分かりました。
そもそも知らないことに気づくのは難しいですが、メンターさんや一緒に学習しているチームメンバーとの会話の中からも気づきや発見が多くてとても助かっています。
今回、新しいことの知る機会を与えくださったメンターさんに感謝です。