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 <type_is_field>
                  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!