1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

閲覧数を管理する足跡モデルの設計 - その1 DB設計とその実装

Last updated at Posted at 2021-06-10

閲覧数を管理するモデルの設計にあたって

目的:SQLの学習

ポートフォリオサイトの機能拡張とSQLの学習を目的に「この作品を閲覧しているユーザーはこんな作品に興味があります」機能を追加をします。

この機能の追加に向け、どのユーザーがどの作品を閲覧したかを管理する、足跡モデルを作成します。

利用イメージ

  1. ユーザーがログイン(current_user)
  2. ユーザーが作品を閲覧 → リクエスト (works_controllerのshowメソッド)
  3. 新規足跡オブジェクトを追加、または既存の足跡オブジェクトのカウントを+1する
  4. 作品viewを表示

DB設計(関連付けと足跡モデルについて)

テーブル設計は以下のように設定する。

足跡モデル.png

各モデルの相関
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

こだわりポイント

  1. user_idwork_idの組み合わせは一意
  2. あるユーザーによるある作品の閲覧数はcountsで管理する
  3. 足跡オブジェクトが作成された時点でcountsのデフォルト値は1
  4. 閲覧される度に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

足跡作成メソッドのこだわり点

  1. ユーザーと作品の組み合わせが存在するかどうかで条件分岐
  2. 存在する場合にはcounts+1
  3. 存在しない場合には足跡オブジェクト作成

実際の動作に基づき足跡を追加する

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>

課題点

今回の変更で可能になった点

  1. 足跡モデルの作成・関連付け・デフォルト値の設定
  2. 足跡オブジェクトの自動作成(create_footprint_by)
  3. 足跡の表示 
    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つの問題が発生する。

  1. workの呼び出し後に、足跡が作成または追加されている。(create_footprint_byメソッドの位置)
  2. joinsにおける内部結合の特徴 = 結合相手がいない行は結合結果から消滅する

これらの解決や原因などについては次の記事でまとめるものとする。

1
1
2

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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?