Implementing inheritance with params: CreateProducts < ActiveRecord::Migration[5.0]

Subscribe to receive new articles. No spam. Quality content.

Follow @makagon

In Rails 5 each migration class is inherited from ActiveRecord::Migration[5.0] . It looks quite unusual to see that [5.0] part at the end of the parent class. In this article I'll describe why we might need it and how it works.

It all started from the message in dry-rb channel on Gitter. Somebody asked:

hello, can anyone give me some pointers on how is this array syntax implemented? class UserRepo < ROM::Repository[:users]

Yes, inheritance with params is being used not just in Rails, but in ROM too :)

So I started to dig into this topic and it's quite interesting. One more time I was amazed how flexible Ruby is.

Examples

Basic inheritance:

class Human; end class Man < Human end

Also, we can do something like this:

class Human; end foo = Human class Man < foo end

Or this:

class Human; end def parent_class Human end class Man < parent_class end

As long as Ruby code after < evaluates to a class object - it works.

Interesting, let's try to put it this way:

class Human def self.[](version) puts version self end end class Man < Human[:basic] end

It prints basic . Ruby executed this code Human[:basic] , called class method Human.[] and passed basic as version variable.

One thing to notice that Ruby executes this code immediately. I didn't create any object of that class, but it still executed self.[](version) method.

Rails use this param to pass version of migration, in case of ROM gem, they use it to pass root ancestor, so this code:

class UserRepo < ROM::Repository[:users]

Is shorter version of the following:

class UserRepo < ROM::Repository::Root root :users end

And if we go to implementation we will see this:

def [](name) klass = Class.new(self < Repository::Root ? self : Repository::Root) klass.root(name) klass end

Indeed, they get the name param and assign it as a root.

It's quite simple, right? Just add class method [] that returns a class object and you have it, inheritance with params :)

More fun

I wanted to show couple more interesting things that Ruby offers to us. Did you know that class declaration in Ruby returns value?

result = class Foo; end result.inspect # => nil

It returns nil for empty class, or symbol with the name of the latest defined method:

result = class Foo def bar 5 end def baz 10 end end result.inspect # => :baz

But we can return custom value as well:

result = class Foo def bar 10 end 42 end result.inspect # => 42

The other interesting moment that classes don't have a name until you assign class object to a constant, it might sound hard to understand so let's check this example:

klass = Class.new # => #<Class:0x007fb14e036ba8>

Now it's just an object of class Class , but as soon as we assign this object to a constant:

Man = klass

It's not a #<Class:0x007fb14e036ba8> anymore:

puts klass # => Man

If your'e interested why it behaves like this, you can read "Metaprogramming Ruby" book which describes this topic really well.

Thanks for reading, now you know how to implement inheritance with params and couple more interesting tricks in Ruby!

Subscribe to receive new articles. No spam. Quality content.

Follow @makagon