Tuesday 11 February 2014

Ruby: Class loading/unloading example

Recently I had the unfortunate task of writing specs to test a singleton.
Why would that be a chore I hear you ask?
Because the singleton in question had several configuration options.
Most of which were file based.
So out of the ~60 specs, 50 of them were for error conditions.
(File missing, folder not readable, not JSON, JSON but with invalid stanzas etc)

The problem was that each test had to start with a clean slate.
The singleton class must be pristine and new each run.
How the hell do you do that?

Unload the class, then reload it.

I fiddled about quite a bit with this, so I've put together a simple project that illustrates how this is done.

The bottom line is that you have to send :remove_const and the class name to the module or whatever your class is defined in.

For example, say a class is defined in a file skavee_bloopsie.rb thus:

module Skavee
  class Bloopsie
  end
end

Then this is what you run to unload and reload it:

Skavee.send(:remove_const, :Bloopsie)

This works at any depth of modules:

module A
  module B
    module C
      class Z
        def ohai
          'ohai!'
        end
      end
    end
  end
end

A::B::C.send(:remove_const, :Z)

You can test this yourself by copying and pasting this into irb:

module A
  module B
    module C
      class Z
        def ohai
          'ohai!'
        end
      end
    end
  end
end

a = A::B::C::Z.new
 => #<A::B::C::Z:0x007fc78c9b1dc8>

puts a.ohai
 => "ohai!"

A::B::C.send(:remove_const, :Z)
 => A::B::C::Z

b = A::B::C::Z.new
 => NameError: uninitialized constant A::B::C::Z

And here's the Gotcha!

a.ohai
 => "ohai!"

Wha? Well it's obvious. The instance is still being referenced.

So be careful out there people.

An example gem is available via my repo at https://github.com/ZenGirl/ruby-class-unloading

No comments:

Post a Comment