Ruby on Rails の意味不明な黒魔術を問題視するシリーズ。

問題: 次のメソッド定義において、(*1) や (*2) や (*3) は何をしているんでしょう?

module ActiveRecord module ConnectionAdapters class PostgreSQLColumn < Column private ... def self . string_to_binary (value) if PGconn .respond_to?( :escape_bytea ) self .class.module_eval do define_method( :string_to_binary ) do | value | PGconn .escape_bytea(value) if value end end else self .class.module_eval do define_method( :string_to_binary ) do | value | if value result = '' value.each_byte { | c | result << sprintf( '\\\\ %03o ' , c) } result end end end end self .class.string_to_binary(value) end ... end end end

(activerecord-2.1.0/lib/active_record/connection_adapters/postgresql_adapter.rb より抜粋)

答えは、自己再定義メソッドの定義。

ActiveRecord::ConnectionAdapters::PostgreSQLColumn.string_to_binary() メソッドは、最初に実行されたとき、自分自身を再定義する *1。

class Foo def hello (name) puts " *** 1回目の呼び出し... " self .class.module_eval do define_method( :hello ) do | name | puts " Hello #{ name } ! " end end self .hello(name) puts " *** 終了 " end end foo = Foo .new foo.hello( ' world ' ) foo.hello( ' world ' ) foo.hello( ' world ' )



実行結果:

*** 1回目の呼び出し... Hello world! *** 終了 Hello world! Hello world!



よくこんなこと思いつくよな。最初に考えたやつは頭いい。

でも、これが本当に必要なのかは疑問だ。こう書けば済む話だと思う。

module ActiveRecord module ConnectionAdapters class PostgreSQLColumn < Column private ... if PGconn .respond_to?( :escape_bytea ) def self . string_to_binary (value) PGconn .escape_bytea(value) if value end else def self . string_to_binary (value) if value result = '' value.each_byte { | c | result << sprintf( '\\\\ %03o ' , c) } result end end end ... end end end



どう考えてもこっちのほうがわかりやすい。わざわざ黒魔術を使う意味がわからない。

なんか妥当な理由を思いついた人がいたら教えてください。