Rails で created_by と updated_by をいい感じにセットしたい

はじめまして!

8月にインゲージに入社したエンジニア1年生の @shutooike です!(保険)

突然ですが、弊社サービスの設定系のテーブルにはその設定を誰が作成・更新したものかを記録するために created_by, updated_by というログインユーザーのIDを入れるカラムがあります。

その created_by, updated_by にログインユーザーIDをセットする処理は、単に save(updated_by: current_user.id) としているところもあれば、before_save などのコールバックでセットしているところもありモデルごとにバラバラでした。

そこらへんいい感じにしてよと @ishiyu に言われたので、負債返却週間 *1で処理の共通化をした話をします!

実装①:コールバック編 🤙

方針before_validation コールバックでログインユーザー(操作ユーザー)のIDをセットする

require 'active_support/concern'
module Concerns::Userstamp
  extend ActiveSupport::Concern
  
  #----------------------------------------
  # Included
  #----------------------------------------
  included do
    # CALLBACKS
    before_validation :set_operator_id

    # VALIDATIONS
    validates :created_by, presence: true
    validates :updated_by, presence: true
  end

  private
  
  def set_operator_id
    self.created_by = operator_id if self.new_record?
    self.updated_by = operator_id 
  end

  def operator_id
    User.current.id # User.current は devise でいうところの `current_user` を呼び出せるようなメソッド
  end
end

この Userstamp モジュールをモデルに include することで、作成・更新時は created_by, updated_by のことを考えなくてよくなりました。

※ コールバックが走らないメソッドは別です。

レビューにて 📝

@shutooike:

できましたレビューお願いします〜

@ishiyu:

お!いい感じだね〜
ただ、access_token のような内部で自動更新する場合は updated_by 変えたくないんだよね〜
save(touch: false) ってしたら updated_at 更新されないからそのノリで使いたい

@shutooike:

たしかに 🦀

実装②:黒魔術編 🔮

方針:ActiveRecord の Timestamp モジュールのメソッドをオーバーライドして save(touch: false)updated_by の更新しないようにする

rails/activerecord/lib/active_record/timestamp.rb github.com

require 'active_support/concern'
module Concerns::Userstamp
  extend ActiveSupport::Concern

  private

  def _create_record
    self.created_by = operator_id
    self.updated_by = operator_id

    super
  end

  # Rails6 では引数がなくなるので、バージョンアップ時に修正する必要あり
  def _update_record(*args, touch: true, **options)
    if touch && self.has_changes_to_save?
      self.updated_by = operator_id
    end

    super(*args, touch: touch, **options)
  end

  def operator_id
    User.current.id # User.current は devise でいうところの `current_user` を呼び出せるようなメソッド
  end

end

private メソッドをオーバーライドするという若干トリッキーなことをしていますが、これで save(touch: false) とした時は updated_at と同様に updated_by を更新しないようになりました!

ちなみにこのコードは Rails 5 系でしか動かないので注意してください。

さいごに

大いなる力には、大いなる責任が伴うことを忘れてはいけない

とりあえずいつものやつ置いときます 😎

他にいいやり方があればぜひ教えてください!

ではまた 🙋‍♂️🙋‍♂️🙋‍♂️

*1:弊社で毎月第1週に行っている名前通り負債を返却する週間