はじめに
impressionist gemを使ってページビューやユニークユーザー数が取得できるんだけど、それをソートしてランキング化したいという要望があると思う。
調べてみると、まずimpressionsテーブルからimpressionable_idを取り出して、今度はそれを対象のテーブルに対してwhereで条件として適用している記事があった。
「impressionist Gemでランキングを作る」
https://tagamidaiki.com/create-ranking-with-impressionist-gem/
けれどこのようなやり方は、あまりいい方法じゃないなという話です。
なぜいい方法じゃないのか
記事ではimpressionの対象はArticleなのでそれを前提として、何が困るのかというと
- MySQLじゃないとFIELD関数がないので困る
- impressionsのcreated_atに条件を指定しても、それは参照された際にimpressionがcreateされた日時なので困る
- おそらく「昨日書かれた記事の中でのランキング」が欲しいと思うんだけど、記事では「impressionがあった日からのランキング」になっている
- つまりArticleのcreated_atによって抽出しないと、過去の記事がランキングに出てくる
- おそらく「昨日書かれた記事の中でのランキング」が欲しいと思うんだけど、記事では「impressionがあった日からのランキング」になっている
- 上と似ている話で、impressionsテーブルからデータを取得する際にArticlesを条件にしたいのでjoinすることになる
解決策
これはimpressionist のwikiに書いてある
https://github.com/charlotte-ruby/impressionist/wiki/How-to-order-records-by-impressionist_count.
これを読めば分かるってなもんだけど、impressionistを知っている状態で、もっと分かりやすくまとめとくと
- 対象とするテーブルに
impressions_count
なカラム作ってintegerにしておく - 対象となるモデルクラスに
:counter_cache
オプション追加 - あとは
impressions_count
に対してorderを使う
対象とするテーブルに impressions_count
なカラムを作ってintegerにしておく
class AddCounterCacheToArticle < ActiveRecord::Migration[5.1]
def change
add_column :articles, :impressions_count, :integer, default: 0
end
end
このカラムはimpressionsテーブルがcreateされるとき、その対象のテーブルのimpressions_countがupdateされるようにしておく。
対象となるモデルクラスに :counter_cache
オプション追加
class Article < ActiveRecord::Base
is_impressionable counter_cache: true
end
これで下記のようなSQLが発行され、結果を集計しそれを加算してimpressions_count
が増える。
INSERT INTO "impressions" ("impressionable_type", "impressionable_id", "user_id", "controller_name", "action_name", "request_hash", "ip_address", "session_hash", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING "id" (省略)
SELECT COUNT(*) FROM "impressions" WHERE "impressions"."impressionable_id" = $1 AND "impressions"."impressionable_type" = $2 [["impressionable_id", 1], ["impressionable_type", "Article"]]
UPDATE "articles" SET "impressions_count" = COALESCE("impressions_count", 0) + 6 WHERE "articles"."id" = $1 [["id", 1]]
(コードを読んでもどこで計算しているのか分からなかったので次のリストはメモ程度)
- 厳密に言うと、元のimpressions_countに対して集計結果との差分を足し合わせている
- 例1: article.impressions_countは0(またはnull)のデータだが集計した結果6なら
+ 6
- 例2: article.impressions_countは1のデータだが集計した結果6なら差分の
+ 5
- 例1: article.impressions_countは0(またはnull)のデータだが集計した結果6なら
これの何が嬉しいかというと、運営していく中でこのカラムを追加しても集計した値が格納されること。
あとは impressions_count に対してorderを使う
@most_viewed = Article.order('impressions_count DESC').take(10)
これで先に述べたように条件を直接Articleに指定すれば良いだけになる。
最初に引用した記事のように日付で条件をつけたいなら次のようになるだろう。
(読みやすさのために改行した)
@most_viewed = Article.order('impressions_count DESC')
.where("? <= created_at", Time.now.yesterday)
.where("created_at <= ?", Time.now).take(10)
その他
impressions_count
のカラム名を変えたい場合なども READMEに書いてある。
参考
- impressionistの導入については、impressionist gemを使ってrailsでページビューをトラッキングするに書いた