Time to continue learning about Single Table Inheritance with Ruby on Rails.
Learn how to implement STI with Rails with this free ebook
In this article, we are going to see how to create a common controller for our models (Animal, Lion, etc). We will also need to create the corresponding routes and add some helpers to generate the right paths. If you don’t have the code from the first part, you can get it here.
The common controller
Time to start working ! First, we have to generate the common controller for our models. The name is going to be AnimalsController which matches our parent model.
rails g controller AnimalsController index show new edit create update destroy
Define the routes
Let’s move to the routes file and define our routes. First, remove the lines generated by rails since we are going to use resources
:
# Remove these lines from config/routes.rb
get "animals/index"
# Code hidden for brevity
get "animals/destroy"
Define the root of our app and the routes for Animals :
# config/routes.rb
Sti::Application.routes.draw do
resources :animals
root 'animals#index'
end
Setup the index action and the corresponding view
Fire up your server and access localhost:3000
and you will see, well nothing. We should add some content to our index view, maybe the list of all the animals in the database. To provide this list to the view, we need to add some simple code to our controller :
# app/controllers/animals_controller.rb
def index
@animals = Animal.all
end
And copy/paste (or retype it, totally up to you) the following in animals/index.html.erb.
# app/views/animals/index.html.erb
<h1>The Lion Tribe</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Race</th>
<th>Age</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<% @animals.each do |animal| %>
<tr>
<td><%= animal.name %></td>
<td><%= animal.race %></td>
<td><%= animal.age %></td>
<td><%= link_to 'Show', "" %></td>
<td><%= link_to 'Edit', "" %></td>
<td><%= link_to 'Destroy', "", method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
The view still looks pretty bad huh, but whatever, it’s not a design tutorial. You are free to add some css (and you can even pull request on [the repo][2], I will definitely merge it if it looks good).
Now that we have a basic way to show our data, we can start playing with Single Table Inheritance. In the first part, we created one table animals and one model Animal. We also created 3 other models (Lion, Meerkat, WildBoars) that inherit from Animal and all live happily in the same table. Thanks to that, we avoided repeating code at the model level. We can follow the DRY rule at the controller level by using a unique controller for all our STI models.
Routes and STI
After a bit of code, the index action of the Animals controller will be able to show either all the animals or only the animals of a specific race. To do this, the controller need to know which race is requested. Thanks to Rails, we can define a default parameter in our routes.
# config/routes.rb
resources :animals
resources :lions, controller: 'animals', type: 'Lion'
resources :meerkats, controller: 'animals', type: 'Meerkat'
resources :wild_boars, controller: 'animals', type: 'WildBoar'
root 'animals#index'
See the type
key ? That’s the trick. Everytime we access /lions/*, Rails will add a parameter in params
with the key type
. So, for each model that inherit from Animal
, we define resource routes and specify that they have to use the animals
controller. We also add the corresponding type.
We can now extract the type value in the controller with the following methods :
class AnimalsController < ApplicationController
before_action :set_race
def index
@animals = Animal.all
end
# Code hidden for brivety
private
def set_race
@race = race
end
def race
Animal.races.include?(params[:type]) ? params[:type] : "Animal"
end
def race_class
race.constantize
end
end
Basically, before any action we assign the value of the race params, or Animal
if no race is passed, to the variable @race so our views can access it. We can now change the Animal.all
to race_class.all
. The method race_class
is going to send back the constantized parameter type
like Lion
for example.
def index
@animals = race_class.all
end
Warm Up
If you access http://localhost:3000
, you will have the list of all animals. But if you access http://localhost:3000/lions
or http://localhost:3000/meerkats
, you only get the list of the specified animal. Wonderful, isn’t it ?!
That’s it for this article. In the next part, we are going to add dynamic paths and the missing views !