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.