[url=http://railsmagazine.com/articles/4]ruby delegation library[/url] 和 [url=http://www.simonecarletti.com/blog/2009/12/inside-ruby-on-rails-delegate/]Rails ActiveRecord delegation[/url]。
好,我们总结一下,看看 define_method 的真正威力。
修改自 ruby-doc.org 上的 [url=http://ruby-doc.org/core/classes/Module.html#M001654]例子[/url]
class A
def fred
puts "In Fred"
end
def create_method(name, &block)
self.class.send(:define_method, name, &block)
end
define_method(:wilma) { puts "Charge it!" }
end
class B < A
define_method(:barney, instance_method(:fred))
end
a = B.new
a.barney #=> In Fred
a.wilma #=> Charge it!
a.create_method(:betty) { p self.to_s }
a.betty #=> B
[b]什么时候用 method_missing?[/b]
现在你估计在想,总有该用它的时候吧,不然还要它干嘛?没错。
[b]动态命名的方法(又名,元方法)[/b]
案例:我要依据某种模式提供一组方法。这些方法做的事情顾名思义。我可能从来没有调用过这些可能的方法,但是等我要用的时候,它们必须可用。
现在才是人话!这其实正是 ActiveRecord 所采用的方式,为你提供那些基于属性的动态构建的查找方法,比如 find_by_login_and_email(user_login, user_email)。
def method_missing(method_id, *arguments, &block)
if match = DynamicFinderMatch.match(method_id)
attribute_names = match.attribute_names
super unless all_attributes_exists?(attribute_names)
if match.finder?
# ...you get the point
end # my OCD makes me unable to omit this
# ...
else
super # this is important, I'll tell you why in a second
end
end
[b]权衡利弊[/b]
当你有一大堆元方法要定义,又不一定用得到的时候,method_missing 是个完美的折衷。
想想 ActiveRecord 中基于属性的查找方法。要用 define_method 从头到脚定义这些方法,ActiveRecord 需要检查每个模型的表中所有的字段,并为每个可能的字段组合方式都定义方法。
find_by_email
find_by_login
find_by_name
find_by_id
find_by_email_and_login
find_by_email_and_login_and_name
find_by_email_and_name
# ...
假如你的模型有 10 个字段,那就是 10! (362880)个查找方法需要定义。想象一下,在你的 Rails 项目跑起来的时候,有这么多个方法需要一次定义掉,而 ruby 环境还得把它们都放在内存里头。
老虎·伍兹都做不来的事情。
[b]** 正确的 method_missing 使用方式[/b]
(译者猥琐地注:要回家了,以下简要摘译)
[b]1、先检查[/b]
并不是每次调用都要处理的,你应该先检查一下这次调用是否符合你需要添加的元方法的模式:
def method_missing(method_id, *arguments, &block)
if method_id.to_s =~ /^what_is_[w]+/
# do your thing
end
end
[b]2、包起来[/b]
检查好了,确实要处理的,请记得把函数体包在你的好基友,define_method 里面。如此,下次就不用找情妇了:
def method_missing(method_id, *arguments, &block)
if method_id.to_s =~ /^what_is_[w]+/
self.class.send :define_method, method_id do
# do your thing
end
self.send(method_id)
end
end
[b]3、擦屁股[/b]
自己处理不来的方法,可能父类有办法,所以 super 一下:
def method_missing(method_id, *arguments, &block)
if method_id.to_s =~ /^what_is_[w]+/
self.class.send :define_method, method_id do
# do your thing
end
self.send(method_id)
else
super
end
end
[b]4、昭告天下[/b]
def respond_to?(method_id, include_private = false)
if method_id.to_s =~ /^what_is_[w]+/
true
else
super
end
end
要告诉别人,你的类虽然暂时还没有这个方法,但是其实是能够响应这方法的。
[b]** 总结 **[/b]
在每个 Ruby 程序员的生活中,这仨方法扮演了重要的角色。define_method 是你的好基友,method_missing 是个如胶似漆但也需相敬如宾的情妇,而 respond_to? 则是你的爱子,如此无虞。