Sometimes it is nice to be able to show meaningful text in your URLs

URLs like: http://www.site.com/sports/1/teams/1;results
Although relatively short, don't have a lot of meaning

http://www.site.com/sports/cricket/teams/first-11;results
Has more meaning, but is not ideal from the web applications point of view.

http://www.site.com/sports/1-cricket/teams/1-first-11;results
Is the best of both worlds, and surprisingly easy to achieve in ruby.

In all the models you want a descriptive id, just overwrite the to_params method. By default model.to_params returns the value of the models primary key.

In my case the name field of all my models is the field i want to appear in the URL.

The following will do that. Sets the name to lowercase and replaces spaces with -

 
  def to_param
    "#{id}-#{name.downcase.gsub(/ /, '-'))}"
  end

Unfortunately this is not ideal, what if you had non ascii characters in your name, you would end up with escaped characters and an ugly URL

So i chose to make use of the PermalinkFu plugin from Rick Olson

 
  def to_param
    "#{id}-#{PermalinkFu.escape(name)}"
  end

The PermalinkFu plugin will convert the string so it is suitable to be placed in the url, it uses Iconv to do a unicode translation to ascii. This means strings such as café get translated to cafe

You might think that by overwriting to_params rails would get confused, but it dosn't. Well as long as you observe a simple rule:

  • When generating a url pass the model in where it expects an id
    :id => model NOT :id => model.id This works as rails automatically calls to_params to get the id, if you call model.id it will bypass to_params

After writing this i noticed SEO on Rails posted about Even better looking URLs with permalink_fu. I don’t think this is ideal since if you update the name/permalink anyone linking to your URLs will end up with a broken link. Also there is no way to ensure unique permalinks (unless you add extra code into your model which i will cover in a future post).

4 Responses to “Search Engine Friendly URL’s with Ruby on Rails”

  1. Adam Hill Says:

    Good work on the combination, but I'm assuming this is for things without unique strings for their names/titles/slugs. Otherwise using a unique slug and making an index on the slug column would be fast, and good for readability and parsing and speed.

    Even if slugs are only unique within the scope of their parents, using the same "unique slug with no id and db index" concept to get the parent first (via a before_filter etc), then get the child is still perfectly readable and speedy, surely...

  2. AaronT Says:

    Adam:

    Well if you use this method (the to_param) without prepending the object id, it will break record finding.

    When you do Model.find(param[:id]) the non integer part of the string will get stripped off so it is just like searching by an id number.

    The other issue is if you ever change the field the to_param uses it is going to break any external links.

  3. Adam Hill Says:

    Just a note - I hit a bug using the combo {id}-{slug} method when doing popups. It fails to generate the URL.

    eg <%= linkto('Preview', newsletterprevieweditionpath(@newsletter,@newsletteredition), :title => 'Preview this edition', :popup => ['plaintext_email', 'height=600,width=810']) -%>

    I get an error of newsletterprevieweditionurl failed to generate from {:action=>"preview", :newsletterid=>"1-school", :controller=>"admin/newslettereditions", :id=>nil}, expected: {:action=>"preview", :controller=>"admin/newslettereditions"}, diff: {:newsletter_id=>"1-school", :id=>nil}

    Any pointers on this issue? or is it actually a Rails issue with how popups hand to_params (or lack thereof)?

  4. Adam Hill Says:

    My bad, scrap last comment.

Sorry, comments are closed for this article.