是否有可能’卸载’(’un-require’)Ruby库?

我正在寻找加载一些库,让他们做一些工作,然后执行相反的require以避免以后的兼容性错误。 我不想转储到文件并重新启动shell,因为创建的对象(例如data )可以由我的其他库很好地处理,只是不存在我想要卸载的早期文件。

有人有任何建议或知道这是否可行? 2006年的一次谈话并没有得出多少结论,除了“看起来Webrick设法以某种方式做到这一点”。

有问题的库是Google_drive和Nokogiri (电子表格处理库Roo依赖于Google_drive进行在线电子表格读/写,如该链接所述)。

就像@Alex所说,你可以使用Kernel#fork创建一个新的ruby进程,你require你的库。 新的分叉进程将可以访问父进程中加载​​的数据:

 def talk(msg) # this will allow us to see which process is # talking puts "#{Process.pid}: #{msg}" end # this data was loaded on the parent process # and will be use in the child (and in the parent) this_is_data = ["a", "b", "c"] talk "I'm the father process, and I see #{this_is_data}" # this will create a new ruby process fork{ talk "I'm another process, and I also see #{this_is_data}" talk "But when I change `this_is_data`, a new copy of it is created" this_is_data << "d" talk "My own #{this_is_data}" } # let's wait and give a chance to the child process # finishes before the parent sleep 3 talk "Now, in the father again, data is: #{this_is_data}" 

执行的结果会因机器而异, Process.id将返回不同的值,但它们将如下所示:

 23520: I'm the father process, and I see ["a", "b", "c"] 23551: I'm another process, and I also see ["a", "b", "c"] 23551: But when I change `this_is_data`, a new copy of it is created 23551: My own ["a", "b", "c", "d"] 23520: Now, in the father again, data is: ["a", "b", "c"] 

这很好! fork创建的每个进程都是一个操作系统级进程,并在其自己的内存空间中运行。

你可以做的另一件事是以某种方式管理通过加载文件创建的全局变量,取而代之的是使用require by load 。 这种方法并没有解决已经指出的所有问题,但确实可以提供帮助。 请参阅以下规格:

 require "minitest/autorun" describe "Loading files inside a scope" do def create_lib_file(version) libfile = <{ MyLibrary3 }.must_raise NameError "".respond_to?(:omg_danger!).must_be :==, false end end end end 

并执行结果:

 Run options: --seed 16453 # Running tests: loaded 0.0.3 .loaded 0.0.2 .loaded 0.0.1 . Finished tests in 0.004707s, 637.3486 tests/s, 1274.6973 assertions/s. 3 tests, 6 assertions, 0 failures, 0 errors, 0 skips 

我不知道有什么方法可以卸载文件,但是你可以将精心挑选的全局变量重置为nil和undefine常量(足够接近):

 class Foo; end Object.constants.include?(:Foo) Object.send(:remove_const, :Foo) Object.constants.include?(:Foo) Foo # NameError: uninitialized constant Foo 

根据您的冲突,您还可以临时重命名冲突的类:

 Bar = Foo Object.send(:remove_const, :Foo) do_stuff Foo = Bar 

不幸的是,Ruby的一些特性最终会让你干净地“卸载”一个库。 首先,“加载”Ruby库可以运行任意Ruby代码。 其次,可以在Ruby中动态地重新定义现有的常量和方法。

如果Ruby库只定义了新的类和模块,你可以简单地将它们定义为@Denis所指出的。 但是,在这种情况下,即使您只是按原样保留,也不太可能发生“兼容性错误”。 如果一个库猴子修补核心Ruby类,创建信号处理程序,或设置跟踪钩子或at_exit钩子,那么很难找到已经改变的所有内容并且干净地反转这些更改。

你最好的选择是首先加载你的数据,然后使用像Process#fork这样的东西来分叉一个新的shell,然后加载这些库。 完成后,杀死子shell并返回到父级。 您的数据仍然存在。

https://github.com/burke/zeus和https://github.com/jonleighton/spring使用类似的技术来避免反复等待Rails加载。 也许你可以适应他们的一些工作。