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.
Learn how to implement STI with Rails with this free ebook
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 ;)