概要
ransackで関連するモデルのレコード数による並び替えを実装した時のことを備忘録として記録します。
環境
・ruby '2.5.7'
・rails '5.2.3'
前提
・gem 'ransack'は導入済であること
過程
0.自モデル内での並び替え
まずは、関連するモデルのレコード数による並び替えを実装する前に、自モデル内での並び替えを実装していきます。
対象のモデル
今回は、Datespotモデルでの並び替えを実装していきます。
Datespotモデルには、dateカラムがあるとします。
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を以下のようにします。
<%= 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カラムでソート条件を指定しています。
さらに、selectedにparams[:q][:sorts]を指定することで、検索条件が残すことができます。
コントローラー
コントローラーではフォームから受け取ったparams[:q]を使って検索と並び替えを実行します。
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モデルには、以下のようなカラムがあるとします。
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モデルは以下のようになります。
class Datespot < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :datespot, counter_cache: :comments_count
end
ここで1つ注意が必要です。counter_cacheはbelongs_to宣言で指定する必要があります。
また、実際に数を数えたいカラムは「相手」のモデル(関連付けられているモデル)の方に追加する必要があります。
上記の例では、Datespotモデルの方にcomments_countカラムを追加する必要があります。
comments_countカラムの追加
Datespotモデルにcomments_countカラムを追加しましょう。
class AddCommentsCountToDatespots < ActiveRecord::Migration[5.2]
def change
add_column :datespots, :comments_count, :integer, null: false, default: 0
end
end
rails db:migrateを忘れずに行います。
並び替え条件の追加
あとは、並び替え条件に追加すれば、実装完了です!!
<%= 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クラス(浮動小数点数)で保存しています。
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カラムを追加しましょう。
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メソッドを定義します。
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コールバックを定義します。
after_save :update_rate_avarage
after_destroy :update_rate_avarage
def update_rate_avarage
datespot.update_rate_avarage
end
今回は、after_saveとafter_destroy時に、Datespotモデルのrate_averageカラムが更新されるようにしています。
ちなみに、コールバックとは、オブジェクトのライフサイクル期間における特定の瞬間に呼び出されるメソッドのことです。
コールバックを利用することで、Active Recordオブジェクトが作成/保存/更新/削除/検証/データベースからの読み込み、などのイベント発生時に常に実行されるコードを書くことができます。
④ 並び替え条件の追加
あとは、並び替え条件に追加すれば、実装完了です!!
<%= 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 コールバック