Возможно ли "выгрузить" ("un-require") библиотеку Ruby?
Я ищу, чтобы загрузить несколько библиотек, заставить их сделать некоторые работы, а затем выполнить противоположное require
чтобы избежать ошибок совместимости позже. Я не хочу сбрасывать файл и перезапускать оболочку, так как созданные объекты (такие как data
) могут быть хорошо обработаны моими другими библиотеками, просто не в присутствии ранних, которые я пытаюсь выгрузить,
Кто-нибудь получил какие-либо предложения или знал, возможно ли это? Разговор с 2006 года не пришел к какому-либо заключению, кроме того, "похоже, что Уэбрику это удается как-то".
В этих библиотеках находятся Google_drive и Nokogiri (библиотека обработки электронных таблиц Roo зависит от Google_drive для чтения/записи электронных таблиц, как описано в этой ссылке).
Ответы
Ответ 1
Как и @Alex, вы можете использовать Kernel#fork
для создания нового рубинового процесса, в котором 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 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 = <<CODE
class MyLibrary#{version}
VERSION = "0.0.#{version}"
end
class String
def omg_danger!
end
end
puts "loaded \#{MyLibrary#{version}::VERSION}"
CODE
File.write("my_library.rb", libfile)
end
after do
File.delete("my_library.rb") if File.exists?("my_library.rb")
end
describe "loading with require" do
it "sees the MyLibrary definition" do
create_lib_file("1")
require_relative "my_library.rb"
MyLibrary1::VERSION.must_be :==, "0.0.1"
"".respond_to?(:omg_danger!).must_be :==, true
end
end
describe "loading with #load " do
describe "without wrapping" do
it "sees the MyLibrary definition" do
create_lib_file("2")
load "my_library.rb"
MyLibrary2::VERSION.must_be :==, "0.0.2"
"".respond_to?(:omg_danger!).must_be :==, true
end
end
describe "using anonymous module wraping" do
it "doesn't sees MyLibrary definition" do
create_lib_file("3")
load "my_library.rb", true
->{ 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
Ответ 2
Я не знаю, как выгрузить файл, но вы можете сбросить выбранные глобальные переменные на нулевые и неопределенные константы (что достаточно близко):
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
Ответ 3
К сожалению, парочка характеристик Ruby заканчивается тем, что вы хотите "разгрузить" библиотеку. Во-первых, "загрузка" библиотеки Ruby может запускать произвольный код Ruby. Во-вторых, существующие константы и методы могут быть динамически переопределены в Ruby.
Если библиотека Ruby определяет только новые классы и модули, вы можете просто определить их, как указал @Denis. Однако в этом случае "ошибки совместимости" очень маловероятны, даже если вы просто оставите их как есть. Если библиотека обезьян - исправляет основные классы Ruby, создает обработчики сигналов или настраивает крючки трассировки или крючки at_exit
, будет очень и очень сложно отследить все, что изменилось и полностью изменило эти изменения.
Лучше всего было бы сначала загрузить ваши данные, а затем использовать что-то вроде Process#fork
для разветвления новой оболочки, а затем загрузить библиотеки. Когда вы закончите, убейте дочернюю оболочку и вернитесь к родительскому объекту. Ваши данные все равно будут там.
https://github.com/burke/zeus и https://github.com/jonleighton/spring используют аналогичные методы, чтобы избежать повторного ожидания загрузки Rails. Возможно, вы можете адаптировать некоторые части своей работы.
Ответ 4
Несмотря на то, что обычно говорят, можно отменить/разгрузить пакеты, используя этот процесс.
- Предполагая, что требуемый файл хранится как
d:/foo.rb
с этим простым содержимым:
class Foo
end
- Поскольку любой класс, модуль или метод определяется как постоянный в Ruby, вы можете сначала отсоединить его:
irb(main):001:0> require 'd:/foo.rb'
=> true
irb(main):002:0> defined? Foo
=> "constant"
irb(main):003:0> Object.send(:remove_const, :Foo)
=> Foo
irb(main):004:0> defined? Foo
=> nil
- Уже требуемые/загруженные файлы записываются в глобальную переменную
$"
, после чего вам необходимо удалить из нее то, что вам уже требуется:
irb(main):005:0> $".select{|r| r.include? 'foo.rb'}
=> ["d:/foo.rb"]
irb(main):006:0> $".delete('d:/foo.rb')
=> "d:/foo.rb"
irb(main):007:0> $".select{|r| r.include? 'foo.rb'}
=> []
- Теперь вы можете снова запросить файл, и все будет обновлено и доступно.
irb(main):008:0> require 'd:/foo.rb'
=> true
irb(main):009:0> $".select{|r| r.include? 'foo.rb'}
=> ["d:/foo.rb"]
irb(main):010:0> defined? Foo
=> "constant"
irb(main):011:0> Foo.new
=> #<Foo:0x000000033ff8d8>