概要
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 コールバック