classをカスタマイズするために再定義じゃなくてaliasしないとだめな場合がある話
当たり前といえば当たり前なaliasの挙動の話です。
わかってたはずなのに、とある状況だと気づかなかったのでメモります。
今回はまった部分は以下の部分の修正
・railsでdb保存した場合に自動でcreated_at列に現在の時間が入る機能。
これををレガシーDBの列名がdt_creationなのでそれに対応したいと思いました。
(列名に型のprefix名がついちゃってるとか本当かんべんしてー)
オリジナルのコード
module ActiveRecord module Timestamp def self.included(base) #:nodoc: base.alias_method_chain :create, :timestamps --- 省略 --- end --- 省略 --- private def create_with_timestamps #:nodoc: if record_timestamps current_time = current_time_from_proper_timezone write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil? write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil? --- 省略 --- end create_without_timestamps end --- 省略 --- end end
これを下記のようにcreate_with_timestampsメソッドを再定義しても動きません。
再定義ver(NG)
module ActiveRecord module Timestamp def create_with_timestamps #:nodoc: if record_timestamps current_time = current_time_from_proper_timezone write_attribute('dt_creation', current_time) if respond_to?(:dt_creation) && dt_creation.nil? end end end end
aliasで元のcreateを上書きすればOK
alias ver(OK)
module ActiveRecord class Base def create_with_timestamps_regacy #:nodoc: current_time = current_time_from_proper_timezone write_attribute('dt_creation', current_time) if respond_to?(:dt_creation) && dt_creation.nil? create_without_timestamps_regacy end alias_method_chain :create, :timestamps_regacy end end
これはaliasした場合、古いメソッド名のほうを再定義しても新しいメソッド名には反映されないからです。
簡単なtestコード
def test "test" end def test_new "test_new" end alias test test_new def test_new "test_new_override" end p test # test_newと表示(test_newの再定義は反映されない)
よくよく考えるといつもよくやる下記の動作ができてるってことはそりゃそうだと思った。
http://www.ruby-lang.org/ja/man/html/_A5AFA5E9A5B9A1BFA5E1A5BDA5C3A5C9A4CEC4EAB5C1.html#alias
# メソッド foo を定義 def foo "foo" end # 別名を設定(メソッド定義の待避) alias :_orig_foo :foo # foo を再定義(元の定義を利用) def foo _orig_foo * 2 end p foo # => "foofoo"
もし、古いメソッド名のほうを再定義して新しいメソッドにも影響あったら上記のやりたいことできないもんね。
aliasの内部的な仕組みを想像するとメソッド定義のコピーであってメソッドのポインタをコピーしてるわけではなさそう。
もしくは再定義するとオリジナルのメソッドポインタとは違う、別のメソッドポインタで作られているのかもしれない。
まとめ
カスタマイズするには再定義ではだめな場合がある(aliasされて実装されてる機能の場合)
目には目を、aliasにはaliasをw