DEV Community

Jeremy Friesen for The DEV Team

Posted on • Originally published at takeonrules.com on

Redefining Ruby Instance Methods at Runtime

Or With Great Power Comes Great Responsibility

I previously published this in at a different site, but I think it remains helpful for those working in Ruby.

TL;DR - In Ruby, each object is a class, and a class is an object. You can redefine one object’s method and not affect other object instances of that class.

The Task

At one point, we loaded into our system a test object that violated our data model. We performed the load via an alternate method as a bit of an experiment.

This object was stubborn, refusing any basic attempts at destruction.

I shelled into our machine, loaded up the ruby console, and ran the following:

rails $ gf = GenericFile.find('my-id')
rails $ gf.destroy
=> NoMethodError: undefined method 'representative' for nil:NilClass

Enter fullscreen mode Exit fullscreen mode

I tried the usual, without knowing the code: gf.representative = nil. That didn’t work. It turns out the representative was an alias for the identifier. So I looked at the validation logic:

def check_and_clear_parent_representative
  if batch.representative == self.pid
    batch.representative = batch.generic_file_ids
                 .select {|i| i if i != self.pid}.first
    batch.save!
  end
end

Enter fullscreen mode Exit fullscreen mode

Not wanting to spend too much effort on this, I brought out one of Ruby’s developer weapons.

def gf.check_and_clear_parent_representative; true; end

Enter fullscreen mode Exit fullscreen mode

Then I ran gf.destroy and “Poof!” the object passed validation and was destroyed.

Explanation

For any object in Ruby you can redefine, in isolation, its instance methods. (eg. def my_instance.the_method) This is because each Ruby object has its own singleton class; in essence of copy of the object’s class.

In the above example, when I wrote def gf.check_and_clear_parent_representative; true; end, I was rewriting the check_and_clear_parent_representative method for the specific GenericFile object that I had previously found. No other GenericFile objects, past or future, would have that change.

I don’t use this tool much, in fact I recommend that you use it only in the context of a one-off solution. This can confound other developers and might invalidate the method cache. In the case of the former, you are increasing the cognitive density of the application. In the case of the latter, you are decreasing performance.

But sometimes you need to bring a dangerous tool to move past an arbitrary barrier. But know the consequence.

Top comments (0)