はじめに
絶賛ポートフォリオ制作中です!
今回は未ログインのユーザーが投稿詳細画面を表示した時に、
閲覧数をカウントするように実装したためアウトプットします✍️
※旅行やデートのプランを共有するアプリなので、投稿機能はPostではなくPlanを使用しています!
ER図
モデル、マイグレーションファイル作成
モデル作成
$ rails g model ViewCount user:references plan:references
referencesとは(直訳:参照、参考)
名前の通り、作成済みのテーブルを参照する場合に使用します
コマンドを実行すると下記カラムがマイグレーションファイルに自動追加されます
t.references :user, null: false, foreign_key: true
t.references :plan, null: false, foreign_key: true
ここで指定しなくとも、後からマイグレーションファイルに追加可能です!
のちにモデルに記載する「belongs_to」を自動で記載もしてくれます
※「has_many」は追加してくれないので注意⚠️
以下の記事を参照しました!
マイグレーションファイル
足りないところを追加したり、自動入力されている箇所を修正します
class CreateViewCounts < ActiveRecord::Migration[6.1]
def change
create_table :view_counts do |t|
# 初期の記述と変わっているため注意!↓
t.references :user, null: true, foreign_key: true
t.references :plan, null: false, foreign_key: true
t.string :user_or_ip, null: false
t.timestamps
end
# ユーザーと投稿の組み合わせを組合せを一意に保つ為の記述
add_index :view_counts, [:user_or_ip, :plan_id], unique: true
end
end
注意⚠️
userのnull: false
をnull: true
に変更します
通常はnullを許可しませんが、今回は未ログインのユーザーの閲覧数をカウントする必要が(current_userがnilになる可能性が)あるためです!
t.string :user_or_ip, null: false
とadd_index :view_counts, [:user_or_ip, :plan_id], unique: true
を追加しましょう!
忘れないうちにマグレートします!
$ rails db:migrate
アソシエーション
class User < ApplicationRecord
:
has_many :view_counts, dependent: :destroy
:
end
class Plan < ApplicationRecord
:
has_many :view_counts, dependent: :destroy
:
end
class ViewCount < ApplicationRecord
belongs_to :user, optional: true
belongs_to :plan
validates :user, uniqueness: { scope: :plan_id }, if: -> { user.present? }
validates :user_or_ip, uniqueness: { scope: :plan_id }, if: -> { user.nil? }
end
optional: true
とは 🌱
belongs_toの外部キーのnilを許可するという意味です
今回の例で言うと、未ログインユーザーの閲覧数をカウントする場合、user_idがnilになります!このオプションを指定しないとuser_idのnillを許可していないので、エラーが発生してしまいます
このオプションはデフォルトではfalse
になっています
こちらの記事がわかりやすかったです!
バリデーション
ユーザーと投稿の組み合わせが一意であることを確認しています
if: -> { user.present? }
userがnilでない場合はuserとplan_idの組み合わせを確認
if: -> { user.nil? }
userがnilの場合は、user_or_ipとplan_idの組み合わせを確認
コントローラーに記述
class Public::PlansController < ApplicationController
def show
@plan = Plan.find(params[:id])
user_or_ip = current_user ? current_user.id.to_s : request.remote_ip
unless ViewCount.find_by(user_or_ip: user_or_ip, plan: @plan)
ViewCount.create(user_or_ip: user_or_ip, plan: @plan, user: current_user)
end
end
end
user_or_ip = current_user ? current_user.id.to_s : request.remote_ip
三項演算子の構文を使用しています
条件 ? 真の場合の値 : 偽の場合の値
current_user ?
では現在ユーザーがログインしているかどうかを判定しています
真の場合(ユーザーがログインしている場合)
current_user.id.to_s
current_userのidを文字列化し、user_or_ip
に代入します
⚠️次に記載するipアドレスが文字列なので、合わせるためにidを文字列に変換しています!
偽の場合(ユーザーがログインしていない場合)
request.remote_ip
リクエスト元のユーザーのIPアドレスを取得し、user_or_ip
に代入します
初めて知った記述だったため、念の為確認しました↓
unless ViewCount.find_by(user_or_ip: user_or_ip, plan: @plan)
ViewCount.create(user_or_ip: user_or_ip, plan: @plan, user: current_user)
find_byでuser_or_ip
とplan
の組み合わせが既にないか確認し、
なければViewCoutデータを作成します(同じクライアントが複数回閲覧しても閲覧数は1)
viewを記述
さいごに、表示したい場所に以下を記述します
<%= @plan.view_counts.count %>Views
検討するべきポイント
未ログインのユーザーに対してIPアドレスを取得していますが、
同じネットワークを共有する複数のユーザーが同じIPアドレスを使用する場合があります
そのため異なるユーザーの閲覧を区別することができない場合があります
もっといい方法がありましたら教えていただけますと幸いです(これが限界でした…!)
さいごに
閲覧数の記事は沢山あったのですが、未ログインユーザーの閲覧数をカウントする方法が見つからなかったためアウトプットも兼ねて書きました✍️
誤り等ございましたら教えていただけますと幸いです!
参照記事
参考にさせていただきました!ありがとうございます!