Возможно ли "выгрузить" ("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

Несмотря на то, что обычно говорят, можно отменить/разгрузить пакеты, используя этот процесс.

  1. Предполагая, что требуемый файл хранится как d:/foo.rb с этим простым содержимым:
class Foo

end
  1. Поскольку любой класс, модуль или метод определяется как постоянный в 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
  1. Уже требуемые/загруженные файлы записываются в глобальную переменную $", после чего вам необходимо удалить из нее то, что вам уже требуется:
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'}
=> []
  1. Теперь вы можете снова запросить файл, и все будет обновлено и доступно.
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>