bch*_*ill 8 ruby metaprogramming ruby-on-rails
我的目标是String用其他方法替换类中的方法(这是针对研究项目).通过在String类中编写代码,这适用于许多方法
alias_method :center_OLD, :center
def center(args*)
r = self.send(*([:center_OLD] + args))
#do some work here
#return something
end
Run Code Online (Sandbox Code Playgroud)
对于某些方法,我也需要处理Proc,这没问题.但是,对于该scan方法,调用它具有从正则表达式匹配设置特殊全局变量的副作用.如文档所述,这些变量是线程和方法的本地变量.
不幸的是,一些Rails代码调用scan使用$&变量.该变量在我的scan方法版本中设置,但因为它是本地的,它不会使它回到使用该变量的原始调用者.
有没有人知道解决这个问题的方法?如果问题需要澄清,请告诉我.
如果它$&有所帮助,我到目前为止看到的变量的所有用途都在Proc传递给scan函数内部,所以我可以获得该Proc的绑定.但是,用户似乎根本无法改变$&,因此我不知道这将有多大帮助.
现行守则
class String
alias_method :scan_OLD, :scan
def scan(*args, &b)
begin
sargs = [:scan_OLD] + args
if b.class == Proc
r = self.send(*sargs, &b)
else
r = self.send(*sargs)
end
r
rescue => error
puts error.backtrace.join("\n")
end
end
end
Run Code Online (Sandbox Code Playgroud)
当然,我会在返回之前做更多的事情r,但这甚至是有问题的 - 所以为了简单起见,我们会坚持这一点.作为测试用例,请考虑:
"hello world".scan(/l./) { |x| puts x }
Run Code Online (Sandbox Code Playgroud)
无论有没有我的版本,这都可以正常工作scan.使用"vanilla" String类可以产生同样的效果
"hello world".scan(/l./) { puts $&; }
Run Code Online (Sandbox Code Playgroud)
即,它打印"ll"和"ld"并返回"hello world".随着修改后的字符串类它打印两个空行(因为$&是nil),然后返回的"hello world".如果我们能够做到这一点,我会很高兴的!
您无法设置$&,因为它源自$~最后一个 MatchData。但是,$~可以设置并且实际上可以满足您的要求。诀窍是将其设置在块绑定中。
该代码的灵感来自Pathname 的旧 Ruby 实现。
(新代码是C语言,不需要关心Ruby框架局部变量)
class String
alias_method :scan_OLD, :scan
def scan(*args, &block)
sargs = [:scan_OLD] + args
if block
self.send(*sargs) do |*bargs|
Thread.current[:string_scan_matchdata] = $~
eval("$~ = Thread.current[:string_scan_matchdata]", block.binding)
yield(*bargs)
end
else
self.send(*sargs)
end
end
end
Run Code Online (Sandbox Code Playgroud)
线程本地(实际上是光纤本地)变量的保存似乎没有必要,因为它仅用于传递值,并且线程永远不会读取除最后一组值之外的任何其他值。它可能是为了恢复原始值(很可能是nil因为该变量不存在)。
完全避免线程局部变量的一种方法是创建一个$~lambda 的 setter(但它确实为每个调用创建一个 lambda):
self.send(*sargs) do |*bargs|
eval("lambda { |m| $~ = m }", block.binding).call($~)
yield(*bargs)
end
Run Code Online (Sandbox Code Playgroud)
对于其中任何一个,您的示例都有效!