Ruby 如何为Timecop修补文件和CoreExtensions

Ruby 如何为Timecop修补文件和CoreExtensions,ruby,monkeypatching,Ruby,Monkeypatching,我需要修改补丁文件。Timecop不影响文件系统报告的时间,这是file.atime使用的时间,而HttpClient在将文件发布到服务器时使用的时间,这反过来意味着VCR不能完全按照预期工作。好吧,这意味着我不能使用改进 我不明白这里发生了什么: class File def atime "this one happens" end end module CoreExtensions module File module TimecopCompat d

我需要修改补丁文件。Timecop不影响文件系统报告的时间,这是file.atime使用的时间,而HttpClient在将文件发布到服务器时使用的时间,这反过来意味着VCR不能完全按照预期工作。好吧,这意味着我不能使用改进

我不明白这里发生了什么:

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"

为什么基于模块的monkey补丁没有出现?我需要改变什么才能让它工作?我应该使用另一种猴子补丁吗?

这个问题与include将模块附加到祖先链的方式有关。提供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将模块附加到祖先链的方式有关。提供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并不是什么神奇的东西。它实际上非常简单:它使模块成为它所混合到的类的超类。现在:超类方法覆盖子类方法吗?不,当然不是,相反

因此,include不可能重写模块所包含的类的方法

这就是prepend的用途,它混合在祖先层次结构开头的模块中。不幸的是,这不能简单地用继承来解释,它是不同的。

包含并不是什么神奇的东西。它实际上非常简单:它使模块成为它所混合到的类的超类。现在:超类方法覆盖子类方法吗?不,当然不是,相反

因此,include不可能重写模块所包含的类的方法


这就是prepend的用途,它混合在祖先层次结构开头的模块中。不幸的是,这不能简单地用继承来解释,它是不同的。

让我们在不改变问题的情况下简化您的示例

module TimecopCompat
  def atime
    "this one does not"
  end
end
我把类文件单独留下,因为它已经有了一个实例方法

正如其他答案所解释的,执行

File.include TimecopCompat
File.prepend TimecopCompat
结果:

File.ancestors
  #=> [File, TimecopCompat, IO, File::Constants, Enumerable, Object, Kernel, BasicObject] 
File.new('temp').atime
  #=> 2019-07-16 20:20:51 -0700
File.ancestors
  #=> [TimecopCompat, File, IO, File::Constants, Enumerable, Object, Kernel, BasicObject] 
File.new('temp').atime
  #=> "this one does not" 
鉴于执行

File.include TimecopCompat
File.prepend TimecopCompat
结果:

File.ancestors
  #=> [File, TimecopCompat, IO, File::Constants, Enumerable, Object, Kernel, BasicObject] 
File.new('temp').atime
  #=> 2019-07-16 20:20:51 -0700
File.ancestors
  #=> [TimecopCompat, File, IO, File::Constants, Enumerable, Object, Kernel, BasicObject] 
File.new('temp').atime
  #=> "this one does not" 
然而,改变任何核心方法的行为都是不好的做法,因为它的原始行为可能依赖于程序中的其他地方

这里有两种可接受的做法。第一个是创建一个新的方法,比如说它有一个File对象File,比如说它的参数:

file = File.new('temp')
x = new_atime(file)
新的_atime不能与作为其接收器的文件对象链接,但这对于一个安全而健壮的解决方案来说是一个很小的代价

第二个选项是用于优化文件类

我们可以确认,文件时间未在C类之外更改:


让我们在不改变问题的情况下简化您的示例

module TimecopCompat
  def atime
    "this one does not"
  end
end
我把类文件单独留下,因为它已经有了一个实例方法

正如其他答案所解释的,执行

File.include TimecopCompat
File.prepend TimecopCompat
结果:

File.ancestors
  #=> [File, TimecopCompat, IO, File::Constants, Enumerable, Object, Kernel, BasicObject] 
File.new('temp').atime
  #=> 2019-07-16 20:20:51 -0700
File.ancestors
  #=> [TimecopCompat, File, IO, File::Constants, Enumerable, Object, Kernel, BasicObject] 
File.new('temp').atime
  #=> "this one does not" 
鉴于执行

File.include TimecopCompat
File.prepend TimecopCompat
结果:

File.ancestors
  #=> [File, TimecopCompat, IO, File::Constants, Enumerable, Object, Kernel, BasicObject] 
File.new('temp').atime
  #=> 2019-07-16 20:20:51 -0700
File.ancestors
  #=> [TimecopCompat, File, IO, File::Constants, Enumerable, Object, Kernel, BasicObject] 
File.new('temp').atime
  #=> "this one does not" 
然而,改变任何核心方法的行为都是不好的做法,因为它的原始行为可能依赖于程序中的其他地方

这里有两种可接受的做法。第一个是创建一个新的方法,比如说它有一个File对象File,比如说它的参数:

file = File.new('temp')
x = new_atime(file)
新的_atime不能与作为其接收器的文件对象链接,但这对于一个安全而健壮的解决方案来说是一个很小的代价

第二个选项是用于优化文件类

我们可以确认,文件时间未在C类之外更改:


啊,很好,我明白了:File.include意味着如果函数还没有在File中定义,那么它将在included模块中查找。Prepend意味着,如果函数没有在Prepend模块中定义,那么它将在文件中查找,然后在链上查找。整洁的啊,很好,我明白了:File.include意味着如果函数还没有在File中定义,那么它将在included模块中查找。Prepend意味着,如果函数没有在Prepend模块中定义,那么它将在文件中查找,然后在链上查找。整洁的谢谢您的回答,但是对file.atime的实际调用在httpclient gem中,而httpclient gem又被API gem(在本例中为Boxr和VCR)包装。好的,这两种方法在本例中都不起作用。谢谢您的回答,但是对file.atime的实际调用在httpclient gem中,而在本例中,它又由API gem包装,即Boxr和VCR。好吧,这两种方法在这种情况下都不起作用。与你的q没有严格的关系
uestion,但请注意,使用Timecop是一种错误。现在使用Rails TimeHelper更好,这一点已经被广泛接受,特别是因为Timecop已经一年多没有更新了。在撰写本文时,它并不正式支持最新的ruby。至少它没有在回购协议的travis.yml中进行测试。当然,如果你正在维护一个没有时间升级的旧应用程序,那么这就是继续使用它的充分理由。请注意,这与您的问题没有严格的关系,但请注意使用Timecop是非常重要的。现在使用Rails TimeHelper更好,这一点已经被广泛接受,特别是因为Timecop已经一年多没有更新了。在撰写本文时,它并不正式支持最新的ruby。至少它没有在回购协议的travis.yml中进行测试。当然,如果你正在维护一个没有时间升级的旧应用程序,那么这就是继续使用它的充分理由。只是要知道。