LoginSignup
0
0

More than 1 year has passed since last update.

閲覧数を管理する足跡モデルの設計 その2 設計の変更とその実装

Posted at

導入

前回の記事に引き続き、閲覧数管理する足跡モデルの設計を行う。前回からの引き継ぎとして、現状の課題点として「ModelへのSQLの発行が乱立している。」点があげられる。今回はその解決を行う。
最終的な実装は一番最後にまとめてあります。

前回の記事:

課題点

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回目と同一にする事ができると考えられる事から、以下のように変更したいと思う。

:controller/works_controller.rb
@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) 呼び出し後に、足跡が作成または追加されている。create_footprint_byメソッドの位置

@workに代入された後に、さらに足跡が追加されている。つまり、代入されている(表示される)閲覧数と実際の閲覧数に +1 の差が発生してしまう。

(2) joinsにおける内部結合の特徴 = 結合相手がいない行は結合結果から消滅する

参考:

内部結合の挙動では、結合先のテーブルを左テーブル(works)に合わせて複製されます。しかし、結合先が存在しない場合には、結合結果から消滅します。
つまり、足跡オブジェクトが1つも存在しない場合、@work自体がnilになってしまう。
これらの解決策として、仕様を変更する。

課題点(1)の解決策

(1)の閲覧数に +1 の差が発生してしまう問題に関しては、根本的な解決策にはなっていない気もするが、以下のようにして対応する。

:controller/works_controller.rb
@work = Work.select("works.*, SUM(footprints.counts) + 1 as total_footprints_count").joins(:footprints).includes(:user).find(params[:id])
@work.create_footprint_by(current_user)

呼び出す時に、カラムに1を追加した値とすること。これにより、現状の閲覧数と同値の値が代入されることになる。ついでに擬似カラム名も意味が分かるような名前に変更。

課題点(2)の原因

問題はこっちである。この設計自体のミスを見つけた、気がする。
足跡オブジェクトが1つも存在しない場合、@work自体がnilになってしまう問題。
そもそも、現状の足跡オブジェクトの作成(増加ではなく)は全てworks_controller/showメソッドに依存している。そして現状のSQLでは以下のような挙動になる。

作品が閲覧されるタイミングで足跡オブジェクトが作成される。
しかし、足跡オブジェクトが存在しない場合は、作品を呼び出す事ができない。
大きなジレンマが発生している。だったら@workを代入する前に、足跡を作成すれば良いか?

:controller/works_controller.rb
# ここに @work = Work.find.. が必要
@work.create_footprint_by(current_user) 
@work = Work.select("works.*, SUM(footprints.counts) + 1 as total_footprints_count").joins(:footprints).includes(:user).find(params[:id]) # 1回目 SELECT

ただ、そうなると結局一番最初の実装と同じで、SQLが3行になってしまう。
と言うことで、この問題の原因は、足跡オブジェクトの作成されるタイミングに問題があると考えた。

課題点(2)の解決策

そもそも、閲覧数管理がworks_contorollerのshow*のみに*依存している問題があると考えた為、以下のように作品が生成されるタイミングで、足跡オブジェクトを作成するものとする。

その為、モデルのコールバックを用いて足跡オブジェクト作成するものとする。作品作成(works/create)後には、自動的にshowページへリクエストが飛ぶので、作成者本人は一番最初に作品を閲覧するので、user_idは作成者のidを利用する。

:models/work.rb
class Work < ApplicationRecord
  after_create { Footprint.create(user_id: user_id, work_id: id, counts: 0) }
    :
end

こだわりポイント

足跡オブジェクトのデフォルト値は1なのだが、ここでは値を0に指定する。
作品作成後には、自動的にshowページへリクエストが飛ぶ(+1される)ので、この時点での閲覧数は0にし、リクエストのタイミングでデフォルトの1になるようにしている。

また、モデルのコールバックで作成した事により、seedファイルなど作成された場合にも必ず1つの足跡オブジェクトが作成される事になる。

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