Ruby: The Actual Type of an Object
Today’s post was inspired by my friend Mat Brown.
So, in Ruby it’s quite possible to hide the class of an object, and make it
pretend to be something else. Sometimes this can be useful - for example if you
don’t want to extend a core object, but want people to be able to work with an
object as if you had. A core example of this is how Rails used to treat
ActiveSupport::TimeWithZone
. You can take the concept pretty far:
class String
def class
Fixnum
end
end
So, the class is
"hello".class # Fixnum
Then we get smart with Ruby:
"hello".is_a?(Fixnum) # false
but the library implementers keep up with us:
class String
def is_a?(clazz)
clazz == Fixnum
end
end
"hello".is_a?(Fixnum) # true, gahhhh
Again and again, every time we think we’ve figured out the real class - we get stumped.
Luckily, there are a few ways that are normally not (and shouldn’t be) overridden:
Fixnum === "hello" # false
# which you can mess up with
class Class
def ===(obj)
if obj.is_a?(String) && self == Fixnum
true
elsif String === obj
false
else
super
end
end
Fixnum === "hello" # true
String === "hello" # false
And .. for the most fun, we can bind Object’s base implementation of #class
against an object and call it:
Object.instance_method(:class).bind("hello").call # String
And wouldn’t you know it:
class Object
def class
if self.is_a?(String)
Fixnum
else
super
end
end
end
Object.instance_method(:class).bind("hello").call # Fixnum