如何临时重定义Ruby中的一个方法?
How to temporarily redefine a method in Ruby?
说,我有:
class Test
def initialize(m)
@m = m
end
def test
@m
end
end
如何临时为Test
return113
的所有实例(包括现有实例和新实例)创建方法#test
,然后稍后恢复原始方法?
这听起来很简单,但我找不到实现它的好方法。可能是因为我对 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
(如果存在的话)?..感觉就像是一种 hack 而不是正确使用元编程?..
- 是否可以在不使用别名的情况下做这样的事情(而且不修改
Test
的源代码)?
- 如果没有(或者如果有 easier/nicer 方法)如果你可以修改
Test
的源代码,你会怎么做?
如果您能描述实现同一件事的多种方法,即使是困难或不切实际的方法,我将不胜感激。只是想了解一下 Ruby 中元编程的灵活性和局限性:)
非常感谢
P.S。我开始这一切的原因:
我正在使用 gem rack-throttle
来限制以 /api
开头的请求,但其他网址不应受到影响。我想测试所有这些以确保它有效。为了测试节流,我也必须将中间件添加到测试环境中。我已经成功测试了它(使用 minitest),但是不应限制所有其他测试 ApiController
的测试,因为如果我们需要在每个请求后等待 1 秒,它会使测试花费更长的时间。
我决定在 minitest 的 #setup
中用 { true }
猴子修补 RequestSpecificIntervalThrottle#allowed?
以暂时禁用所有这些测试的节流,然后在 [=25= 中重新启用它]s(否则测试节流本身的测试将失败)。如果你告诉我你将如何处理这个问题,我将不胜感激。
然而,既然我已经开始深入研究元编程,我也很好奇如何实现这一点(暂时重新定义一个方法),即使我实际上并不打算使用它。
您可以使用来自任何实例方法的 instance_method
to get a 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不会影响未绑定方法。
class 方法的等效项是:
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
正如你在问题中所问的那样不切实际:)
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
说,我有:
class Test
def initialize(m)
@m = m
end
def test
@m
end
end
如何临时为Test
return113
的所有实例(包括现有实例和新实例)创建方法#test
,然后稍后恢复原始方法?
这听起来很简单,但我找不到实现它的好方法。可能是因为我对 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
(如果存在的话)?..感觉就像是一种 hack 而不是正确使用元编程?..
- 是否可以在不使用别名的情况下做这样的事情(而且不修改
Test
的源代码)? - 如果没有(或者如果有 easier/nicer 方法)如果你可以修改
Test
的源代码,你会怎么做?
如果您能描述实现同一件事的多种方法,即使是困难或不切实际的方法,我将不胜感激。只是想了解一下 Ruby 中元编程的灵活性和局限性:)
非常感谢
P.S。我开始这一切的原因:
我正在使用 gem rack-throttle
来限制以 /api
开头的请求,但其他网址不应受到影响。我想测试所有这些以确保它有效。为了测试节流,我也必须将中间件添加到测试环境中。我已经成功测试了它(使用 minitest),但是不应限制所有其他测试 ApiController
的测试,因为如果我们需要在每个请求后等待 1 秒,它会使测试花费更长的时间。
我决定在 minitest 的 #setup
中用 { true }
猴子修补 RequestSpecificIntervalThrottle#allowed?
以暂时禁用所有这些测试的节流,然后在 [=25= 中重新启用它]s(否则测试节流本身的测试将失败)。如果你告诉我你将如何处理这个问题,我将不胜感激。
然而,既然我已经开始深入研究元编程,我也很好奇如何实现这一点(暂时重新定义一个方法),即使我实际上并不打算使用它。
您可以使用来自任何实例方法的 instance_method
to get a 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不会影响未绑定方法。
class 方法的等效项是:
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
正如你在问题中所问的那样不切实际:)
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