ImpressionistでPVランキング機能を作った時のメモ

# 概要
インプレッション（ブラウザに表示を要求する）を記録してくれるGem「Impressionist」を利用し、
ip_addressでビュー（インプレッションの数）をカウント、
ビューが多い順でランキング表示する機能を作成した際のメモ

※MVCをある程度理解したぐらいの初心者ですので、
　間違いなどあったらご指摘いただければ幸いです…。

# 環境

- Ruby 2.5.3p105 revision 65156
- Rails 5.2.2
- Gem : [Impressionist v1.6.1](https://github.com/charlotte-ruby/impressionist)

# Impressionのインストール

まずは、impressionistのGitHubで説明されている通り、
railsアプリにGemをインストールする。

```cmd:Gemfile
gem 'impressionist'
```
```cmd:ターミナル
$ bundle install
```

###マイグレーションファイルのジェネレート
```cmd:ターミナル
$ rails g impressionist
```
###インプレッションテーブルの作成
```cmd:ターミナル
$ rails db:migrate
```

###インプレッションテーブルの内容
```
t.string   "impressionable_type"  # モデル名: Widget
t.integer  "impressionable_id"    # モデルのインスタンスID: @widget.id
t.integer  "user_id"              # インプレッション（ブラウザに表示を要求）したユーザーID: @current_user.id
t.string   "controller_name"      # コントローラー名の記録
t.string   "action_name"          # アクション名の記録
t.string   "view_name"            # 今回は使わないので気にしない
t.string   "request_hash"         # unique ID per request, in case you want to log multiple impressions and group them
t.string   "session_hash"         # logs the rails session
t.string   "ip_address"           # request.remote_ip, ブラウザに表示要求してきたグローバルIPアドレス
t.text     "params"               # request.params, アクション名、コントローラー名、リソースIDを除いたparameters
t.string   "referrer"             # インプレッションが記録される直前にいたページ
t.string   "message"              # 使わないので気にしない
t.datetime "created_at"           # インプレッション記録日時
t.datetime "updated_at"           # 使わないので気にしない（というかインプレッションをアップデートする事ってあるんだろうか？）
```
#モデルの設定
impressionistの機能を利用したいモデルに対して、``is_impressionable``を記載します。
また、Showアクションで表示したビューページにPV数を表示したいので、
``counter_cache: true``オプションを追加します。

```Ruby:imagepost.rb
class Imagepost < ApplicationRecord  
  is_impressionable counter_cache: true
end
```

###counter_cache オプションを利用するための設定
counter_cacheオプションを利用する為に、対象のモデルに以下のようにカラムを追加します。

```cmd:ターミナル
$ rails g migration AddImpressionsCountToImagepost impressions_count:integer
$ rails db:migrate
```

```ruby:マイグレーションファイル
class AddImpressionsCountToImagepost < ActiveRecord::Migration[5.2]
  def change
    add_column :imageposts, :impressions_count, :int, null: false, default: 0
  end
end
```
※基本的にカラム名は、``テーブル名_count``にする。
　``カラム名_count``以外のカラム名を指定する場合は、モデル側の``is_impressionable``の
　カウンターキャッシュオプションを``counter_cache: true``から、
　``counter_cache: カラム名``に変更する必要があるので注意！

#コントローラーの設定
インプレッションを記録したいモデルクラスのコントローラーに``impressionist``を記述します。
※``impressionist``だけを記述した場合は、コントローラーアクションの全てが記録されます。

今回は記録したいインプレッションがShowアクションだけなので、``:actions``オプションを``[:show]``に設定します。
また、IPアドレスでPV数をカウントするのですが、同じイメージポストに対し、同じIPアドレスからのインプレッションを記録すると、
レコードが大量になってしまうので、``:unique``オプションを``[:impressionable_id, :ip_address]``に設定し、
同じイメージポストに同じIPアドレスでアクセスした場合は、インプレッションを記録しないようにします。

``:unique => [:ip_address]``を記載します。``

```ruby:imageposts_controller.rb
class ImagepostsController < ApplicationController
  impressionist :actions => [:show], :unique => [:impressionable_id, :ip_address]
```

記録したインプレッションからPV数を取得するのですが、
``counter_cach: true``を設定しているので、以下のように記述する事でPV数を取得できます。


```ruby:imageposts_controller.rb
  def show
    @imagepost = Imagepost.find_by(id: params[:id])
    @views = @imagepost.impressions.size #PV数を取得
  end
```

PV数の取得はこれで完了です。
次に、計測したPV数をもとにランキング機能を追加していきます。
今回はランキング用のページにランク上位3位のイメージポストを表示したいので、
ルーティングから設定します。

#ルーティングの設定
ランキングページを独立させたい為、以下のようにルーティングします。

```ruby:routes.rb
  get 'pv_ranking', to: 'imageposts#pv_ranking'
```

#コントローラにアクションを追加
ルーティングで指定したコントローラに、アクションを定義します。
@pv_ranking部分の説明に関しては、[@hitochan様](https://qiita.com/hitochan)の記事[【Rails】ランキング機能の実装](https://qiita.com/hitochan/items/258215ec62ead338c2eb)で詳しく解説されている為、
ぜひこちらをご参照ください。

```ruby:imageposts_controller.rb
  def pv_ranking
    @pv_ranking = Imagepost.find(Impression.group(:impressionable_id).order('count(impressionable_id) desc').limit(3).pluck(:impressionable_id))
  end
```

これでランキング機能の実装は完了です。

#ランキング機能を実際に実装してみて得た知見
PV数のカウントについて、セッションとIPアドレスのどちらで計測するかですが、
Impressionistの解説ではセッションでカウントする場合の注意として、以下の通り言及されています。

[Impressionist  README.mdより抜粋](https://github.com/charlotte-ruby/impressionist)　※翻訳はGoogle先生
>This may be more desirable than filtering by IP address depending on your situation,
>IPによるフィルタリングは同じIPを使用する訪問者を無視する場合があるため、 
>since filtering by IP may >ignore visitors that use the same IP.
>状況によってはIPアドレスによるフィルタリングよりも望ましい場合があります。
>The downside to this filtering is that a user could clear session data in their browser and skew the results.
>このフィルタリングの欠点は、ユーザーがブラウザのセッションデータを消去して結果を歪める可能性があることです

#自分なりの解釈
ランキング機能を作るにあたって、いろいろと脱線していった感が否めないですが、
機能を実装するうえで考慮しておかなければならない部分を知れたのは、大きなメリットでした。

以下自分なりの解釈なので間違ってる可能性があるかもです…（間違ってたらご指摘いただければ幸いです…）

###IPアドレスでPV数をカウントする場合
【メリット】
・動的IPアドレスは※ISPによって割り当てられることから、ユーザーが任意に変更する事ができないため、
　セッションハッシュのようにアカウントを変えて意図的にPV数を増やしたりすることができない
　※ISP => インターネットサービスプロバイダ（so-netとかbb.excite）

【デメリット】
・グローバルIPアドレスはISP次第で変更される可能性がある為、
　もしかしたら過去に記録されたIPアドレスが、当時記録したユーザーとは異なるユーザーに割り当てらていた場合、
　実際にユーザーが見たPV数と、記録されたPV数に誤差が生じる

###セッションハッシュでPV数をカウントする場合
【メリット】
・過去に記録されたIPアドレスが、当時記録したユーザーとは異なるユーザーに割り当てらていた場合でも、
　セッション毎での計測であれば、正しいPV数が計測可能

【デメリット】
・アカウントを大量に作成するなどして、セッションを変えてしまえば、
　PV数の不正に底上げできてしまう可能性がある