Hello there! Are you at the point in your Ruby on Rails project where you’d like integer-based slugs like /users/2 to be friendlier and look like /users/matt instead? How about /posts/how-to-make-great-tacos instead of /posts/31? There are many reasons you’d want to do something like this, and there’s a great gem called FriendlyId that can help us out.

This post will focus on how we can use FriendlyId to only return results when we provide the friendly slug. Otherwise, we want to return a 404 to avoid some of the issues below.

What’s wrong with just using the ids Rails gives us?

Nothing, per se, but friendlier slugs give us some advantages that are too good to pass up. Here are some considerations:

  • Security – Using simple integers as ids exposes a lot of information you may not want the world to know. It makes it easier for unsavory folks to poke around when they can identify your users by an easy-to-guess number, for example.
  • Size – By default, Rails is going to generate the ids sequentially. This means any Model you create will be stuck with tiny numbers for a while. Imagine the confidence you’d inspire when you have your client submit payment at /invoice/1!
  • Search – Google, Duck Duck Go, etc.- will all appreciate and favor descriptive URLs instead of integer-based alternatives. /products/mens/jeans/ is a lot more descriptive than /products/2/4.
  • Humans – we matter too! It turns out we’re really good at identifying patterns, which makes well-structured URLs helpful to our users. (Despite specific browsers trying to hide that beautiful information)
Setting in Safari to show full website addresses.
Safari tries to hide the path from us. 🙁

Installing FriendlyId

I won’t regurgitate their documentation but go to their repo and follow the setup process. Come back here when you’re done.

The Payoff

This is where we get to the whole point of this post. If you’ve read the FriendlyId guide, you know you will be finding database entries using Class.friendly.find vs. Class.find. Let’s say you have a controller method that sets the post for several of your actions for a Post model.

# posts_controller.rb
def set_post
  @post = Post.friendly.find params[:id]
end

By default, FriendlyId will allow you to pass either the integer id Rails provides or the fancy new slugs you now have. What you do next will be dependent on where you are in your project. If you have established users who have bookmarks or manually enter specific paths for your website, 404ing the original integer IDs will cause problems. Furthermore, if you have high-ranking pages indexed by Google, then you probably would want to redirect those, at the very least.

However, if you have the good fortune of being early enough in your app that you don’t have the above considerations, you can proceed as follows.

# posts_controller.rb
def set_post
  # Using find_by_friendly_id won't look up original integer ids
  @post = Post.friendly.find_by_friendly_id params[:id]

  # Redirect old slugs using FriendlyId's History module
  if params[:id] != @post.slug
    redirect_to edit_admin_post_path(@post), status: :moved_permanently
  end
end

This setup will process the friendly slugs (even if they are changed at some point) and return a 404 if the integer id is used. I like this process because it solves some of the issues mentioned in this post and is more visually appealing than switching to UUIDs.

Your mileage may vary, but this is an excellent solution if you’re at a point where you can implement it.

Written by Matt Haliski

The First of His Name, Consumer of Tacos, Operator of Computers, Mower of Grass, Father of the Unsleeper, King of Bad Function Names, Feeder of AI Overlords.