Back to Blog
Jutsu>Rails|Tutorial6/8/2025

Single Table Inheritance with Rails 4 (Part 3)

In the last article, we created our controller and define the index view. In this one, we are going to add the missing views and see some tricks to handle paths for STI models.

Dynamic paths helper

Before we implement the other views, we need a way to create dynamic paths based on the race. To do that, we can use the following helpers method.

# helpers/animals_helper.rb
# Returns a dynamic path based on the provided parameters
def sti_animal_path(race = "animal", animal = nil, action = nil)
  send "#{format_sti(action, race, animal)}_path", animal
end

def format_sti(action, race, animal)
  action || animal ? "#{format_action(action)}#{race.underscore}" : "#{race.underscore.pluralize}"
end

def format_action(action)
  action ? "#{action}_" : ""
end

The show must go on

Update the index.html to integrate this new method :

<td><%= link_to 'Show', sti_animal_path(animal.race, animal) %></td>

And create the file show.html.erb in views/animals :

<h1>Hi, I'm a <%= @animal.race %>.</h1>
<p>My name is <%= @animal.name %></p>
<%= @animal.talk %>

Last step before seeing our show action, we need to define @animal in the controller. To do that, we are going to add a callback before_action before every action where we will need @animal to be defined :

class AnimalsController < ApplicationController
  before_action :set_animal, only: [:show, :edit, :update, :destroy]
  before_action :set_race

  def index
    @animals = race_class.all
  end

  def show
  end

  # Code hidden for brivety

  private

  # Code hidden for brivety

  def set_animal
    @animal = race_class.find(params[:id])
  end

end

Now refresh the webpage, click on show and tadaa ! It's going to take you to the right animal with the right path (lions/meerkats/wild_boars).

Sharing the index view

We are almost done, first let's improve our index page. For each animal in the table, we are going to add a link to the list of animals of the same species :

<td><%= link_to "See all #{animal.race.pluralize}", sti_animal_path(animal.race) %></td>

And a way to show the list of all animals :

    [...]
    </tbody>
</table>

<%= link_to 'See all animals', sti_animal_path %>

Finally, we need a link to create a new animal :

[...]
<%= link_to 'See all animals', sti_animal_path %>
<%= link_to "New #{@race}", sti_animal_path(@race, nil, :new) %>

Play a bit with the app, you should be able to navigate between all the animals and specific races. We better add the new view now since we just created the link to get there !

Creating animals

The new view is quite simple, all the logic will be setup in the form partial :

# views/animals/new.html.erb
<h1>New <%=  "#{@race.capitalize}" %></h1>
<%= render 'form' %>
<%= link_to 'Back', sti_animal_path(@race) %>

Also, we need to instantiate a new object in the action new of our controller :

# controllers/animals_controller.rb
def new
  @animal = race_class.new
end

Now the form :

<%= form_for(@animal) do |f| %>
  <% if @animal.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@animal.errors.count, "error") %> prohibited this animal from being saved:</h2>

      <ul>
      <% @animal.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>

  <div class="field">
      <%= f.label :race %><br>
      <%= f.select :race, Animal.races.map {|r| [r.humanize, r.camelcase]}, {}, disabled: @race != "Animal" %>
  </div>

  <div class="field">
    <%= f.label :age %><br>
    <%= f.text_field :age %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Okay, now you should be able to access the new page and fill the form. If you play a bit with the path, you will notice that the select box for the race is only enabled if you create a new Animal. If you try to create a new Lion (/lions/new), the select box will be selected by default and will be disabled.

Strong parameters for Animal

Before we actually create animals with our form, we need to define the parameters that can be passed to our animal. This is a Rails 4 new feature, if you want more information, check out the doc.

As you can see in the following code, we added a method called animal_params which define the three parameters that we want to allow for the model Animal. We also added the code for the create action :

class AnimalsController < ApplicationController

    # Code hidden for brievety

    def create
        @animal = Animal.new(animal_params)
        if @animal.save
          redirect_to @animal, notice: "#{race} was successfully created."
        else
          render action: 'new'
        end
    end

    # Code hidden for brievety

    private

    # Code hidden for brievety

    def animal_params
        params.require(race.underscore.to_sym).permit(:name, :race, :age)
    end

end

Updating and deleting animals

Now let's add the code for the two missing actions, update and destroy, and the edit view. First, we are going to put the links to access them in the index page :

  <td><%= link_to 'Edit', sti_animal_path(animal.race, animal, :edit) %></td>
  <td><%= link_to 'Destroy', sti_animal_path(animal.race, animal), method: :delete, data: { confirm: 'Are you sure?' } %></td>

Then, we define the actions in the controller :

def update
  if @animal.update(animal_params)
    redirect_to @animal, notice: "#{race} was successfully created."
  else
    render action: 'edit'
  end
end

def destroy
  @animal.destroy
  redirect_to animals_url
end

And finally, the edit view :

#views/animals/edit.html.erb
<h1>Editing <%= "#{@race}" %></h1>
<%= render 'form' %>
<%= link_to 'Back', sti_animal_path(@race) %>

And that's it ! We are done.

Source code

The code is available on Github.

Warm Up

Through this set of tutorials, I hope you understood what can be done with Single Table Inheritance and how it can prevent you from having similar controllers. You won't use STI everyday, but when you need it you will be happy to find it ;)

Related articles

Comments

Loading comments...

Level Up Your Dev Skills & Income 💰💻

Learn how to sharpen your programming skills, monetize your expertise, and build a future-proof career — through freelancing, SaaS, digital products, or high-paying jobs.

Join 3,000+ developers learning how to earn more, improve their skills, and future-proof their careers.

Single Table Inheritance with Rails 4 (Part 3) | Devmystify