如何为 Timecop 修补 File 和 CoreExtensions
How to patch File and CoreExtensions for Timecop
我需要猴子补丁文件。 Timecop 不影响文件系统报告的时间,这是 File.atime
使用的时间,而这又是 HttpClient 在将文件发布到服务器时使用的时间,这反过来意味着 VCR 不能完全按预期工作。 AFAIK,这意味着我不能使用优化。
我不明白这是怎么回事:
class File
def atime
"this one happens"
end
end
module CoreExtensions
module File
module TimecopCompat
def atime
"this one does not"
end
end
end
end
File.include CoreExtensions::File::TimecopCompat
File.new('somefile').atime # --> "this one happens"
为什么基于模块的猴子补丁没有发生?我需要更改什么才能使其正常工作?我应该使用其他类型的猴子补丁吗?
问题与 include
将模块附加到祖先链的方式有关。 “Ruby modules: Include vs Prepend vs Extend”非常详细地概述了 include
和 prepend
之间的差异。
看看这两个例子:
class Foo
def hello
"1"
end
end
module Bar
def hello
"2"
end
end
Foo.include Bar
Foo.new.hello
# => "1"
Foo.ancestors
# => [Foo, Bar, Object, Kernel, BasicObject]
对比
class Foo
def hello
"1"
end
end
module Bar
def hello
"2"
end
end
Foo.prepend Bar
Foo.new.hello
# => "2"
Foo.ancestors
# => [Bar, Foo, Object, Kernel, BasicObject]
基本上,您希望在您的情况下使用 prepend
,因为 include
不会覆盖现有方法。
include
不是什么神奇的东西。其实很简单:它使模块成为它所混入的 class 的 superclass 。现在:superclass 方法会覆盖 subclass 方法吗?不,当然不是,恰恰相反。
因此,include
不可能覆盖 class 模块 included
进入的方法。
这就是 prepend
的用途,它混合在祖先层次结构的 开头 的模块中。 (不幸的是,这不能简单地用继承来解释,它是不同的东西。)
让我们在不更改问题的情况下简化您的示例。
module TimecopCompat
def atime
"this one does not"
end
end
我单独留下了 class File
因为它已经有一个实例方法 File#atime.
File.new('temp').atime
#=> 2019-07-16 20:20:51 -0700
正如其他答案所解释的那样,执行
File.include TimecopCompat
结果:
File.ancestors
#=> [File, TimecopCompat, IO, File::Constants, Enumerable, Object, Kernel, BasicObject]
File.new('temp').atime
#=> 2019-07-16 20:20:51 -0700
正在执行
File.prepend TimecopCompat
结果:
File.ancestors
#=> [TimecopCompat, File, IO, File::Constants, Enumerable, Object, Kernel, BasicObject]
File.new('temp').atime
#=> "this one does not"
但是,更改任何核心方法的行为是不好的做法,因为它的原始行为可能依赖于程序中的其他地方。
这里有两种可接受的做法。第一个是创建一个方法(new_atime
,比方说),它有一个 File
对象(file
,比方说)作为它的参数:
file = File.new('temp')
x = new_atime(file)
new_atime
不能与作为其接收者的 File
对象链接,但对于安全和稳健的解决方案来说,这是一个很小的代价。
第二种选择是使用Refinements到refine
File
class.
module RefinedFile
refine File do
def atime
"this one does not"
end
end
end
class C
using RefinedFile
File.new('temp').atime
end
#=> "this one does not"
我们可以确认 File#atime
在 class 之外没有被改动 C
:
File.new('temp').atime
#=> 2019-07-16 20:20:51 -0700
我需要猴子补丁文件。 Timecop 不影响文件系统报告的时间,这是 File.atime
使用的时间,而这又是 HttpClient 在将文件发布到服务器时使用的时间,这反过来意味着 VCR 不能完全按预期工作。 AFAIK,这意味着我不能使用优化。
我不明白这是怎么回事:
class File
def atime
"this one happens"
end
end
module CoreExtensions
module File
module TimecopCompat
def atime
"this one does not"
end
end
end
end
File.include CoreExtensions::File::TimecopCompat
File.new('somefile').atime # --> "this one happens"
为什么基于模块的猴子补丁没有发生?我需要更改什么才能使其正常工作?我应该使用其他类型的猴子补丁吗?
问题与 include
将模块附加到祖先链的方式有关。 “Ruby modules: Include vs Prepend vs Extend”非常详细地概述了 include
和 prepend
之间的差异。
看看这两个例子:
class Foo
def hello
"1"
end
end
module Bar
def hello
"2"
end
end
Foo.include Bar
Foo.new.hello
# => "1"
Foo.ancestors
# => [Foo, Bar, Object, Kernel, BasicObject]
对比
class Foo
def hello
"1"
end
end
module Bar
def hello
"2"
end
end
Foo.prepend Bar
Foo.new.hello
# => "2"
Foo.ancestors
# => [Bar, Foo, Object, Kernel, BasicObject]
基本上,您希望在您的情况下使用 prepend
,因为 include
不会覆盖现有方法。
include
不是什么神奇的东西。其实很简单:它使模块成为它所混入的 class 的 superclass 。现在:superclass 方法会覆盖 subclass 方法吗?不,当然不是,恰恰相反。
因此,include
不可能覆盖 class 模块 included
进入的方法。
这就是 prepend
的用途,它混合在祖先层次结构的 开头 的模块中。 (不幸的是,这不能简单地用继承来解释,它是不同的东西。)
让我们在不更改问题的情况下简化您的示例。
module TimecopCompat
def atime
"this one does not"
end
end
我单独留下了 class File
因为它已经有一个实例方法 File#atime.
File.new('temp').atime
#=> 2019-07-16 20:20:51 -0700
正如其他答案所解释的那样,执行
File.include TimecopCompat
结果:
File.ancestors
#=> [File, TimecopCompat, IO, File::Constants, Enumerable, Object, Kernel, BasicObject]
File.new('temp').atime
#=> 2019-07-16 20:20:51 -0700
正在执行
File.prepend TimecopCompat
结果:
File.ancestors
#=> [TimecopCompat, File, IO, File::Constants, Enumerable, Object, Kernel, BasicObject]
File.new('temp').atime
#=> "this one does not"
但是,更改任何核心方法的行为是不好的做法,因为它的原始行为可能依赖于程序中的其他地方。
这里有两种可接受的做法。第一个是创建一个方法(new_atime
,比方说),它有一个 File
对象(file
,比方说)作为它的参数:
file = File.new('temp')
x = new_atime(file)
new_atime
不能与作为其接收者的 File
对象链接,但对于安全和稳健的解决方案来说,这是一个很小的代价。
第二种选择是使用Refinements到refine
File
class.
module RefinedFile
refine File do
def atime
"this one does not"
end
end
end
class C
using RefinedFile
File.new('temp').atime
end
#=> "this one does not"
我们可以确认 File#atime
在 class 之外没有被改动 C
:
File.new('temp').atime
#=> 2019-07-16 20:20:51 -0700