Rails Ransack sort_linkが使えない統計カラムなどをソートするヘルパーを作ってみた

事実確認とやりたいこと

Ransackのsort_linkでは統計カラムのソートができない

Railsプロジェクトにおいて検索機能を「Ransack」というgemを使って作成している場合、
sort_link()というヘルパー一発でソート機能を実装できます。
しかし、それは単にModelに存在しているカラムのソートが簡単に実装できるヘルパーであってSUMなどの統計後のデータに対してのソートはできません。

例えば以下のような売上データがあるとします。

salesテーブル

id name price
1 うまい棒 10
2 うまい棒 10
3 よっちゃんイカ 30
4 うまい棒 10
5 うまい棒 10

このテーブルをSUMで統計して売上合計が大きい順にソートしたいです。
期待している出力結果は以下の通りです

name price
うまい棒 40
よっちゃんイカ 30

しかし、sort_link()ではModelに存在しているカラムしか指定できません。
sort_link(@q, :price)では以下のような出力となってしまいます。

name price
よっちゃんイカ 30
うまい棒 40

やりたいこと

sort_link(@q, :total_price)みたいなのができたらベストだと考えます。
ぐぬぬぬ。何かいい方法はないのでしょうか?

ソートするヘルパーを作ってしまえ

「ないのなら作ってしまおう。今後も使うことあると思うし!」
と勢いで作ってみました。
JSまで巻き込んでしまったのは自分でもいい判断だったのかはわからないですが、
こんな感じに実装しました。

Controller

orderでsortパラメーターを使ってソートします。

@q = Sales.select('name, sum(price) as total_price').group(:name).order(params[:sort]).ransack(search_params)
@sales = @q.result

View

テーブルのヘッダーに今回作ったヘルパーtable_sort()を指定します。
検索ありです。
※slimを使っています。

= search_form_for @q, url: sales_path, class: 'search-form' do |f|
  = hidden_field_tag :sort
  = f.search_field :name_cont,
  = f.submit '検索'


table
  thead
    tr
      th = "name"
      th = table_sort('price', 'total_price')
    - @sales.each do |item|
      tr
        th = item.name
        th = item.total_price

Helper

ヘッダーに表示するテキストとソートする対象の名前を引数に、
href="javascript:void(0)"のlink_toを返します。

# テーブルのソートリンクを作成する
def table_sort(text, target)
  order = (params[:sort].present? && params[:sort].include?(target) && params[:sort].include?('asc')) ? 'desc' : 'asc'
  text +=  params[:sort].present? && params[:sort].include?(target) ? params[:sort].include?('asc') ? ' ▲' : ' ▼' : ''
  link_to text, 'javascript:void(0)', class: 'sort-link', data: {colmun: target, order: order}
end

Javascript

今回JSを使用したのは検索条件を引き継ぐためです。
リンクの最後にgetパラメータを全て連ねる方法もありますがスマートじゃないと思いやめました。

$(function() {
    $(document).on("click", ".sort-link", function() {
      $('#sort').val($(this).data('colmun') + ' ' + $(this).data('order'));
      $('.search-form').submit();
    });
});

最後に

ベストプラクティスなのかはわかりませんが、今のところ自分では満足しています。
イイね!と思ったら使ってみてください。