2015-03-02 63 views
0

我想元編程的方式來定義基於謂詞方法的爆炸方法。現在,我有行爲我想和method_missing工作:在Ruby中基於現有方法的定義方法

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動態定義的方法來創建對任何一個爆炸方法方法以一個帶有匹配參數的問號結束,但我不知道如何反思方法的所有細節。

Method#parameters看起來像是一個體面的第一步,但我不知道如何翻譯,以阻止參數或處理默認值。

回答

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)將在類中添加一個爆炸的方法對每個謂詞,您可以使用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 
+0

超級酷。我從來不知道你可以傳遞一個圖示作爲塊參數。我猜測沒有辦法反映默認值呢? – fny 2015-03-02 22:10:10

+0

'SomeClass.instance_method(:method_name).parameters'將爲您提供該方法名稱的參數列表,其中包括哪些是必需的,哪些是可選的,但不會給您默認值。 – mikej 2015-03-02 22:25:22

+0

我最終將這個用例演化成了我的第一個發佈的gem,[Interrobang](https://github.com/fny/interrobang)。謝謝您的幫助! – fny 2015-04-03 05:17:20