【Ransack】セレクトボックスでソート機能

# はじめに🧎🏻

Ransackを使い検索機能を実装している個人開発サービスにて
下記画像のようなソート機能を実装する際に少し面倒だったので、備忘録も兼ねて記します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834267/210b75ec-bd2f-945f-228c-0b3bff89cd79.png" width="300">

### 環境💻
- Ruby `3.0.1`
- Ruby on Rails `6.1.4.4`
- 【gem】 [Ransack](https://github.com/activerecord-hackery/ransack)

### 想定読者👥
- ransackでのソートをデフォルト表示からセレクトボックスでの選択に変えたい人

# ①セレクトボックスの選択肢にリンクを貼る🔗
まず、ただのセレクトボックスだと選択したところでリンク先に飛ばないので、
`<option>`を選択するとそれに対する`value`の値に遷移するようにします。

```html
<select onChange="location.href=value;">
    <option value="">default</option>
    <option value="./sample1.html">sample1</option>
    <option value="./sample2.html">sample2</option>
    <option value="./sample3.html">sample3</option>
</select>
```
> onChangeを使用して、valueの値が変化した時に`location.href=value;`を実行します。
valueには、optionのvalueに指定したものが入ってきます。


参考：[select optionで選択された時にvalueに指定した値で遷移する方法 | Awesome Blog](https://awesome-linus.com/2019/05/09/select-option-href/)

# ②valueにソートリンクを入れる🎯

次にvalueにソート用のリンクを入れます。
#### 1. `sort_link`の中身を見る
ransackには`sort_link`というヘルパーメソッドが用意されていて、これを使うとその名の通り、ソート用のリンクを出力してくれます。便利ですね。
ということでこれを使えそうなので、どんなリンクが出力されるのか見てみましょう。

```erb
<%= sort_link(@q, :updated_at, '新着順' ) %>
　↓ 
<a class="sort_link " href="/whiskeys?q%5Bs%5D=updated_at+desc">新着順</a> 
```

`href="/whiskeys?q%5Bs%5D=updated_at+desc"`というhref属性が出力されています。
- `whiskeys`　→　indexページのurl(よくある例なら`users`や`posts`など)
- `q`　→　ransackのクエリを格納するparamsのname属性。（詳細は割愛）
- `%5Bs%5D=updated_at+desc`　→　`[s]=update_at+desc`　→　`q[s]`というparamsに`update_at+desc`という値を入れる

ransackでは`q[s]`の中身をみて並び替えのクエリを発行しているようですね。

#### 2-A. NG例❌
つまりこの`%5Bs%5D=updated_at+desc`をセレクトフォームの各valueに渡せば、実現できそう。
ということで`<option>`に以下のようなコードを書きました。
```html
<option value="?q%5Bs%5D=updated_at+desc">新着順</option>
```
これでも動作はします。 **が、**
キーワード検索やその他何か条件を絞って検索する場合、
ransackではその条件もクエリパラメータに含めて渡しているため、上記の形だとそれを上書きする形になります。

そのため、**「なにか条件を絞って検索した結果」に対してソートしたい場合**に条件がリセットされて出来ません。

#### 2-B. OK例⭕（but, Not Smart🤢）
じゃあどうするのかというと、
ransackで用意されている`sort_link`や`sort_url`から生成した場合は、
**検索した条件も含めたURLが出力される**のでそれを利用します。
※`sort_url`…`sort_link`の**urlのみ**返してくれるメソッド。

```erb
# 見た目上改行しています
<option value=
	"<%= sort_by_sort_url(sort_url(@q, :updated_at), 'updated_at+desc') %>">
	新着順
</option>
```
- `sort_by_sort_url`…渡されたurlのソート部分のクエリパラメータを渡した文字に置き換える独自メソッド。(ヘルパーに定義)
```ruby
   def sort_by_sort_url(url, order)
       url.gsub(/%5Bs%5D=\w+\+\w+/, "%5Bs%5D=#{order}")
   end
``` 

#### 3. 完成形
ということで完成形がこちらです。
```erb
<select class="ui fluid dropdown" onChange="location.href=value;">
  <option value="">並び替え</option>
  <option value="<%= sort_by_sort_url(sort_url(@q, :updated_at), 'updated_at+desc') %>">新着順</option>
  <option value="<%= sort_by_sort_url(sort_url(@q, :price), 'price+desc') %>">価格の高い順</option>
  <option value="<%= sort_by_sort_url(sort_url(@q, :price), 'price+asc') %>">価格の安い順</option>
  <option value="<%= sort_by_sort_url(sort_url(@q, :rarity), 'rarity+desc') %>">入手のし易さ：安定供給</option>
  <option value="<%= sort_by_sort_url(sort_url(@q, :rarity), 'rarity+asc') %>">入手のし易さ：激レア</option>
  <option value="<%= sort_by_sort_url(sort_url(@q, :mouth_feel), 'mouth_feel+asc') %>">口当たり：軽い順</option>
  <option value="<%= sort_by_sort_url(sort_url(@q, :mouth_feel), 'mouth_feel+desc') %>">口当たり：重い順</option>
  <option value="<%= sort_by_sort_url(sort_url(@q, :flavor_strength), 'flavor_strength+asc') %>">香りの強さ：繊細な順</option>
  <option value="<%= sort_by_sort_url(sort_url(@q, :flavor_strength), 'flavor_strength+desc') %>">香りの強さ：濃厚な順</option>
</select>
```

これでセレクトボックスで選択した条件でのソート機能が出来上がりました☕️
リファクタリングの余地しかないと思うので、どしどしコメントいただければと思います。

# おわりに🙇🏻‍♂️

### 追加でやりたいこと💭

完成形の時点で対応できておらず、今後改修したい点についてメモを残します。

- ソート後の画面ではセレクトボックスの選択がリセットされるため、デフォルトの「並び替え」が選択されており現在のソート条件がパッと見でわからない。
- 関連先のレコード数（いいね数など）でのソートも実現したい。流し見した感じransackのソートはカラムから判断していそうなので、キャッシュカラム（関連先の数を保存するカラム）が必要そう。

なにか思いついた方はコメントなどでご教授いただけますと泣いて喜びます🙇🏻‍♂️
その他にも間違っている点や補足、タイポなどありましたらお気軽にコメント、編集リクエストいただけますと幸いです！！

# 参考記事📄
https://github.com/activerecord-hackery/ransack

https://www.rubydoc.info/github/ernie/ransack/Ransack/Nodes

https://awesome-linus.com/2019/05/09/select-option-href

### 追記
セレクトボックスの選択肢をリンクにしない場合は、下記のやり方でもっとスマートにできます。

https://qiita.com/akkie/items/9d09d21a0e8ee47a1691

違いとしては
- 【本記事】セレクトボックスで選択　→　ソート結果表示
- 【上記記事】セレクトボックスで選択　→　送信ボタン　→　ソート結果表示

という手順の違いです。
UXの観点になるので議論が分かれそうですが、
この記事を見つけていたらおそらく今回のようなやり方はしなかったと思います。。。