Edited at

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


結論

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


参考