検索機能の実装

# 検索機能の実装
## 概要
- 学習マッチングサイトを作成する。ユーザーが講座を作成して、ユーザー間で売買を行う。バイヤー側が、講座を様々な条件で検索できるようにしたい。
- 具体的には、カテゴリー、受講形態（オンラインかオフラインか）、アクセス先（住所かURLか）、価格（上限）、タグで検索できるようにしたい。
- カテゴリー、受講形態、アクセス先、価格はand検索、タグはor検索にしたい。
## アソシエーション
![0b5fbcf246ecc3190132d23b59ea7a28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1923339/de465090-247d-6bcc-3039-a723e0f98ce3.png)
## テーブル定義
- Lessonsテーブル
[![Image from Gyazo](https://i.gyazo.com/b80f2208176be59fa64d498ab6db603f.png)](https://gyazo.com/b80f2208176be59fa64d498ab6db603f)
- Categoriesテーブル
[![Image from Gyazo](https://i.gyazo.com/e6b254a58ba82aee9e2721776f586fb6.png)](https://gyazo.com/e6b254a58ba82aee9e2721776f586fb6)
- Tagsテーブル
[![Image from Gyazo](https://i.gyazo.com/7269b75680240171180d10c1871f950b.png)](https://gyazo.com/7269b75680240171180d10c1871f950b)
- TagListsテーブル
[![Image from Gyazo](https://i.gyazo.com/daac82824864dbef6de8106ea3093313.png)](https://gyazo.com/daac82824864dbef6de8106ea3093313)
## Lessonsコントローラ
[![Image from Gyazo](https://i.gyazo.com/8a995a4c24e37b925f0da408fcded334.png)](https://gyazo.com/8a995a4c24e37b925f0da408fcded334)
検索画面ではsearchメソッドが呼ばれ、検索結果表示画面ではresultメソッドが呼ばれる。
## and検索としてのsearchメソッド
[![Image from Gyazo](https://i.gyazo.com/23f155d505904dedbfbdc4e327998263.png)](https://gyazo.com/23f155d505904dedbfbdc4e327998263)
カテゴリー、受講形態、アクセス先、価格、それぞれを別々の検索メソッドとして定義し、それをメソッドチェーンで繋げることでand検索を実現している。
### scopeについて
scope機能とは、モデル側であらかじめ特定の条件式に対して名前をつけて定義し、その名前でメソッドの様に条件式を呼び出すことが出来る仕組みのこと。

class モデル名 < ApplicationRecord
&emsp;scope :スコープの名前, -> { 条件式 }
end

が基本構文であり、条件式に合致する値を返す。さらに、

class モデル名 < ApplicationRecord
&emsp;scope :スコープの名前, -> (引数){ 条件式 } 
end

このように引数を渡すことができる。今回は、受け取ったparamsを引数に渡して、各条件式に適用している。
### whereについて
今回は、全てプレースホルダーを使った書き方をしている。こちらの方が引数を渡す際には、後々変更を加えたりすることを考えて、シンボル指定や文字列指定よりも扱いやすい気がする。
### カテゴリー検索
[![Image from Gyazo](https://i.gyazo.com/93a2623d21e8a8eb0b8cc2257027241c.jpg)](https://gyazo.com/93a2623d21e8a8eb0b8cc2257027241c)
category_idを受け取り、category_idが合致するものを検索している。「if category_id.present?」により、category_idがない場合は、実行されない。
### 受講形態検索
[![Image from Gyazo](https://i.gyazo.com/cd7f46f0918511a456a4f263a6854e40.jpg)](https://gyazo.com/cd7f46f0918511a456a4f263a6854e40)
attending_style（オンラインかオフライン）を受け取り、合致するものを検索している。attending_styleがない場合は実行されない。
### アクセス先検索
[![Image from Gyazo](https://i.gyazo.com/70c40f2de79d6486628abc5fe8c4938b.jpg)](https://gyazo.com/70c40f2de79d6486628abc5fe8c4938b)
フォームに入力された文字列(access)を元に、あいまい検索を行う。そのために、LIKEを使用する。「%#{access}%」とすることで、入力された文字列を含むものを全て抽出できる。
### 価格検索
[![Image from Gyazo](https://i.gyazo.com/55d7132281b43e9c682c766257d3f581.jpg)](https://gyazo.com/55d7132281b43e9c682c766257d3f581)
選択された価格(price)以下の価格であるものを検索している。priceがない場合は実行されない。
### searchメソッドにまとめる
[![Image from Gyazo](https://i.gyazo.com/1bc788cb4dd4a2af0d2c26c905db8e1f.jpg)](https://gyazo.com/1bc788cb4dd4a2af0d2c26c905db8e1f)
各検索メソッドをメソッドチェーンで繋ぎ、それをsearchメソッドとして定義している。入力された値がない場合にはreturnで離脱させる。scope　の　{}　の部分は　do...end　に置き換え可能であるらしいので、今回はこのブロック構文を使用した。
### ストロングパラメータについて
[![Image from Gyazo](https://i.gyazo.com/d1a43823dc23e0b5c0d8621dc70e3d9c.jpg)](https://gyazo.com/d1a43823dc23e0b5c0d8621dc70e3d9c)
検索画面から送られたparameterに対して、許可処理を行っているが、require[:search]とすると、params[:search]が空であった場合に、エラーが発生する。そのため、今回はfetchメソッドを使用する。
fetchメソッドは
ハッシュ.fetch(:キー, デフォルト値)
と書く。
指定したキーの値を取得し、存在しない場合にはデフォルト値を返す。このため、空であった場合でもエラーをなくすことができる。
あとは、permitメソッドを使って、必要な情報のみを取得するようにする。
## タグをor検索する
[![Image from Gyazo](https://i.gyazo.com/689fcd4e5cbd97c6f2df1003ef01eca7.jpg)](https://gyazo.com/689fcd4e5cbd97c6f2df1003ef01eca7)
if...endでタグの情報がある場合にのみ、or検索が実行されるようにしている。
### 該当するtagのidを配列に格納する
[![Image from Gyazo](https://i.gyazo.com/ded542cdfda4ab0c594e12e130d7bad3.jpg)](https://gyazo.com/ded542cdfda4ab0c594e12e130d7bad3)
params[:search][:tags]は先頭に空文字を含む配列であるため、selectメソッドを使って、空文字を排除してからtag_idsに格納している。
select(&:present?)　は、基本構文である　select{|tag| tag.present?}と同じ挙動である。上記の書き方の方が処理速度が少し早いらしいです。
### 該当するtagのtag_listを配列に格納する
[![Image from Gyazo](https://i.gyazo.com/cd9b027962b2cd04ef5731ad20fd8aae.jpg)](https://gyazo.com/cd9b027962b2cd04ef5731ad20fd8aae)
TagListモデルの空のインスタンスを生成する。
[![Image from Gyazo](https://i.gyazo.com/64b60d42342876ba59e8223531749184.jpg)](https://gyazo.com/64b60d42342876ba59e8223531749184)
orメソッドとwhere句を組み合わせることで、tag_idが合致するtag_listを全て@tag_listsに格納している。
### 該当するlessonを配列に格納する
[![Image from Gyazo](https://i.gyazo.com/8495d202a06667d2d96808313804bfe9.jpg)](https://gyazo.com/8495d202a06667d2d96808313804bfe9)
Lessonモデルの空のインスタンスを生成する。
[![Image from Gyazo](https://i.gyazo.com/f8732e4e2b1ade6948a6e6da2e2e9ad6.jpg)](https://gyazo.com/f8732e4e2b1ade6948a6e6da2e2e9ad6)
上記の方法と同じで、orメソッドとwhere句を組み合わせて、tag_listのlesson_idとidが合致するlessonを全て@lessons　に格納している。
## 修正！
以下のように修正した。
[![Image from Gyazo](https://i.gyazo.com/0cf93c0a7f1b1cb2efcb9dbd9086bd42.png)](https://gyazo.com/0cf93c0a7f1b1cb2efcb9dbd9086bd42)
この記事を書きながら、tagがない場合にand検索できないことに気づいた。そのため、tagがある場合を、
[![Image from Gyazo](https://i.gyazo.com/8b130b41fb9e9266eab4c6028ec04c7f.jpg)](https://gyazo.com/8b130b41fb9e9266eab4c6028ec04c7f)
とした。params[:search][:tag]は先頭に空文字が入るため、最低でも１つの要素を持つ配列であるため、lengthが１より大きい場合にはtagがあると判断できる。
tagがない場合には、else以下の処理が実行され、and検索のみが実行されるようにした。
## まとめ
ポートフォリオが形になって２ヶ月程度が経って、自分のやったことを言語化するために、本記事を作成したが、改めて見るとかなり間違いが多く、それにも気づけたことはかなりの収穫であったと思います。
以下が参考にしたリンクです。
[【Rails】複数の条件の検索機能を作る。日付範囲検索を行う](https://qiita.com/poire_prog/items/48f569cdfc8000e18896)
[railsで複数ワードでの検索機能(and)とマイナス検索機能(-)を実装してみる](https://qiita.com/Orangina1050/items/c53919864b5313ab9672)
[Rails 5 の or を色々試してみた](https://qiita.com/QUANON/items/0033a5adb76d0cb963cd)

[【Rails】 モデルのスコープ機能(scope)の使い方を１から理解する](https://pikawaka.com/rails/scope)
[Rubyにおけるブロック構文(do～end)の使い方](https://style.potepan.com/articles/27709.html)
[【初心者必見】Rubyのselectメソッドの基礎から応用まで解説](https://www.sejuku.net/blog/19226)

[【Rails】 whereメソッドを使って欲しいデータの取得をしよう！](https://pikawaka.com/rails/where)
[【SQL】基本だけど重要！条件式の設定”WHERE句”について解説。](https://style.potepan.com/articles/24069.html)
[Ruby on Railsでor条件のSQLで検索する方法を解説
](https://style.potepan.com/articles/29804.html#whereOR)[Active Record クエリインターフェイス](https://railsguides.jp/active_record_querying.html)

[はじめてのRuby！fetchメソッドの使いかたをマスターしよう！](https://www.sejuku.net/blog/58930)
[【Rails】 permitメソッドを使ってストロングパラメーターにしよう](https://pikawaka.com/rails/permit#require%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)