【Rails】キーワード追加検索、絞り込み検索

# 1. はじめに
[【Rails】検索機能、ソート機能実装](https://qiita.com/tomaaaaaaaaa/items/6a7de506103734e8f5ce)
　上記リンクは前回投稿したもので、`whereメソッド`を用いてひとつのキーワードで条件に当てはまるレコードを取得していました。また、`params[:sort]`で受け取った値により`orderメソッド`を用いて取得した値を並べ替えしていました。


```ruby:前回のコード
sort = params[:sort] || "created_at DESC"
@keyword = params[:keyword]
@products = Product.where('name LIKE(?) OR description LIKE(?)', "%#{@keyword}%", "%#{@keyword}%").order(sort)
```

### これを複数のキーワードにより検索できるよう変更を加えていきます。
　コードが冗長となっている箇所や置き換えることのできるメソッドなど多くあるかと思いますので、その時はご指摘いただけると幸いです。

# 1. 導入したい機能
- 検索ワードの追加機能（もしくは絞り込み機能）
- 上記に対応したソート機能

# 2. 取得する値が複数だった場合にも対応できるようにする
　**前提として複数のキーワードとして入力される値は、キーワードごとにスペースが含まれるものと考えます。**

### 2-1. キーワード追加による検索の場合

```ruby:products_controller.rb

sort = params[:sort] || "created_at DESC"
@keyword = params[:keyword]

if @keyword.present?
  @products = []
  # 分割したキーワードごとに検索
  @keyword.split(/[[:blank:]]+/).each do |keyword|
    next if keyword == ""
    @products += Product.where('name LIKE(?) OR description LIKE(?)', "%#{keyword}%", "%#{keyword}%")
  end
  @products.uniq!
else
  @products = Product.order(sort)
end
```

　[splitメソッド](https://ref.xaio.jp/ruby/classes/string/split)により、入力された値を引数を区切り文字として、配列として返します。
　引数には、正規表現で`/[[:blank:]]+/`を使用し、これにより「全角スペース」、「半角スペース」にも対応できるようにしました。`+`を追加することで、連続した空白にも対応します。

　[[:blank:]についてはこちらを参照してください。](https://docs.ruby-lang.org/ja/latest/doc/spec=2fregexp.html)(正規表現リファレンス(POSIX 文字クラス))

　`params[:keyword]`へ`「りんご　みかん　ぶどう」`と値が与えられた場合、`["りんご", "みかん", "ぶどう"]`と配列で返されます。
　これをeach文により、配列の要素ごとに検索をして、それぞれの結果をを空の配列`@products` へ結合します。
　次に配列の中で重複する要素があった場合は、`uniq!メソッド`によりそれを削除し、新しい配列を返します。

　これにより、複数のキーワードに対応することができ、**「りんご、みかん、ぶどう」の条件に当てはまる該当カラムのレコードを全て取得することができました。**

### 2-2. 絞り込み検索の場合

```ruby:products_controller.rb

sort = params[:sort] || "created_at DESC"
@keyword = params[:keyword]

if @keyword.present?
  # 変更点①
  @products = Product.order(sort)
  # 分割したキーワードごとに検索
  @keyword.split(/[[:blank:]]+/).each do |keyword|
    next if keyword == ""
    # 変更点②
    @products = @products.where('name LIKE(?) OR description LIKE(?)', "%#{keyword}%", "%#{keyword}%")
  end
  # 今回は@products.uniq!は必要ないです
else
  @products = Product.order(sort)
end
```

　**変更点は二箇所です**
　`whereメソッド` は重ねて使用することができるため、上記の記述でeach文により、それぞれのキーワードで検索した場合このようになります。

```ruby:
# params[:keyword] = ["りんご", "みかん", "ぶどう"] だった場合
@products = Product.all.where('name LIKE(?)', "%#{"りんご"}%").where('name LIKE(?)', "%#{"みかん"}%").where('name LIKE(?)', "%#{"ぶどう"}%")
```
　上記の通り、`りんご`で検索した結果を`みかん`で検索し、さらに`ぶどう`で検索をしています。
　これにより、**「りんご、みかん、ぶどう」の条件で絞り込んだ該当カラムのレコードを取得することができました。**

# 3. ソート（並べ替え）機能を対応させる

　`2-1. キーワード追加による検索の場合`は、検索結果が配列に格納されています。
　そのため、**orderメソッドによる並べ替えができず、取得した配列の要素ごとにしか並べ替えができなくなりました。**

　これに対応するため、以下のように`sortメソッド`により配列の要素をソートします。
　`params[:sort]`の値は、[【Rails】検索機能、ソート機能実装](https://qiita.com/tomaaaaaaaaa/items/6a7de506103734e8f5ce) に記述しています。




```ruby:products_controller.rb

# 複数キーワード検索のソート
sort_result = params[:sort] || "created_at DESC"

if sort_result == "price asc"
  @products = @products.sort {|a, b| a[:price] <=> b[:price]}
elsif sort_result == "price desc"
  @products = @products.sort {|a, b| b[:price] <=> a[:price]}
elsif sort_result == "created_at asc"
  @products = @products.sort {|a, b| a[:created_at] <=> b[:created_at]}
elsif sort_result == "created_at desc"
  @products = @products.sort {|a, b| b[:created_at] <=> a[:created_at]}
end
```


以下は、[rubyリファレンス(sortメソッド）](https://ref.xaio.jp/ruby/classes/array/sort)引用

>sortメソッドは、配列の要素をソートした新しい配列を返します。要素の順序の比較には<=>演算子が使われ、「要素1 <=> 要素2」の結果が-1なら要素1が先、0なら同じ、1なら要素2が先となります。

```ruby:
@products = @products.sort {|a, b| a[:price] <=> b[:price]}
```
　このように記述すると、@productsの配列の要素をpriceカラムの値によって昇順(ASC)に並べ替え、a <=> b のa、bを入れ替えれば降順(DESC）に並べ替えが行われます。


# 4. あとがき

　
　記述が冗長な箇所やそもそも活用されていない記述が見受けられるかもしれません。その際は、ご指摘やご指導いただけますと幸いでございます。よろしくお願いします。