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
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
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
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.