基于Ruby中现有方法的定义方法

时间:2015-03-02 19:43:23

标签: ruby metaprogramming

我正在尝试使用基于谓词方法定义爆炸方法的元编程方法。现在我有我想要使用method_missing的行为:

class PredicateBang
  def true?
    true
  end

  def false?
    false
  end

  def method_missing(method, *args, &block)
    if bang_match = /\A([^?]+)!\z/.match(method.to_s)
      predicate_method = :"#{bang_match[1]}?"
      if respond_to?(predicate_method)
        unless send(predicate_method, *args, &block)
          raise "#{predicate_method} is false"
        end
        return
      end
    end
    super.method_missing(method, *args, &block)
  end
end

PredicateBang.new.true!
PredicateBang.new.false! # false? is false (RuntimeError)

不是覆盖method_missing,而是通过迭代instance_methods(false)并使用define_method为任何结束的方法创建一个bang方法来动态定义方法。带有匹配参数的问号,但我不确定如何反映方法的所有细节。

Method#parameters似乎是一个不错的第一步,但我不确定如何将其转换为阻止参数或处理默认值。

1 个答案:

答案 0 :(得分:2)

无需使用Method#parameters,您可以使用*args捕获传递的参数(包括默认值)并使用&block样式参数传递任何块,就像您一样与method_missing合作。所以一个基本的例子是

define_method(bang_method) do |*args, &block|
  unless send(predicate_method, *args, &block)
    raise "#{predicate_method} is false"
  end
end

现在要添加一个方法add_bang_methods(clazz)来为每个谓词添加一个bang方法,你可以使用instance_methods来提取方法列表和class_eval在类的上下文中定义这些方法。所以粗略的轮廓将是:

def add_bang_methods(clazz)
  clazz.instance_methods.each do |method_name|
    if predicate_match = /\A([^?]+)\?\z/.match(method_name.to_s)
      bang_method = :"#{predicate_match[1]}!"
      clazz.class_eval do
        define_method(bang_method) do |*args, &block|
          unless send(method_name, *args, &block)
            raise "#{method_name} is false"
          end
        end
      end
    end
  end
end