Pretty-ifying URLs

April 18th, 2009 by Radar

In today’s modern world we are not limited (cough, FORTRAN) to storing information in just a couple of bits. Some say that having pretty URLs is good SEO. I say it’s just common sense. I want the URL to tell a story, not give me some number that is only important to the system it’s coming from.

This is where to_param comes in.

By default this is defined like this:

def to_param
  id
end

Pretty high-tech stuff there. I won’t explain it to you. Give it a moment to sink it. et-cetera. This method is called on an object when it is passed to a url helper such as link_to or redirect_to. It is important to note that you should NEVER call @object.id as that will give the id of the object, not the to_param version of that name.

You can override this in your model in order to give you pretty urls! The way you do this is:

def to_param
  "#{id}-#{name.parameterize}"
end

Where id is the id of the model and name is the text you want into the URL. We call parameterize on this text in order to make it URL friendly.

The reason why we put in the id in the URL is two-fold. Firstly, (/forums/1-best-forum-ever) is so that when it gets passed to our controller as params[:id] or similar, we’re able to pass it to a finder:

Forum.find(params[:id])
# will be passed in as...
Forum.find("1-best-forum-ever")

Now you may go “Hey… wait a second! I don’t have an ID in my database that says ’1-best-forum-ever’!” and you’d be right, you don’t. But you do have an ID of 1 for a record somewhere. Rails will call to_i on this value and convert it simply down to 1 and you’ll be able to treat the pretty URL as if it were a real ID.

Secondly, if two objects in your database have the same name they won’t clash for the parameterized versions, since the id will always be unique.

But I don’t want my ID in my URL

Hey, that’s cool too! Just a little tougher… Before you save your records you’ll want to write out the permalink as a new field, so define a field called permalink in your table if you want to take this route. Then you do a before_save and define to_param like this:

before_create :set_permalink

def set_permalink
  self.permalink = name.parameterize
end

def to_param
  permalink
end

Now when you call the finder you’re going to need to find_by_permalink! instead of just find:

Forum.find_by_permalink!(params[:id])

We have to use the bang version of find_by_permalink because this will raise an ActiveRecord::RecordNotFound exception if the record is not found, just like find. (Thanks to Yarsolav Markin for mentioning this)

So there you have it. to_param overriding for pretty URLs.

Tags: , ,

8 Responses to “Pretty-ifying URLs”

  1. Yaroslav Says:

    You gotta use findbypermalink! (with the bang) to let it behave exactly as find, ie throw an exception if not found.

  2. Bodaniel Jeanes Says:

    Hrmm good article except you should only set the permalink on create, if you don’t have the ID in the URL. If you change the permalink when the title changes then it’s no longer permanent! Then it actually has a NEGATIVE effect on SEO and general linking to from other sites…

  3. pjammer Says:

    Man parameterize is sure easier then what i was doing: “#{id}-#{title.downcase.gsub(/[^[:alnum:]]/,’-')}”.gsub(/-{2,}/,’-')

  4. NeilCauldwell Says:

    My favorite usage of to_param is the username at root scenario, i.e. http://twitter.com/radarlistener . This guy gave a great explanation of how to work round any routes.rb conflicts with doing so http://henrik.nyh.se/2008/10/validating-slugs-against-existing-routes-in-rails

    …I’m sure I’ve commented here with the above link once before, but it’s so useful it’s worth sharing whenever to_param is mentioned; the topic often leads to the ‘root usernames’ scenario. If your Rails app has a core ‘social object’ (cats, recipes, discussion threads, etc), putting pretty urls for the model at the root is worth thinking about.

  5. Josh Nichols Says:

    I’ve been a big fan of friendlyid for permalinks: http://github.com/norman/friendlyid/tree/master

  6. Radar Says:

    Neil,

    Thanks for the link, I should’ve probably written about it in the post, but I didn’t think about it at the time. I like how he covers it.

  7. psychess Says:

    And what happens if you have an edit form for this model? Then it would seem you’re stuck because formfor calls toparam right away (e.g., form_for @person) and right there everything is going to break. Right?

  8. Radar Says:

    psychess, not if you have the ID prefix. I never thought about this but you do raise a valid point, I’ll fix this up in the post later on tonight.

Leave a Reply