根据 Ruby 中的现有方法定义方法

Define Method Based on Existing Method in Ruby

我正在尝试对一种基于谓词方法定义 bang 方法的方法进行元编程。现在我有了想要使用的行为 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 似乎是不错的第一步,但我不确定如何将其转换为块参数或处理默认值。

无需使用 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),它会为 class 上的每个谓词添加一个 bang 方法,您可以像您提到的那样使用 instance_methods 来获取方法列表和 class_eval 在 class 的上下文中定义这些方法。所以粗略的大纲是:

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