Is it possible to 'unload' ('un-require') a Ruby library?

Like @Alex said, you could use the Kernel#fork to create a new ruby process where you will require your libraries. The new forked process will have access to data loaded in the parent process:

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}"

The result of this execution will vary in your machine, the Process.id will return different values, but it will be like these:

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"]

And this is good! Each process created by fork is an O.S. level process and run in it's own memory space.

Another thing you can do to somehow manage the globals created by loading a file, is replace the use of require by load. This approach doesn't solve all the problems already pointed, but really can help. See the following specs:

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

And the result of execution:

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

I'm not aware of any way to unload a file, but you can reset handpicked global variables to nil and undefine constants (which is close enough):

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

Depending on what your conflicts are, you could also temporarily rename the conflicting classes:

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

Despite of what is usually said, it's possible to unrequire/unload packages using this process.

  1. Assuming that the file required is stored as d:/foo.rb with this simple content :
class Foo

end
  1. As any Class, Module or Method is defined as a constant in Ruby you can first unlink it :
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. The files already required/loaded are recorded in the global var $", you then need to purge what you've already required from it :
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. Now you can require your file again and everything will be refreshed and available.
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>