Ruby元编程之梦中情人method_missing方法详解

2019-09-25 09:42:07于丽


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

权衡利弊

当你有一大堆元方法要定义,又不一定用得到的时候,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 环境还得把它们都放在内存里头。

老虎·伍兹都做不来的事情。

** 正确的 method_missing 使用方式

(译者猥琐地注:要回家了,以下简要摘译)

1、先检查

并不是每次调用都要处理的,你应该先检查一下这次调用是否符合你需要添加的元方法的模式:

def method_missing(method_id, *arguments, &block)
  if method_id.to_s =~ /^what_is_[w]+/
    # do your thing
  end
end

2、包起来

检查好了,确实要处理的,请记得把函数体包在你的好基友,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

3、擦屁股

自己处理不来的方法,可能父类有办法,所以 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

4、昭告天下