在Ruby中使用method_missing陷阱

时间:2008-11-14 19:39:17

标签: ruby metaprogramming

在Ruby中定义method_missing方法时有什么要注意的吗?我想知道是否存在一些来自继承,异常抛出,性能或其他任何东西的不那么明显的交互。

6 个答案:

答案 0 :(得分:59)

一个显而易见的问题:如果您重新定义respond_to?,请务必重新定义method_missing。如果method_missing(:sym)有效,则respond_to?(:sym)应始终返回true。有许多图书馆依赖于此。

随后:

一个例子:

# Wrap a Foo; don't expose the internal guts.
# Pass any method that starts with 'a' on to the
# Foo.
class FooWrapper
  def initialize(foo)
    @foo = foo
  end
  def some_method_that_doesnt_start_with_a
    'bar'
  end
  def a_method_that_does_start_with_a
    'baz'
  end
  def respond_to?(sym, include_private = false)
    pass_sym_to_foo?(sym) || super(sym, include_private)
  end
  def method_missing(sym, *args, &block)
    return foo.call(sym, *args, &block) if pass_sym_to_foo?(sym)
    super(sym, *args, &block)
  end
  private
  def pass_sym_to_foo?(sym)
    sym.to_s =~ /^a/ && @foo.respond_to?(sym)
  end
end

class Foo
  def argh
    'argh'
  end
  def blech
    'blech'
  end
end

w = FooWrapper.new(Foo.new)

w.respond_to?(:some_method_that_doesnt_start_with_a)
# => true
w.some_method_that_doesnt_start_with_a
# => 'bar'

w.respond_to?(:a_method_that_does_start_with_a)
# => true
w.a_method_that_does_start_with_a
# => 'baz'

w.respond_to?(:argh)
# => true
w.argh
# => 'argh'

w.respond_to?(:blech)
# => false
w.blech
# NoMethodError

w.respond_to?(:glem!)
# => false
w.glem!
# NoMethodError

w.respond_to?(:apples?)
w.apples?
# NoMethodError

答案 1 :(得分:13)

如果你的方法缺失方法只是寻找某些方法名称,如果你没有找到你正在寻找的东西,不要忘记调用super,以便其他方法缺失可以做他们的事情。

答案 2 :(得分:11)

如果可以预测方法名称,最好动态声明它们而不是依赖method_missing,因为method_missing会导致性能损失。例如,假设您希望扩展数据库句柄,以便能够使用以下语法访问数据库视图:

selected_view_rows = @dbh.viewname( :column => value, ... )

您可以提前确定数据库中的所有视图,然后迭代它们以创建“viewname”方法,而不是依赖于数据库句柄上的method_missing并将方法名称作为视图名称分派给数据库。在@dbh。

答案 3 :(得分:5)

构建于Pistos's pointmethod_missing至少比我尝试过的所有Ruby实现上的常规方法调用慢一个数量级。他有可能在可能的情况下避免拨打method_missing

如果您喜欢冒险,请查看Ruby鲜为人知的Delegator课程。

答案 4 :(得分:0)

另一个问题:

method_missingobj.call_methodobj.send(:call_method)之间的行为有所不同。基本上前者错过了所有私有和非定义的方法,而后来的方法并没有错过私有方法。

当有人通过method_missing呼叫您的私人方法时,send永远不会抓住电话。

答案 5 :(得分:0)

詹姆斯的答案很棒但是,在现代红宝石(1.9+)中,就像Marc-André所说,你想要重新定义respond_to_missing?,因为它可以让你在respond_to?之上访问其他方法,就像method(:method_name)返回方法本身一样。

示例,定义如下的类:

class UserWrapper
  def initialize
    @json_user = { first_name: 'Jean', last_name: 'Dupont' }
  end

  def method_missing(sym, *args, &block)
    return @json_user[sym] if @json_user.keys.include?(sym)
    super
  end

  def respond_to_missing?(sym, include_private = false)
    @json_user.keys.include?(sym) || super
  end
end

结果:

irb(main):015:0> u = UserWrapper.new
=> #<UserWrapper:0x00007fac7b0d3c28 @json_user={:first_name=>"Jean", :last_name=>"Dupont"}>
irb(main):016:0> u.first_name
=> "Jean"
irb(main):017:0> u.respond_to?(:first_name)
=> true
irb(main):018:0> u.method(:first_name)
=> #<Method: UserWrapper#first_name>
irb(main):019:0> u.foo
NoMethodError (undefined method `foo' for #<UserWrapper:0x00007fac7b0d3c28>)

因此,在覆盖respond_to_missing?时始终定义method_missing