Single Table Inheritance is great. Possible because even if I don’t work Java since at least 14 years, I still love inheritance in OOP.
I recently played around with STI in Rails and want to share a couple of small tricks:
class Server
end
class HerokuServer
def host = "herokuapp.com"
end
class GenericServer
def host = "sslip.io"
end
class ServersController
def show
@server = Server.find(params[:id])
end
end
First important thing is that @server will be an instance of either HerokuServer or GenericServer.
Rails assigns the correct class automatically even if you call Server.find. This is pretty smart and useful, so
we can call @server.host.
But our path helpers are broken now.
In fact I have a generic ServerController and my routes.rb defines:
resources :servers
So when we use
<%= link_to [:edit, @server] %>
it will inevitably fail with:
NoMethodError - undefined method `edit_generic_server_path' for #<ActionView::Base:0x00000000021ea8>
we can fix this in some different ways:
@server = Server.find(params[:id]).becomes(Server)
but now our @server cannot respond to .host anymore. Alternatively, we can use an explicit path helper:
<%= link_to edit_server_path(@server) %>
or use .becomes only when we need it:
<%= link_to [:edit, @server.becomes(Server)] %>
but with both these approaches it means we have to change it everywhere.
The last possible approach is to override the method model_name.
class GenericServer
def self.model_name
Server.model_name
end
end
but we might not want to define this in all our subclasses, so let’s define this on the Server class instead:
class Server
def self.model_name
ActiveModel::Name.new(self, nil, 'Server')
end
end
You can now keep using all the generic path helpers, but also keep the @server object behaving as its own specific
subclass.