LoginSignup
1
1

More than 3 years have passed since last update.

[Ruby] クラス定義の終了後に特定の処理を行う

Last updated at Posted at 2019-06-21

結論

TracePoint.trace(:end) を使う。

問題

Rails でモデルのバリデーションが少なくとも 1 つは定義されることを保証するためのモジュールを実装したい。実装は次のイメージ。

module WithSomeValidators
  extend ActiveSupport::Concern

  class NoValidatorError < StandardError; end

  included do
    # バリデータが存在しない場合は NoValidatorError を発生させる。
    raise(NoValidatorError) unless validators.present?
  end
end

class Girl < ApplicationRecord
  include WithSomeValidators

  validates :name, presence: true
end

しかし、このコードでは Girl クラスでバリデーションを定義しているにもかかわらず NoValidatorError が発生してしまう。これは include WithSomeValidators を呼んだ時点、つまり validates を呼ぶ前にバリデータの存在を確認してしまうためだ。

class Girl < ApplicationRecord
  include WithSomeValidators # この時点ではバリデータ (Girl.validators) は存在しない。

  validates :name, presence: true # ここでバリデータが追加される。
end

しかし、だからといって

class Girl < ApplicationRecord
  validates :name, presence: true

  include WithSomeValidators
end

のように、バリデーションの定義漏れを避けるためのモジュールをバリデーション定義後に include するのは本末転倒だ。

解決策

Girl クラスの定義が終わった後に Girl.validators を確認できればよい。これを実現するために TracePoint.trace を使い、end イベントをトレースする。

module WithSomeValidators
  extend ActiveSupport::Concern

  class NoValidatorError < StandardError; end

  included do
    TracePoint.trace(:end) do |tp|
      break unless tp.self == self

      tp.disable # トレースを有効にしたままにするとパフォーマンス低下を招く。

      raise(NoValidatorError) unless validators.present?
    end
  end
end

class Girl < ApplicationRecord
  include WithSomeValidators # NoValidatorError は発生しない。

  validates :name, presence: true
end

参考

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