Lineage of a Ruby Class


The other day I was writing some code in which I needed to know if a specific class was a descendant of another class.

I've done a similar thing with Ruby objects in the past, but never with classes, and I think I'll start from there.

If we want to know if a Ruby object's class is a descendant of the Vehicle class, we can simply ask it if it is a Vehicle

Vehicle = Class.new Animal = Class.new

Car = Class.new(Vehicle) Dog = Class.new(Animal)

Car.new.is_a?(Vehicle) # => true Dog.new.is_a?(Vehicle) # => false

And we can even go one level down in the lineage an it'll still answer as espected

Sedan = Class.new(Car) Sedan.new.is_a?(Car) # => true

But when asking the same thing to a class, the story is different. And for that, I want to show you my specific use case.

I had a class called Field that represented a field in a form. A form could have many fields and so on.

This was part of a Form Objects library, and in that library I wanted a small DSL that allowed me to create a field and assign a type (it has more than than, but I'll simplify)

class MyForm < Form field :name, String # ^ ^ # | Field type # Field name end

This method created 2 new instance methods on the form: a getter and a setter. And that setter needed to know the type we needed and set the apropriate value.

If the field type was Field or any descendant, the value is unchanged, but if it's something else, we needed to wrap it in a Field (getting a uniform level of abstraction).

class Form def self.field(field_name, field_type) define_method field_name do instance_variable_get "@#{field_name}" end

define_method "#{field_name}=" do |raw_value| value = if raw_value else Field.for(raw_value) end

instance_variable_set "@#{field_name}", value end

end end

Let's center our focus in that placeholder.

My first approach was to use is_a? out of habit. But as it turns out always returned false:

Field = Class.new

String.is_a? Field # => false Field.is_a? Field # => false

And that's because I'm always passing a class in.

Now, seeing it as in the last example seams dumb on my part. But on my defense, I was seeing something like this:

field_type.is_a? Field # => false

Because I was asking it to a variable passed in (and also, this particular case came in after several days of developing the library).

So I had to investigate how to do it. The first thing I did was just asking the class if it's ancestors included the Field class.

field_type.ancestors.include?(Field)

And this got my specs passing. But after looking at it for some time, I started finding that approach more and more annoying. It was very verbose and un-Ruby-like to me, so I went where every developer goes in these cases... Stackoverflow.

I found out that the Class class in Ruby defines the < operator to ask exactly what I wanted to ask: Are you a descendant of Field?

Field = Class.new MyField = Class.new(Field)

MyField < Field # => true

And it's not just limited to that, it defines all the comparison operators, so we can ask for ancestors or descendants with this idiom, which is absolutely awesome to me

MyField < Field # => true Field < Field # => false

MyField <= Field # > true Field < Field # => true

Field > MyField # => true Field > Field # => false

Field >= MyField # > true Field > Field # => true

So next time you need to know about the lineage of a class, you have an excellent short-hand to do so. Thanks Ruby!