blog.waterlow.work

Ruby, Rails, js, etc...

【パーフェクトRails】コールバックをクラスに分離する

目的

パーフェクトRuby on Railsの9章の9−2「複雑なバリデーションとコールバックを整理する」を読んでいて コールバックをクラスに分離する場面ややり方がよくわからなかったのでまとめる。

パーフェクト Ruby on Rails

パーフェクト Ruby on Rails

はじめにさらっと今回の主張だけ述べて、本編に突入します。

コールバックをクラスに分離するのは必要?

  1. あってもいいとは思うけど、作る機会は少ないかも。
    作る場合はapp/modelsapp/servicesに入れておけばいいかな

「コールバックをクラスに分離する」とは何か?

Railsのドキュメントにあったソースをそのまま載せてしまいます。

class BankAccount < ActiveRecord::Base
  before_save      EncryptionWrapper.new("credit_card_number")
  after_save       EncryptionWrapper.new("credit_card_number")
  after_initialize EncryptionWrapper.new("credit_card_number")
end

class EncryptionWrapper
  def initialize(attribute)
    @attribute = attribute
  end

  def before_save(record)
    record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
  end

  def after_save(record)
    record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
  end

  alias_method :after_initialize, :after_save

  private
    def encrypt(value)
      # Secrecy is committed
    end

    def decrypt(value)
      # Secrecy is unveiled
    end
end

before_save EncryptionWrapper.new("credit_card_number")って書くとbefore_saveのときにEncryptionWrapper.new("credit_card_number").before_save(self)が呼ばれます。
つまり、渡されたオブジェクトに対してbefore_saveを委譲できるという機能というかapiです。

みんなやっているのか?

ひとまず参考資料をさがしてみることに。
ActiveRecord::Callbacks
9.2 Callbacks | Advanced Active Record in Rails 4 | InformIT
てめえらのRailsはオブジェクト指向じゃねえ!まずはCallbackクラス、Validatorクラスを活用しろ! - Qiita
ruby - Rails: monkey-patching ActiveRecord::Base vs creating a Module - Stack Overflow

以下の、オープンソースrailsプロジェクトがまとまっているサイトがあるのですが、コールバック用クラスの実装にであえませんでした。
Open Source Rails

GitLabで似たようなことをやっているようなソースも有りましたが、細かいところまでは読めませんでした。

今後どうしていくか

コールバックを別クラスに書くことは悪くはないのだが、そもそもコールバックの処理が肥大化していきそうで、あんまり良くないかなと思っている(ユーザ側に関係ない処理であれば良い)。
加えて、既存のrailsプロジェクトであまり行われていなさそうということも今回の調査でわかった。
よって、app/callbacksみたいなディレクトリは作らず、もしコールバックの処理が複数のクラスで共通になったり、肥大化していった場合には、いったんapp/modelsに作る。