Warning: Creating default object from empty value in /homepages/39/d161420129/htdocs/p373.net/wp/wp-content/themes/p373b/admin/functions.php on line 183
How to *Not Really* delete a ruby class from memory

How to *Not Really* delete a ruby class from memory

Alright, alright…(first blog post here, cut me some slack…)

So, how can you delete a class from memory in Ruby?  First, you might be asking….why in the hell would you do that.

Well, as it happens, in my case, I wrote some base classes that other developers will derive off of.  So in this base class I wrote code that enforces that any class that inherits off it it will conform to the prescribed interface, otherwise it will throw an error.  So, of course, I wrote test code that tests this behavior(what am I a farmer?).  This is where I had the problem…

To be clear, let’s get specific:

class JobType
  class_attribute :cost
  self.cost = nil#all subclasses must define this

  def self.get_cost
    raise "subclass must define this" if self.cost.nil?
    return self.cost
  end

  def self.types
    self.subclasses
  end
end

class LogoJobType
  self.cost = 100
end

So, in my spec tests:

it "should raise error for improperly subclassed class" do
  class TotallyNewClass < JobType;end
  expect{TotallyNewClass.new.get_cost}.to raise_exception
end

Now this actually works fine when the test is run in isolation. However, some other tests are failing(which pass in isolation) when they are all run as a suite.

Say what?

Well, I have some other tests that are calling JobType.types. Ok…so you might be saying that I probably shouldn’t rely on Rails’ #subclasses method because you know you’ve dug into the Rails source and see its implemented using ObjectSpace which is slow and a memory hog. Well, you’re absolutely right. Now, forget that you ever remembered that.

What’s happening is that when these tests are run as a suite, TotallyNewClass is entering the JobType class hierarchy….and staying there!

So, my first thought, was ok, that kind of makes sense, maybe I should try to delete the class constant. This is ruby. I can probably do that. Nope. The only thing you can do is undefine a constant like so:

Object.class_eval{remove_const :TotallyNewClass}

However….

ObjectSpace.each_object(class << JobType;self;end){|k| puts k}#still shows TotallyNewClass

What gives? Well… As it turns out, like I said, you can’t destroy an object that a constant points to. You can only undefine that constant.

Our good friend _Why points out:

If you stuff something in an array and you happen to keep that array around, it’s all marked. If you stuff something in a constant or global variable, it’s forever marked.

See more on _Why’s thoughts on garbage collection here:
http://viewsourcecode.org/why/hacking/theFullyUpturnedBin.html

But this actually led me to the answer! Don’t use a constant. Very simply:

new_klass = Class.new(JobType){}

We dynamically create the class and then an instance off it and never assign it to a constant, and eventually the class gets swept by GC, and life is good again.

Thanks for tuning in.

Update 11/18/2011
Ok, so I started testing my app with capybara and spork…and this doesn’t appear to be working. I’m finding many instances of the anonymous class in the class hierarchy. So…I’m just going to implement subclasses myself using the inherited callback which is pretty easy and probably more performant anyway.

    This entry was posted in Coding, Ruby/Rails, Technology and tagged , , . Bookmark the permalink. Both comments and trackbacks are currently closed.