Pain-Free Pretty URLs in Rails

Brian Landau, Former Developer

Article Category: #Code

Posted on

When working recently on a client project in a later stage of development, it became clear that the client wanted “pretty” URLs for specific models in the application. In particular, they wanted some URLs without an id, meaning the normal Rails to_param trick was out:

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

We needed this type of URL on the User model. We already had a field that was both unique and “URL safe,” the login field, so for this model we could just do

def to_param self.login end 

On another model, I decided to create a slug field just for the purpose of pretty URLs. There was already a name field that had to be unique, so I just created a before_validation method to handle the creation and updating of the slug:

before_validation :update_or_create_slug def to_param slug end private def update_or_create_slug if self.new_record? || self.name_changed? || self.slug.blank? self.slug = self.name.try(:slugize) end end 

I used my own “slugize” method, as it’s a little different from the Rails built-in parameterize, and I believe it offers a few minor advantages. I also check for three conditions where we need to assign a new slug:

  1. If it's on a new record.
  2. If the name has changed.
  3. If the slug currently happens to be blank.

You’ll also notice I use the “.try(:slugize)” in case the model is invalid and has nil for the name.

With all this in place, you’d think we’d be all set -- or at least I did.

There was one problem left, though. Let’s say you have a user who is editing an existing model object in the system, and they try to update it with a blank name. This will result in a user with a blank slug field. This wouldn’t seem too bad, but it results in the edit page not being able to render because to_param returns a blank string to the named route URL helper on the form, which raises an ActionController::RoutingError exception.

The solution is to return the old slug value for to_param:

def to_param slug_changed? ? slug_was : slug end 

With that solved, we’re all set to go with pretty, id-free URLs.

If you’re in a situation where you don’t have a required unique field like we did on this project, you might want to look at Patrick’s solution from FeedStitch.

Related Articles