Implementing inheritance with params: CreateProducts < ActiveRecord::Migration[5.0]
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!