Ruby on Railsを弄っている。ActiveRecordで、属性attrに関するフックを、attr関連のメソッドの近くに配置したいのだけれど、よくわからない。
2009-05-08追記: 端っからクラスメソッドbefore_validation
を使ってbefore_validation :test
のように書けるので、この記事は意味がない。ドキュメントにUnlike all the other callbacks, after_find and after_initialize will only be run if an explicit implementation is defined
と書いてあるのをちゃんと読め < 昔の自分
ActiveRecordではbefore_saveとかいくつかのフックポイントが用意されているからこのメソッドをオーバーライドすればDBへの格納やvalidationの途中に何か処理を挟めるわけだ。でも、このやり方だと、各attrへのvalidation処理がbefore_validate1カ所に集中してしまったりして、ぱっと見の、そのattrの性質っていうものが掴みにくいように思うのだ。
これをどうにかする仕組みが無いかどうかちょっと探してみたもののよく分からなかったので、とりあえず、こんなコードを書いてしのいでみた。
#
# enhancement for ActiveRecord::Base
#
class ActiveRecord::Base
%w(
before_validation before_validation_on_create before_validation_on_update
after_validation after_validation_on_create after_validation_on_update
before_save before_create before_update
after_save after_create after_update
).each do |name|
eval <<-EOF, binding, __FILE__, __LINE__
def self.#{name}_hooks
@#{name}_hooks or @#{name}_hooks = []
end
def self.#{name}_hooks=(value)
@#{name}_hooks = value
end
def self.hooks_#{name}(*hook, &proc)
self.#{name}_hooks += hook
self.#{name}_hooks << proc if proc
end
def #{name}
super
self.class.#{name}_hooks.each do |hook|
case hook
when NilClass: # do nothing
when Symbol: self.send(hook)
when String: eval(hook, binding)
when Proc, Method: hook.call()
when UnboundMethod: hook.bind(self).call()
else
raise "for #{name}, the hook \#{hook} must be " +
"an either Symbol, String, Proc, or Method"
end
end
end
EOF
end
def self.validation_hooks
@validation_hooks or @validation_hooks = []
end
def self.validation_hooks=(value)
@validation_hooks = value
end
def self.hooks_validation(attr, message, *hook, &proc)
hook << proc unless proc.nil?
if hook.size != 1
raise ArgumentError, "wrong # of argument: #{hook.size} for 1"
end
self.validation_hooks.push [attr, message, hook[0]]
end
def validate
self.class.validation_hooks.each do |attr, msg, assertion|
unless
case assertion
when Symbol: self.send(assertion)
when String: eval(assertion, binding)
when Proc, Method: assertion.call()
when UnboundMethod: assertion.bind(self).call()
else
raise "for validate(), the hook #{assertion} must be " +
"an either Symbol, String, Proc, or Method"
end
then
errors.add(attr, msg)
end
end
end
end
こんな感じに使用する。
class Test < ActiveRecord::Base
validates_length_of :test, :is => 3, :allow_nil => true
hooks_before_validation %q{ self[:test] = nil if @test.empty? }
def test
... なんか@testがらみの処理
end
def test2
...なんか@test2がらみの処理
end
hooks_validation :test2, "is invalid", %q{ !@test2.valid? }
end
フックを作る時点ではまだクラス定義を評価中であり、アクティブレコードのインスタンスが存在しないので、それをself
とするようなProcを作れなくて、結局文字列として渡してevalさせないといけないっていうのがいまいち使いづらい。
ActiveRecord本体に何か便利な機能は無いんだろうか。もうちょっと探してみる。