LoginSignup
26
30

More than 5 years have passed since last update.

impressionist gemを使ってrailsでページビューのランキングを作る

Last updated at Posted at 2017-11-18

はじめに

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によって抽出しないと、過去の記事がランキングに出てくる
  • 上と似ている話で、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

これの何が嬉しいかというと、運営していく中でこのカラムを追加しても集計した値が格納されること。

あとは 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に書いてある。

参考

26
30
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
26
30