A Practical Example of Using URL Slugs for your Rails Parameters Instead of :Id

Luis Reyes Bartolome
7 min readNov 30, 2020
Lame parody of Metal Slug Double X poster with the word URL replacing the word Metal

Whelp, it’s time to make a new web app using Rails. Time to go into cruise control. You created your model, let’s say it’s called Items, your controller, and routes. After everything has been set up, you push it online and your users now have to go to a URL that looks like “/items/456” . That doesn’t really feel right. A user could save that link, but they’ll never be able to infer what it is. And imagine if you had to reseed your database, or something. That URL would be useless then. The professional sites you see online don’t do this. That is where URL slugs come into play. A URL slug is the part of a URL that tells us about the specific page we’re on in a easy-to-read form — for example “/items/metal-slug-double-x-game” . And hence, what we are going to go through today. Making it so that our URLs show this easy to read URL slug instead of the ugly item id, while not breaking our app.

Though the concept of URL Slugs really applies to any backend, we are going to be doing this for Rails. Also, the assumption is that you are using Rails backend as an API. If you are going to use Rails entirely for your MVC, the process will be the same, you just need to replace :id with :slug in your view files. Everything else will be the same.

Side by side picture of our Rails app with generic ID in URL vs A best buy page with the URL slug in it
If you look at the left, you see the URL ends with our :id. Until the user goes to the link and sees what’s inside, they’ll never know what the link is for. Just Item #50. While on the right, is what the professional Best Buy page looks like. From the URL , you can tell the page is for a Blu-ray double feature of Dragon Ball Z including the movie Lord Slug. Better user experience.
migration file to add a column named slug to our model

Let’s start with some basic set up of our API. For now, just set up the app the same way you normally would. Make a normal Model, in your routes, set up resources :items, and a basic controller with an index and show methods. We are going to tweak these during the process. After setting up the Model as you wanted, lets refer this to model as Item, create a new migration called slug as a string. We will later go into validations and what to put inside a bit later, but for now, we just need this column set up. So, now that we have everything set up on a basic level, let’s go through everything piece by piece starting out with our model.

For our model, let’s dig into what we want to do. When we create a new Item, we can set up each attribute manually, including the slug if we want (and if you want to, skip to the section about validations& macros). But typically, we want this slug to tell us about the “item”. It should be unique just like our :id is unique and it should be tied to our item itself. We could have our item of Slugterra DVD have a slug of “/golden-carrot-24k”, but it doesn’t make sense. So, our first instinct should be to use the item’s name as the slug. But as you may know about URLs, you can’t use spaces and certain characters in your URL. We need some kind of way to make this slug URL-friendly. So, let’s go into our Item model and create some methods. Before that, let’s add a validation of :name & :slug . Let’s create a method called #create_slug . Rails (specifically ActiveSupport) includes a method called #parameterize that will take a string and basically make it URL friendly. The alternative to this is using regex and changing all the characters, but for the sake of ease, we will stick #parameterize. Now that we have this method, we should create a method called #assign_slug . This method will be very simple, it will simply set self.slug = create_slug . This way that every time we call #create_slug, it will create our URL friendly slug based on the item’s name. The last method we need to make is #update_slug . This method’s responsibility is to simply update slug whenever our item is updated. It would be a bad look if our item’s name didn’t match the item’s slug.

An example of what the Model should look like after creating the methods for slugs.
Your Item model should look as follows. A validation to make sure the names and slugs are unique. some macros to make sure that the slug is always updated/created whenever the object is changed/created, and some methods to make the slugs.

Now that we have these methods, we need to make an after_create macro that will call #update_slug, and a before_update macro that will call #assign_slug. Now anytime a new item is created or if its updated (like the name), the slug will always be there and match. So now we can create and manipulate our items without having to manually add slug every time and make sure it doesn’t get screwed up. Time to go into the controller.

In our “normal” controller methods, like #show and #update, we would find our “item” by doing something like Item.find(params[:id]). But because we want to use URL slugs, we can’t use #find. So, we have to go by #find_by and find by the slug everywhere we are looking for a specific item.

Before we go further, let’s refresh our memory about what :id really and what the point is. When we have a RESTful route, it should follow something like /items/XXXXX . Because “XXXXXX” could be anything, instead of somehow hardcoding every single possibility (which would be insane), we refer to it with a variable name. Convention tells us because we normally had this as the id of “items”, we should refer to it as :id. But truthfully, we could refer this as anything, and as such, anything could be written in “XXXXXXXXX”. Its just that we won’t “communicate” well. As such, in the #find_by method, we could go Item.find_by(slug: params[:id]) and it would technically work as intended. But anyone else (including your future self) may look at :id and assume that you are meant to pass the item id.

This is the “before” look on the Controller. This is still saying params[:id] which we will want to change
This is what our Controller should look like. Basically, whenever we refer to an item, we now have to use #find_by on the slug column instead of #find . But as mentioned earlier, though it may work, we don’t want to leave params[:id] as is, we would want to change it to params[:slug]
A screenshot for what a basic route would look like adding :slug to the params

So, now that we have the controllers set up, we do want to refer to params[:id] as something more obvious, such as params[:id]. To make that a thing, we have to go into our routes, and make a change there. The solution for this is pretty quick and easy. If you used resources in your routes (such as resources :items), we’re just going to add “, param: :slug” to the route like the picture. If you are doing custom routes, just simply change the :id part to :slug. NOTE: that you can change that variable name to whatever you wanted, but because we are being to whoever sees this in the future, we’re gonna call it :slug. Now that we changed this, we should now go back to the controller, and change params[:id] to params[:slug]

the “after” screenshot for the controller. It is now doing Item.find_by(slug: params[:slug]) which is friendlier to read
This is what the controller should look now. As you can imagine, anyone coming into this file should be able to tell at a glance, how the route works and what it should be looking for without any extra knowledge of the app.
Notice what the URL now looks like. It goes /items/ and has the name of the item in the url. This immediately tells the user what this link is, and gives us the bonus that we can update the record for whatever reason, and it will always lead the user to this object. Also notice that the name of the object has spaces and other non-friendly characters, but the slug has made it URL-friendly.

To recap what we’ve done, we’ve made a column in our model called slug. We went into the model and created a few methods. #create_slug that takes the item’s name and turns it into a URL-friendly slug. #assign_slug which set the object.slug = #create_slug. And finally, #update_slug which calls #assign_slug . We then created macros that would call #assign_slug & #update_slug whenever a new object is created or updated. When then went into the controller and changed method where we had to find a specific object, and went by #find_by(slug: params[:slug). And finally, we went into our routes and changed the param variable from :id to :slug. Having done all of this allows our website to now have URLs such as the attached photo.

Now that we have this set up and are done, let’s talk about some optional things you want to consider. For a lot of basic uses, the name of your object should be unique. If you find that the slugs of your “items” are too similar or you want to make them extra unique, the solution for this is simple. Go into the #create_slug in the Model and simply add the item’s id to the string returned. You could end up with something like “/items/white-diamond-ring50” while leaving the name column of the object alone. There are some Pros and Cons of doing it this way, but you’ll find whichever feels better for you. But truthfully, both works. Another thing to consider, is that we made the assumption that the name of the object would be comprised of western characters. #parameterize does a pretty decent job of converting string into something user friendly, but you may find a situation where you are using different characters that may not translate well (an example would be “™Iñtërnâtiônàlizæti™øn™”). In this case, you may have to use regex or an outside gem like Unidecode which may do a better job converting the string to how you want it. Because we were break down the functions into parts, all you have to do is edit #create_slug replace #parameterize with your preferred method.

Now that we have set up our URLs to use URL slugs instead of :ids, we now have a site that looks more professional. Also, because we are using slugs vs numbers, our site just became more SEO-friendly as well as improve our user’s experience. An app worthy of Slurm McKenzie

a gif of cartoon character Slurm McKenzie basking in the beach.
“Wimmy wham wham wozzle!”

--

--