如何在Ruby中临时重新定义方法?

问题描述 投票:0回答:2

说,我有:

class Test
  def initialize(m)
    @m = m
  end

  def test
    @m
  end
end

如何暂时让

#test
的所有实例(现有的和新的)的方法
Test
返回
113
,然后稍后恢复原来的方法?

听起来是很简单的事情,但我找不到一个很好的方法来实现它。可能是因为我对 Ruby 的了解太少了。

到目前为止我发现的是:

# saving the original method
Test.send(:alias_method, :old_test, :test)

# redefining the method
Test.send(:define_method, :test) { 113 }

# restore the original method
Test.send(:alias_method, :test, :old_test)

哪个可以完成这项工作,但据我了解,它也会重新定义现有的

#old_test
(如果存在的话)?..感觉就像是一种黑客攻击,而不是正确使用元编程?..

  1. 是否可以在不使用别名的情况下做这样的事情(而且也不修改
    Test
    的源代码)?
  2. 如果没有(或者如果有更简单/更好的方法),如果你可以修改
    Test
    的源代码,你会怎么做?

如果您能描述实现同一目标的多种方法,即使是那些困难或不切实际的方法,我将不胜感激。只是为了了解 Ruby 中元编程的灵活性和局限性:)

非常感谢🤗

附注我开始这一切的原因是: 我正在使用 gem

rack-throttle
来限制以
/api
开头的请求,但其他网址不应受到影响。,我想测试所有这些以确保其有效。为了测试限制,我也必须将中间件添加到测试环境中。我已经成功地测试了它(使用 minitest),但是测试
ApiController
的所有其他测试不应受到限制,因为如果我们需要在每个请求后等待 1 秒,它会使测试花费更长的时间。

我决定在 minitest 的

RequestSpecificIntervalThrottle#allowed?
中使用
{ true }
来猴子修补
#setup
,以暂时禁用所有这些测试的节流,然后在
#teardown
中再次重新启用它(否则测试节流本身的测试将失败)。如果您告诉我您将如何处理这个问题,我将不胜感激。

但是现在我已经开始深入研究元编程,我也很好奇如何实现这一点(暂时重新定义一个方法),即使我实际上并不打算使用它。

ruby metaprogramming class-eval instance-eval
2个回答
5
投票

您可以使用

instance_method
从任何实例方法获取
UnboundMethod
对象:

class Foo
  def bar
    "Hello"
  end
end
old_method = Foo.instance_method(:bar)
# Redifine the method
Foo.define_method(:bar) do
  puts "Goodbye"
end
puts Foo.new.bar # Goodbye

# restore the old method:
Foo.define_method(old_method.name, old_method)

未绑定方法是对方法对象化时的引用,后续对底层类的更改不会影响未绑定方法。

类方法的等价物是:

class Foo
  def self.baz
    "Hello"
  end
end

old_method = Foo.method(:baz).unbind

如果你想制作世界上最小的(也许也是最无用的)存根库,你可以这样做:

class Stubby
  def initialize(klass, method_name, &block)
    @klass = klass
    @old_method = klass.instance_method(method_name)
    @klass.define_method(method_name, &block)
  end

  def restore
    @klass.define_method(@old_method.name, @old_method)
  end

  def run_and_restore
    yield
  ensure
    restore
  end
end

puts Foo.new.bar # Hello

Stubby.new(Foo, :bar) do
  "Goodbye"
end.run_and_restore do
  puts Foo.new.bar # Goodbye
end

puts Foo.new.bar # Hello

1
投票

正如您在问题中所问的那样,这是不切实际的:)

class Test
  def initialize(m)
    @m = m
  end

  def test
    @m
  end
end

t = Test.new(9)
t.test # => 9

Test.define_method(:test) { 113 }
t.test # => 113

Test.define_method(:test) { instance_variable_get(:@m) }
t.test # => 9

Test.undef_method(:test)
t.test # will raise NoMethodError
© www.soinside.com 2019 - 2024. All rights reserved.