メソッドを動的に定義する
ふと、メソッドを動的に定義したいなと思った。
find_record_by_id :diary
とかくと、以下のようなメソッドが定義されるイメージ。
def find_diary_by_id(id) if result = Diary.find_by_id(id) result else render :text => "無効な操作です。" false end end
ApplicationControllerに次のようなメソッドを書けばいいけど、今回は練習のため。
def find_record_by_id(id,klass) if result = klass.find_by_id(id) result else render :text => "無効な操作です。" false end end
動的にメソッドを定義する
まず最初に思ったのが、evalを使えばいいんじゃないかということ。
class Fuga class << self def method_define eval("def fuga;1;end") end end end Fuga.method_define #=> nil Fuga.new.fuga #=>NoMethodError: undefined method `fuga' for from (irb):11 irb(main):012:0> Fuga.fuga #=> 1
だめだった。クラスメソッドになってしまう。クラスメソッド内で、evalしたんだから当然か。もともとのFugaクラスにメソッドが追加されずに、特異クラス(Singletonクラス)にメソッドが追加されるから、クラスメソッドになるのか。
instance_evalはどうかな
class Hoge class << self def method_define instance_eval("def hoge;1;end") end end end Hoge.method_define #=> nil Hoge.hoge #=> 1
同じか。リファレンスを見てみると、次の様に書いてある。
- instance_eval {|obj| ... }
オブジェクトのコンテキストで文字列 expr を評価してその結果を返します。
ブロックが与えられた場合にはそのブロックをオブジェクトのコンテキストで評価してその結果を返します。ブロックの引数 obj には self が渡されます。
オブジェクトのコンテキストで評価するとは self をそのオブジェクトにして実行するということです。また、文字列/ブロック中でメソッドを定義すれば self の特異メソッドが定義されます。
オブジェクトのコンテキストで評価するとは self を・・・のところから、上記の例の場合でも、クラスメソッドになってしまう。本当は、selfはHogeクラスでなく、Hogeクラスのインスタンスになって欲しい。つまり、Hogeクラスのコンテキストでevalが実行されて欲しい。(ここら辺が少しややこしい。まだ完全に理解してないorz)
class_evalで試してみる
リファレンスを見てみると。
- module_eval
モジュールのコンテキストで文字列 expr を評価してその結果を返します。
モジュールのコンテキストで評価するとは、実行中そのモジュールが self になるということです。つまり、そのモジュールの定義文の中にあるかのように実行されます。
これだ。
class Fuga class << self def method_define class_eval("def fuga;1;end") end end end Fuga.method_define Fuga.fuga #=>NoMethodError: undefined method `fuga' for Fuga:Class from (irb):9 Fuga.new.fuga #=>1
find_record_by_id
実際のRailsのコードではないが、こんな感じになる。
class Diary class << self def find_by_id(id) "Return Diary Record id=#{id}" end end end class ApplicationController class << self def find_record_by_id(*args) args.each do |arg| arg = arg.to_s class_eval <<-METHOD def find_#{arg}_by_id(id) #{arg.capitalize}.find_by_id(id) end METHOD end end end end class DiaryController < ApplicationController find_record_by_id :diary def show diary = find_diary_by_id(10) end end p DiaryController.new.show #=>"Return Diary Record id=10"
define_methodっていうメソッドがあった(i _ i)
これを使って、上記のApplicationController::find_record_by_idを書いてみる。
def find_record_by_id(*args) args.each do |arg| arg = arg.to_s define_method("find_#{arg}_by_id") do |id| eval("#{arg.capitalize}").find_by_id(id) end end end
ん。逆にややこしい気がする(笑)