Addressing Models with Paths in Rails 3
Sunday, May 29, 2011When you first start with Rails, you at some point probably followed the guide on setting up a blog in under 15 minutes. This guide serves as an excellent introduction to Rails, but if you've come from a bloging platform such as Wordpress or Movable Type, you are probably used to human friendly addresses for blog articles. The guide unfortunately omits how to implement this functionality in Rails. While this is a trivial undertaking for even a semi–experienced Rails developer, a beginner might not know where to start. I hope to explain how to add this functionally to a Rails project.
In a basic Rails controller the show action code probably looks something like this:
def show
@item_inventory = ItemInventory.find(params[:id])
end
Notice the find call, and the value from the params hash it is passed (the id's value). On a standard resourceful route the show action is mapped as follows:
/<model_name>/:id
This means that that if we were to visit /item_inventory/1, for example, the :id value in the params hash would be 1. The find method on a model will find by id. So, in this situation, if an inventory item with the id of 1 exists it will be returned by the find call. To use human readable addresses such as /item_inventory/aer9_laser_rifle we will need to alter the show action.
Before altering the show action, we must add a string to the model which we can match against to find an inventory item. I am calling it path, but you could call it permalink, or slug etc. We will need to write a simple migration to add this property to the database as follows (remember you can create an empty migration using the rails g migration terminal command):
class CreateInventoryItemPathColumn < ActiveRecord::Migration
def self.up
add_column :item_inventories, :path, :string
end
def self.down
remove_column :item_inventories, :path, :string
end
end
As this field is going to be regularly used, it is worth adding a database index to it. This can be done with the following migration:
class InventoryItemAddIndexToPath < ActiveRecord::Migration
def self.up
add_index :item_inventories, :path, { :unique => true }
end
def self.down
remove_index :item_inventories, :path
end
end
We now need to update the show action code to make use of this new property. As mentioned earlier, the find(params[:id]) call finds by id. Now that our value for :id in the params hash is "aer9_laser_rifle" we will want to call a method that will return the inventory item who's path matches that string. As it happens, rails provides find methods automatically for each property on a model. In this case, the one we want is find_by_path() (these methods follow the naming convention of find_by_
def show
@item_inventory = ItemInventory.find_by_path!(params[:id])
end
Now that we have changed the show method, we will have to update the links to inventory items. To demonstrate I will update the item inventory's index view's code as an example. It is a case of rinse and repeat for any other links in your rails project.
Rather than passing the item_inventory to the item_inventory_path() method in link to, which gives you the path ending in the item_inventory's id, we need to pass item_inventory.path. This will give us the address ending in the item inventory's path. So, the index view's code changes from (no, I don't endorse using HTML tables…):
<h1>Listing item_inventories</h1>
<table>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
<% @item_inventories.each do |item_inventory| %>
<tr>
<td><%= item_inventory.title %></td>
<td><%= link_to 'Show', item_inventory_path(item_inventory) %></td>
<td><%= link_to 'Edit', edit_item_inventory_path(item_inventory) %></td>
<td><%= link_to 'Destroy', item_inventory, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Item inventory', new_item_inventory_path %>
to:
<h1>Listing item_inventories</h1>
<table>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
<% @item_inventories.each do |item_inventory| %>
<tr>
<td><%= item_inventory.title %></td>
<td><%= link_to 'Show', item_inventory_path(item_inventory.path) %></td>
<td><%= link_to 'Edit', edit_item_inventory_path(item_inventory) %></td>
<td><%= link_to 'Destroy', item_inventory, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Item inventory', new_item_inventory_path %>
That's it! You can find an example project on github, and if you have problems you can email me, message me on github or comment on the code in question in the github repository.
Cheers.