LoginSignup
48
51

More than 3 years have passed since last update.

[Rails]ransackでセレクトボックスを使ってソートする

Last updated at Posted at 2019-06-19

プログラミングスクールの課題で某フリマサイトのコピーサイトを作っていた時のこと。ransackを使って検索フォームを作っていたのですが、以下のようなセレクトボックスを使ったソート機能の作り方が調べてもヒットしませんでした。(ransackのソート機能といえばsort_linkがあるがちょっと違うし・・・)
そこで、この機能を自分なりに実装してみたので紹介します。

31045d73bf0b3c2ecdedbf2ac70177d6.gif

いいね!の多い順でのソートを実装しない場合

いいね!の多い順でソートする方法は少し複雑なので先にいいね!以外のパラメーターでソートする方法を説明します。

モデル

今回は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アクションとしました。

routes.rb
  resources :items do
    collection do
      match 'search' => 'items#search', via: [:get, :post]
    end
  end

ビュー

検索オブジェクト(今回は@search)のsortsにソート条件を入れてあげればいいので、selectを以下のようにします。ビューの作成にはHamlを採用しています。

search.html.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]を使って検索とソートを実行します。

items_controller.rb
  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という拡張機能を使ってソート条件を自作する必要があります。

モデル

以下のようなアソシエーションを定義します。

item.rb
  has_many :likes
like.rb
  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モデルに以下を追記します。

item.rb
  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.html.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', 'いいね!の多い順': 'likes_count desc' } , { selected: params[:q][:sorts] }, { onchange: 'this.form.submit()'} )
  = f.submit '完了'

これで、いいね!の多い順にソートすることができるようになりました。

終わりに

以上でセレクトボックスを使ったソート方法の紹介を終わります。
今回はビューに選択肢を書いておりファットな表現になってしまっているので別の場所に記述したほうがいいのかなと思いましたが、Rails初心者なためどこに移せばいいかわかりませんでした。
詳しい方教えてください。他にもおかしなところがあれば指摘いただければと思います。
[追記]
あまり選択肢が多くなるのであればactive_hashを使うのが良さそうですね

参考

activerecord-hackery/ransack
Ransackで簡単に検索フォームを作る73のレシピ
Ransack
[Rails]ransackを利用した色々な検索フォーム作成方法まとめ

48
51
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
48
51