4
5

More than 3 years have passed since last update.

【ransack】関連するモデルのレコード数による並び替え

Posted at

概要

ransackで関連するモデルのレコード数による並び替えを実装した時のことを備忘録として記録します。

環境

・ruby '2.5.7'
・rails '5.2.3'

前提

・gem 'ransack'は導入済であること

過程

0.自モデル内での並び替え

まずは、関連するモデルのレコード数による並び替えを実装する前に、自モデル内での並び替えを実装していきます。

対象のモデル

今回は、Datespotモデルでの並び替えを実装していきます。
Datespotモデルには、dateカラムがあるとします。

(抜粋)db/schema.rb
  create_table "datespots", force: :cascade do |t|
    t.integer "user_id", null: false
    t.datetime "date"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

ビュー

検索オブジェクトのsortsに並び替え条件を入れてあげればいいので、selectを以下のようにします。

datespot_search.html.erb
<%= search_form_for @search do |form| %>
  <div class='datespot-search__search-form'> # class名は、適宜変更してください
   # 検索条件は、適宜追加してください
    <%= form.select( :sorts, {'予定日が近い順': 'date asc', '予定日が遠い順': 'date desc', '更新日が新しい順': 'updated_at desc', '更新日が古い順': 'updated_at asc'}, { selected: params[:q][:sorts] }, class: 'datespot-search__search-form--sort-field' ) %>
    <button type="submit" class="datespot-search__search-form--search-button">
      <i class="fas fa-search datespot-search__search-form--search-icon"></i>
    </button>
  </div>
<% end %>

keyがビューに表示され、value@search.sortsに入ります。
今回は予定日と更新日でソートしたいので、Datespotモデルにあるdateカラムupdated_atカラムでソート条件を指定しています。

さらに、selectedparams[:q][:sorts]を指定することで、検索条件が残すことができます。

コントローラー

コントローラーではフォームから受け取ったparams[:q]を使って検索と並び替えを実行します。

app/controllers/datespots_controller.rb
  def index
    # 検索フォームからアクセスした時の処理
    if params[:q].present?
      @search = Datespot.ransack(params[:q])
      @datespots = @search.result
    else
    # 検索フォーム以外からアクセスした時の処理(デフォルトの並び順)
      params[:q] = { sorts: 'date asc' }
      @search = Datespot.ransack(params[:q])
      @datespots = @search.result
    end
  end

ビューでselectedオプションを使って、検索条件を引き継いでいるので、
params[:q][:sorts]に値が入っていないとエラーになります。
そのため、else以下ではdate ascをデフォルトの並び替え条件として代入しています。
ここは、任意で設定してください。

これで、自モデル内での並び替えを実装できました!!

1.関連するモデルのレコード数による並び替え

それでは、関連するモデルのレコード数による並び替えを実装していきます。

対象のモデル

今回は、Datespotモデルと1対多のアソシエーションがあるCommentモデルでの並び替えを実装していきます。
Commentモデルには、以下のようなカラムがあるとします。

(抜粋)db/schema.rb
  create_table "comments", force: :cascade do |t|
    t.integer "user_id", null: false
    t.integer "datespot_id", null: false
    t.text "content", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

実装したいこと

ここでは、各datespotに紐づくコメント数による並び替えを実装していきます。
そのために、関連レコード数を集計するcounter_cacheというRailsの機能を使用します。
counter_cache設定することにより、関連するテーブルのレコード数を簡単にカウントできます。

counter_cacheの追加

DatespotモデルとCommentモデルは以下のようになります。

app/models/datespot.rb
class Datespot < ApplicationRecord
  has_many :comments
end
app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :datespot, counter_cache: :comments_count
end

ここで1つ注意が必要です。counter_cacheはbelongs_to宣言で指定する必要があります。
また、実際に数を数えたいカラムは「相手」のモデル(関連付けられているモデル)の方に追加する必要があります。
上記の例では、Datespotモデルの方にcomments_countカラムを追加する必要があります。

comments_countカラムの追加

Datespotモデルにcomments_countカラムを追加しましょう。

db/migrate/2020××××××××××_add_comments_count_to_datespots.rb
class AddCommentsCountToDatespots < ActiveRecord::Migration[5.2]
  def change
    add_column :datespots, :comments_count, :integer, null: false, default: 0
  end
end

rails db:migrateを忘れずに行います。

並び替え条件の追加

あとは、並び替え条件に追加すれば、実装完了です!!

datespot_search.html.erb
<%= search_form_for @search do |form| %>
  <div class='datespot-search__search-form'> # class名は、適宜変更してください
   # 検索条件は、適宜追加してください
    <%= form.select( :sorts, {(省略), 'コメント数が多い順': 'comments_count desc'}, { selected: params[:q][:sorts] }, class: 'datespot-search__search-form--sort-field' ) %>
    <button type="submit" class="datespot-search__search-form--search-button">
      <i class="fas fa-search datespot-search__search-form--search-icon"></i>
    </button>
  </div>
<% end %>

これで、各datespotに紐づくコメント数による並び替えを実装できました!!

2.関連するモデルのレコード数による並び替え(応用)

この考えを応用して、各datespotに紐づく評価の平均値による並び替えを実装していきます。

対象のモデル

同様に、Datespotモデルと1対多のアソシエーションがあるCommentモデルでの並び替えです。
Commentモデルにあるrateカラムが評価の値をFloatクラス(浮動小数点数)で保存しています。

(抜粋)db/schema.rb
  create_table "comments", force: :cascade do |t|
    t.integer "user_id", null: false
    t.integer "datespot_id", null: false
    t.text "content", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.float "rate", default: 0.0, null: false # 評価の値を保存している
  end

実装したいこと

上記したように、各datespotに紐づく評価の平均値による並び替えを実装していきます。
そのために、必要な手順を分解すると次のような手順になります。

① Datespotモデルにrate_averageカラムを追加する
② Commentモデルのrateカラムの平均値を取って、Datespotモデルのrate_averageカラムを更新する
③ Commentモデルのrateカラムが追加される度に、Datespotモデルのrate_averageカラムが更新されるようにする
④ 並び替え条件の追加

それでは、1つ1つ見ていきましょう!

① Datespotモデルにrate_averageカラムを追加する

Datespotモデルにrate_averageカラムを追加しましょう。

db/migrate/2020××××××××××_add_rate_average_to_datespots.rb
class AddRateAverageToDatespots < ActiveRecord::Migration[5.2]
  def change
    add_column :datespots, :rate_average, :float, default: 0.0
  end
end

rails db:migrateを忘れずに行います。

② Commentモデルのrateカラムの平均値を取って、Datespotモデルのrate_averageカラムを更新する

Datespotモデルにupdate_rate_avarageメソッドを定義します。

app/models/datespot.rb
  def update_rate_avarage
    comments_average = comments.average(:rate)
    update(rate_average: comments_average)
  end

comments.average(:rate)で、Commentモデルのrateカラムの平均値を取って、それを変数comments_averageに代入します。

そして、update(rate_average: comments_average)で、Datespotモデルのrate_averageカラムを更新します。

ちなみに、update(rate_average: comments_average)は、self.update(rate_average: comments_average)を省略したものです。

③ Commentモデルのrateカラムが追加される度に、Datespotモデルのrate_averageカラムが更新されるようにする

あとは、CommentモデルにActive Recordコールバックを定義します。

app/models/comment.rb
  after_save :update_rate_avarage
  after_destroy :update_rate_avarage

  def update_rate_avarage
    datespot.update_rate_avarage
  end

今回は、after_saveafter_destroy時に、Datespotモデルのrate_averageカラムが更新されるようにしています。

ちなみに、コールバックとは、オブジェクトのライフサイクル期間における特定の瞬間に呼び出されるメソッドのことです。
コールバックを利用することで、Active Recordオブジェクトが作成/保存/更新/削除/検証/データベースからの読み込み、などのイベント発生時に常に実行されるコードを書くことができます。

④ 並び替え条件の追加

あとは、並び替え条件に追加すれば、実装完了です!!

datespot_search.html.erb
<%= search_form_for @search do |form| %>
  <div class='datespot-search__search-form'> # class名は、適宜変更してください
   # 検索条件は、適宜追加してください
    <%= form.select( :sorts, {(省略), '評価が高い順': 'rate_average desc'}, { selected: params[:q][:sorts] }, class: 'datespot-search__search-form--sort-field' ) %>
    <button type="submit" class="datespot-search__search-form--search-button">
      <i class="fas fa-search datespot-search__search-form--search-icon"></i>
    </button>
  </div>
<% end %>

これで、各datespotに紐づく評価の平均値による並び替えを実装できました!!

参考

[Rails]ransackでセレクトボックスを使ってソートする
Railsガイド Active Record の関連付け
Railsガイド Active Record コールバック

4
5
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
4
5