Pretty-ifying URLs
April 18th, 2009 by RadarIn 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.

April 18th, 2009 at 9:58 pm
You gotta use findbypermalink! (with the bang) to let it behave exactly as find, ie throw an exception if not found.
April 18th, 2009 at 10:07 pm
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…
April 18th, 2009 at 11:31 pm
Man parameterize is sure easier then what i was doing: “#{id}-#{title.downcase.gsub(/[^[:alnum:]]/,’-')}”.gsub(/-{2,}/,’-')
April 19th, 2009 at 4:03 am
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.
April 20th, 2009 at 4:16 am
I’ve been a big fan of friendlyid for permalinks: http://github.com/norman/friendlyid/tree/master
April 24th, 2009 at 9:57 am
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.
October 26th, 2009 at 3:05 pm
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?
October 27th, 2009 at 5:01 pm
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.