プログラミングスクールの課題で某フリマサイトのコピーサイトを作っていた時のこと。ransackを使って検索フォームを作っていたのですが、以下のようなセレクトボックスを使ったソート機能の作り方が調べてもヒットしませんでした。(ransackのソート機能といえばsort_linkがあるがちょっと違うし・・・)
そこで、この機能を自分なりに実装してみたので紹介します。
いいね!の多い順でのソートを実装しない場合
いいね!の多い順でソートする方法は少し複雑なので先にいいね!以外のパラメーターでソートする方法を説明します。
モデル
今回はItemモデルのオブジェクトをソートします。
itemにはpriceカラムがあるとします。
def change
create_table :items do |t|
t.integer :price, null: false
t.timestamps
end
end
ルーティング
今回はPOSTで検索フォームを送信します。また、検索フォーム以外からのアクセスはGETになるため、GETでもPOSTでも表示できるようにルーティングを設定します。
今回はitemsコントローラーのsearchアクションとしました。
resources :items do
collection do
match 'search' => 'items#search', via: [:get, :post]
end
end
ビュー
検索オブジェクト(今回は@search)のsortsにソート条件を入れてあげればいいので、selectを以下のようにします。ビューの作成にはHamlを採用しています。
= search_form_for @search, url: search_items_path ,html: { method: :post } do |f|
= f.select( :sorts, { '並び替え': 'id desc', '価格の安い順': 'price asc', '価格の高い順': 'price desc', '出品の古い順': 'updated_at asc', '出品の新しい順': 'updated_at desc' } , { selected: params[:q][:sorts] }, { onchange: 'this.form.submit()'} )
# 適宜、検索条件を追加してください
= f.submit '完了'
keyがビューに表示され、valueが@search.sortsに入ります。今回は価格と出品日時でソートしたいので、itemsテーブルにあるpriceとupdated_atでソート条件を指定します。
selectedにparams[:q][:sorts]を指定することで、前回の検索条件を表示することができます。さらに、onchange: 'this.form.submit()'を記述することで、セレクトボックスが選択された時点でフォームを送信します。
コントローラー
コントローラーではフォームから受け取ったparams[:q]を使って検索とソートを実行します。
def search
if params[:q].present?
# 検索フォームからアクセスした時の処理
@search = Item.ransack(search_params)
@items = @search.result
else
# 検索フォーム以外からアクセスした時の処理
params[:q] = { sorts: 'id desc' }
@search = Item.ransack()
@items = Item.all
end
end
def search_params
params.require(:q).permit(:sorts)
# 他のパラメーターもここに入れる
end
ビューでselectedオプションを使って検索条件を引き継いでいるので、params[:q][:sorts]に値が入っていないとエラーになります。そのため、else以下ではid desc
をデフォルトのソート条件として代入しています。
これで、セレクトボックスを使ったソートができるようになりました。
いいね!の多い順にソートする場合
いいね!の多い順でソートする場合は、あるitemに紐づいているlikesのレコード数をカウントしてソートする必要があります。
これはransackのデフォルトの機能にはないので(間違ってたらごめんなさい)、ransackerという拡張機能を使ってソート条件を自作する必要があります。
モデル
以下のようなアソシエーションを定義します。
has_many :likes
belongs_to :item
itemから見ると、itemとlikeにはhas_manyの関係があります。
(いいね!の送り元のUserモデルなどは適宜作成してください)
そして、likesテーブルには以下のようなカラムがあります。
def change
create_table :likes do |t|
t.references :user, null: false, foreign_key: true
t.references :item, null: false, foreign_key: true
t.timestamps
end
end
Itemモデルに以下を追記します。
ransacker :likes_count do
query = '(SELECT COUNT(likes.item_id) FROM likes where likes.item_id = items.id GROUP BY likes.item_id)'
Arel.sql(query)
end
今回はlikesのカウントでソートするlikes_count
という条件を作っています。ransackでは内部的にArelを使用しているので、query
にSQLを入れてそのあとにArelに変換しています。
これで、likes_count
が使えるようになりました。
ビュー
作成したlikes_count
をセレクトボックスに追加します。
= search_form_for @search, url: search_items_path ,html: { method: :post } do |f|
= f.select( :sorts, { '並び替え': 'id desc', '価格の安い順': 'price asc', '価格の高い順': 'price desc', '出品の古い順': 'updated_at asc', '出品の新しい順': 'updated_at desc', 'いいね!の多い順': 'likes_count desc' } , { selected: params[:q][:sorts] }, { onchange: 'this.form.submit()'} )
= f.submit '完了'
これで、いいね!の多い順にソートすることができるようになりました。
終わりに
以上でセレクトボックスを使ったソート方法の紹介を終わります。
今回はビューに選択肢を書いておりファットな表現になってしまっているので別の場所に記述したほうがいいのかなと思いましたが、Rails初心者なためどこに移せばいいかわかりませんでした。
詳しい方教えてください。他にもおかしなところがあれば指摘いただければと思います。
[追記]
あまり選択肢が多くなるのであればactive_hashを使うのが良さそうですね
参考
activerecord-hackery/ransack
Ransackで簡単に検索フォームを作る73のレシピ
Ransack
[Rails]ransackを利用した色々な検索フォーム作成方法まとめ