This post originated from an RSS feed registered with Ruby Buzz
by Obie Fernandez.
Original Post: SEO Optimization of URLs in Rails with to_param
Feed Title: Obie On Rails (Has It Been 9 Years Already?)
Feed URL: http://jroller.com/obie/feed/entries/rss
Feed Description: Obie Fernandez talks about life as a technologist, mostly as ramblings about software development and consulting. Nowadays it's pretty much all about Ruby and Ruby on Rails.
SEO specialist Tony Spencer just blogged about SEO optimizations for Ruby on Rails, and I was surprised about his recommendation for better URL naming. Basically, he advises the inclusion of a :name parameter in your routes.rb mapping for a particular URL, and then passing along a :name argument in your calls to URL helpers such as link_to.
That will work, but is an awful lot of work where the Rails Way to do it is a lot cleaner and involves significantly less effort.
The secret is to understand how and when the to_param method is used during URL generation, which happens during a call to Routing::Routes.generate. Part of the process of creating a link is to build the request string, and it is here where Rails calls to_param on each value in the request parameters hash, with a line of Ruby code that looks like:
value.each { |val| elements << "#{key}=#{CGI.escape(val.to_param.to_s)}" }
Rails mixes an implementation of to_param into everything that might end up as a parameter value, starting with Object. Firing up the Rails console and calling to_param on different types of objects is a good way of seeing of how it works:
>> Object.new.to_param
=> "#<Object:0x337db8>"
>> 1.to_param
=> "1"
>> 0x555.to_param
=> "1365"
>> "The Farmer".to_param
=> "The Farmer"
The default to_param method of ActiveRecord just returns the value of its primary key:
# Enables Active Record objects to be used as URL parameters in Action Pack automatically.
def to_param
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
(id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
end
That means that we can override to_param in our ActiveRecord models and make their representation as a URL parameter more descriptive and useful in the SEO sense. The most basic* implementation, with a model that has a name column is as follows:
def to_param
"#{id}-#{name.gsub(/[^a-z0-9]+/i, '-')}"
end
[*Keep in mind that the regex in the method above will not recognize accented characters properly.]
My current client project involves cities and requires SEO optimization. Look at what happens when I generate a route for a city:
>> options = {:controller=>'cities',:action=>'show',:id => City.find(1)}
=> {... would be the city, but snipped for example ...}
>> ActionController::Routing::Routes.generate(options)
=> "/cities/1-Little-Rock-AR"
Voila! A Googlebot-optimized slug, in SEO terms.
Important: You must be passing in your model instance as the id parameter, as opposed to the actual id value whenever you generate a URL. It happens automatically when you're using the new Rails 1.2 RESTful routes helpers, but not with older code and over the last 2 years, I've seen lots of people make that mistake (including myself, more than I care to admit).
To be absolutely clear, the following code will NOT work with this technique, because your special to_param implementation will never get called:
Wait a second, won't that break code that relies on params[:id]?
My first thought, when I heard about this technique, was that it would break code on the request handling side of the house. However, it doesn't:
>> City.find('1-Little-Rock-AR')
=> #<City:0x35073c0 @attributes={"name"=>"Little Rock, AR", "updated_at"=>"2007-01-04 23:32:37", "latitude"=>"34.7365", "zoom_level"=>"11", "id"=>"1", "description"=>"The home of the best president of the Uniteed States of Amerrrrrrica, Bill Clinton!", "longitude"=>"-92.2742", "created_at"=>"2007-01-04 23:32:37"}>
Neither does Rails pass-through the string value to the database. Just check your log and see:
City Load (0.001114) SELECT * FROM cities WHERE (cities.id = 1)
Rails (as of 1.2) is smart enough to call to_i on values passed to the database, when the column type is designated as an :integer, which is usually the case for primary keys. Ruby's to_i for strings looks for contiguous numerical characters from left to right, which is why it won't matter if your slug content also has numbers, as long as the proper id value comes first.
If you're interested in the implementation, take a peek at the source code for the ActiveRecord::ConnectionAdapters::Quoting module, around line 16.
This behavior is a marked improvement for the technique over having to manually write an integer coercion, as was necessary to get it to work in pre-Rails 1.2 applications with databases such as Postgres which won't do the coercion automatically, and makes some of the information in this other popular blog entry on the subject from last year outdated.
Tired of your job? Need to hire developers? Visit DZone Jobs: great people, great opportunities.