Your friends at Viget present Extend, a Code & Technology Blog

Pain-Free Pretty URLs in Rails

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.


Get More From Viget

Subscribe to get our monthly newsletter and occasional special announcements.