閲覧数を管理するモデルの設計にあたって
目的:SQLの学習
ポートフォリオサイトの機能拡張とSQLの学習を目的に「この作品を閲覧しているユーザーはこんな作品に興味があります」機能を追加をします。
この機能の追加に向け、どのユーザーがどの作品を閲覧したかを管理する、足跡モデルを作成します。
利用イメージ
- ユーザーがログイン(current_user)
- ユーザーが作品を閲覧 → リクエスト (works_controllerのshowメソッド)
- 新規足跡オブジェクトを追加、または既存の足跡オブジェクトのカウントを+1する
- 作品viewを表示
DB設計(関連付けと足跡モデルについて)
テーブル設計は以下のように設定する。
各モデルの相関
class User < ApplicationRecord
has_many :works
has_many :footprints, dependent: :destroy
end
class Work < ApplicationRecord
belongs_to :user
has_many :footprints
end
class Footprint < ApplicationRecord
belongs_to :user
belongs_to :work
validates :user_id, presence: true, uniqueness: { scope: :work_id }
validates :work_id, presence: true
validates :counts, presence: true
end
Footprintモデルの各カラム
create_table "footprints", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "counts", default: 1, null: false
t.bigint "user_id", null: false
t.bigint "work_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["user_id", "work_id"], name: "index_footprints_on_user_id_and_work_id", unique: true
t.index ["user_id"], name: "index_footprints_on_user_id"
t.index ["work_id"], name: "index_footprints_on_work_id"
end
こだわりポイント
-
user_id
とwork_id
の組み合わせは一意 - あるユーザーによるある作品の閲覧数は
counts
で管理する - 足跡オブジェクトが作成された時点で
counts
のデフォルト値は1 - 閲覧される度に
counts
に+1する
足跡作成メソッド
models/work.rb
class Work < ApplicationRecord
:
def create_footprint_by(user)
if Footprint.find_by(user_id: user.id, work_id: id).present?
footprint = Footprint.find_by(user_id: user.id, work_id: id)
counts = footprint.counts
footprint.update_attribute(:counts, counts + 1)
else
Footprint.create(user_id: user.id, work_id: id)
end
:
end
足跡作成メソッドのこだわり点
- ユーザーと作品の組み合わせが存在するかどうかで条件分岐
- 存在する場合には
counts
+1 - 存在しない場合には足跡オブジェクト作成
実際の動作に基づき足跡を追加する
controller/works_controller.rb
class WorksController < ApplicationController
:
def show
@work = Work.includes(:user).find(params[:id])
@work.create_footprint_by(current_user)
@footprints = Footprint.select("SUM(footprints.counts) as total").find_by(work_id: @work.id)
end
:
end
views/works/show.html.erb
<div class="work__footprints">
<i class="far fa-eye"></i><%= @footprints.total %>
</div>
課題点
今回の変更で可能になった点
- 足跡モデルの作成・関連付け・デフォルト値の設定
- 足跡オブジェクトの自動作成(create_footprint_by)
- 足跡の表示
Footprint.select("SUM(footprints.counts) as total").find_by(work_id: @work.id)
これらの設定により、実際の流れに基づき足跡を作成する事ができた。
一方で課題点
1. ModelへのSQLの発行が乱立している。
controller/works_controller.rb
def show
@work = Work.includes(:user).find(params[:id]) # 1回目 SELECT
@work.create_footprint_by(current_user) # 2回目 UPDATE
@footprints = Footprint.select("SUM(footprints.counts) as total").find_by(work_id: @work.id) # 3回目 SELECT
end
コントローラーにて、個別のSQLへのリクエストが3度も行われてしまっている。3回目のSQLに関しては、1回目と同一にする事ができると考えられる事から、以下のように変更する。
@work = Work.select("works.*, SUM(footprints.counts) as total").joins(:footprints).includes(:user).find(params[:id]) # 1回目 SELECT
@work.create_footprint_by(current_user) # 2回目 UPDATE
しかしこの場合には、2つの問題が発生する。
- workの呼び出し後に、足跡が作成または追加されている。(create_footprint_byメソッドの位置)
- joinsにおける内部結合の特徴 = 結合相手がいない行は結合結果から消滅する
これらの解決や原因などについては次の記事でまとめるものとする。