2011-07-08 92 views
7

我在玩ruby的元编程功能,并且发现它有点多毛。我试图用一个模块来包装一个方法调用。目前,我这样做:在模块中扩展类方法

module Bar 
    module ClassMethods 
    def wrap(method) 
     class_eval do 
     old_method = "wrapped_#{method}".to_sym 
     unless respond_to? old_method 
      alias_method old_method, method 

      define_method method do |*args| 
      send old_method, *args 
      end 
     end 
     end 
    end 
    end 

    def self.included(base) 
    base.extend ClassMethods 
    end 
end 

class Foo 
    include Bar 

    def bar(arg = 'foo') 
    puts arg 
    end 

    wrap :bar 
end 

三个问题:

  1. 有没有办法做到这一点不重命名的方法,从而允许使用的super?或者更干净/更短的东西?

  2. 是否有一个干净的方式来设置默认值?

  3. 是否有办法将wrap :bar呼叫进一步调高?

+0

在现实生活中,我会使用类继承。 –

+0

我也是,但我试图理解这一切。 :-) –

+0

相关:http://stackoverflow.com/questions/6631182/difference-between-class-eval-and-instance-eval-in-a-module –

回答

6

1)清洁剂/短

module ClassMethods 
    def wrap(method) 
    old = "_#{method}".to_sym 
    alias_method old, method 
    define_method method do |*args| 
     send(old, *args) 
    end 
    end 
end 

class Foo 
    extend ClassMethods 

    def bar(arg = 'foo') 
    puts arg 
    end 

    wrap :bar 
end 

据我知道有没有办法做到这一点不重命名。您可以尝试在define_method区块内拨打super。但首先,如果您明确指定参数,则从define_method内调用super只会成功,否则会收到错误。但即使你打电话给在这种情况下,super(*args),self将是Foo的一个实例。因此,拨打bar的电话将转到Foo的超类,不会被发现并最终导致错误。

2)是,像这样

define_method method do |def_val='foo', *rest| 
    send(old, def_val, *rest) 
end 

然而,在红宝石1.8它是在not possibledefine_method使用的块,但是这已经固定1.9。如果你使用的是1.9,你也可以使用这个

define_method method do |def_val='foo', *rest, &block| 
    send(old, def_val, *rest, &block) 
end 

3)不,不幸的。 alias_method要求存在它作为输入的方法。由于Ruby方法在解析时存在,因此wrap调用必须在bar的定义之后放置,否则alias_method会引发异常。