扩展模块中的类方法

时间:2020-12-01 18:14:32

I'm playing with ruby's metaprogramming features, and I'm finding it a bit hairy. I'm trying to wrap a method call using a module. Currently, I'm doing this:

我正在玩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

Three questions:

  1. Is there any way to do this without renaming the method, so as to allow the use of super? Or something cleaner/shorter?

    有没有办法在不重命名方法的情况下这样做,以便允许使用super?还是更清洁/更短的东西?

  2. Is there a clean way to set the default values?

    有没有一种简洁的方法来设置默认值?

  3. Is there a means to move the wrap :bar call further up?

    有没有办法移动wrap:bar进一步调用?

1 个解决方案

#1


6  

1) Cleaner/shorter

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

As far as I know there is no way to achieve this without renaming. You could try to call super inside the define_method block. But first of all, a call to super from within a define_method will only succeed if you specify arguments explicitly, otherwise you receive an error. But even if you call e.g. super(*args), self in that context would be an instance of Foo. So a call to bar would go to the super classes of Foo, not be found and ultimately result in an error.

据我所知,没有重命名就无法实现这一目标。您可以尝试在define_method块中调用super。但首先,如果明确指定参数,则只能在define_method内调用super,否则会收到错误。但即使你打电话,例如super(* args),在该上下文中的self将是Foo的一个实例。所以对bar的调用会转到Foo的超类,找不到并最终导致错误。

2) Yes, like so

2)是的,就像这样

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

However, in Ruby 1.8 it is not possible to use a block in define_method, but this has been fixed for 1.9. If you are using 1.9, you could also use this

但是,在Ruby 1.8中,不可能在define_method中使用块,但这已经修复为1.9。如果您使用1.9,您也可以使用它

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

3) No, unfortunately. alias_method requires the existence of the methods that it takes as input. As Ruby methods come into existence as they are parsed, the wrap call must be placed after the definition of bar otherwise alias_method would raise an exception.

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

#1


6  

1) Cleaner/shorter

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

As far as I know there is no way to achieve this without renaming. You could try to call super inside the define_method block. But first of all, a call to super from within a define_method will only succeed if you specify arguments explicitly, otherwise you receive an error. But even if you call e.g. super(*args), self in that context would be an instance of Foo. So a call to bar would go to the super classes of Foo, not be found and ultimately result in an error.

据我所知,没有重命名就无法实现这一目标。您可以尝试在define_method块中调用super。但首先,如果明确指定参数,则只能在define_method内调用super,否则会收到错误。但即使你打电话,例如super(* args),在该上下文中的self将是Foo的一个实例。所以对bar的调用会转到Foo的超类,找不到并最终导致错误。

2) Yes, like so

2)是的,就像这样

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

However, in Ruby 1.8 it is not possible to use a block in define_method, but this has been fixed for 1.9. If you are using 1.9, you could also use this

但是,在Ruby 1.8中,不可能在define_method中使用块,但这已经修复为1.9。如果您使用1.9,您也可以使用它

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

3) No, unfortunately. alias_method requires the existence of the methods that it takes as input. As Ruby methods come into existence as they are parsed, the wrap call must be placed after the definition of bar otherwise alias_method would raise an exception.

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