One of my favorite aspects of programming is the fact that there’s always more than one way to do something. In fact, I think this is probably why I consider the very act of writing and building software to be a far more intutive task, rather than a merely structured, logical, and rigid pursuit. The very notion of no “one single solution” to solving a problem is what makes programming both a critical thinking skill, but also a creative one.
Let me give you an example: a few months ago, I wrote about using slugs and the
acts_as_url gem to create human-readable urls for an application I was working on. Rails has a handy
to_param method that can be used in conjunction with this gem to generate a hyphen-separated string that can be used in the
show route of a resource in place of the object’s numeric
id, which is both unreadable and usually doesn’t provide any point of reference for the user. So, here we are, using slugs.
However, just because we did something one way initially doesn’t mean that our way is the only way to do it. Actually, I am certain that there are other solutions — and some of them might actually be more flexible than our approach! This was what I realized very quickly when I recently learned about another gem that solves the same problem of slugs in a different, and rather interesting way! In fact it took what I already knew about using and generating slugs in an url structure to another level by using text-based identifiers in place of ids. Basically, it allows us to query for objects by finding them using their slugs, rather than their
ids. Doesn’t this make you super excited? Time to find out more about this approach and become friends with the
Making Friends With Friendly_Id
friendly_id gem, created and maintained by Norman Clarke, describes itself as “the Swiss Army bulldozer of slugging and permalink plugins for ActiveRecord”. And that’s probably a pretty accurate name for all the things that this one single gem is capable of doing!
Before we can really explore all of its neat features, we need to do some initial setup. We’ll start by adding
friendly_id to our
Gemfile, and then running
gem 'friendly_id', '~> 5.1.0'
It’s worth mentioning here that if we’re running on Rails 4.0 or higher, we must use
5.0.0 or greater.
Next, we know we’ll need to add a
slug column to the table in our database that we want to implement
friendly_id on. We’ll add a
publishers table to our bookstore application, and assure that they always have a
name and a
slug column when they are committed to the database. We’ll also add a unique index to the
slug column, since we’ll be using the slug in our urls, which means that no two
publisher instances should have the same slug — and also because we’ll want to be able to look up publishers by their slug rather than by their
1 2 3 4 5 6 7 8 9 10 11 12 13 14
It’s worth nothing that if we were adding
friendly_id to rows that already existed in our database, we would need to generate slugs for these preexisting objects. We have a few options on where to put this command — in a rake task or from the console, for example — but we’d have to find each of our objects and call
save on them to generate their slugs. For example, if we were implementing
friendly_id on our preexisting
Author class, we would run this line:
Now, time to extend or include the
FriendlyId module in our model; that’s right, it doesn’t matter which one you do, just as long as the model has access to the methods defined in the module:
1 2 3 4 5
Now comes the actual implementation! We need to use one method in particular in order to configure the way that the
FriendlyId module will behave inside the context of the model. This method is aptly named as:
friendly_id, which is essentially just the “base” method of the
1 2 3 4 5 6
This method sets the default configurations of what method (yes, a method and not a column in the database!) it should use when trying to find an object. It also allows you to pass an
options hash, which is what we’re doing when we pass it
use: :slugged. We’re effectively telling the
friendly_id method to use the slugged addon.
So now that we’ve got the most basic implementation set up, what does this allow us to do, exactly? Well, given our current model, we can now find instance of our
Publisher class by their name:
1 2 3
Cool! We’re doing almost what Rails’
find method would do, but we’re now no longer finding by a numerical
id, but a string identifer that actually means something to both us as programmers, and our users!
But what if we didn’t want to litter our codebase with
friendly.find everywhere? There’s a solution for that, too. We just need to use another addon, which isn’t implemented by default, called
1 2 3 4 5
This allows us to invoke
find directly — but we have to be careful with this because it could conflict with other places where we are using
find(id) method. Now we can do something like this very easily:
1 2 3
As we continue to implement
friendly_id on other models in our application, we’ll need to keep in mind that any classes that participate in single-table inheritence must extend
FriendlyId in both the parent classes, and all its children classes as well.
But what else can this gem do? It’s time to finally start playing around with all of its functionality!
Where You Lead, Friendly_Id Will Follow
When the documentation called this gem the “Swiss Army bulldozer” of slug url generation, it wasn’t kidding! There really is a ton that we can do with the various modules and addons provided to us by
friendly_id. We’ll explore just a handful of things that we can modify for different use cases.
Should Generate New Friendly Id?
One question we should answer off the bat is how exactly this gem actually decides to generate a slug. It turns out that the
friendly_id gem has a
should_generate_new_friendly_id? method, which determines when and whether to generate a new slug. A peek into the source code of this gem reveals that this method just checks whether there is a slug column defined, and the
base method on the FriendlyId module configurations has been called or not:
1 2 3 4
The documentation points out that it is totally fine to override this method in our models — for example, if we only wanted our slugs to be generated once upon creation, and never updated.
friendly_id expects the slug values that we told it to use in our model to be unique. It also helps that we assured that this is the case by creating a unique index on our
slug column. But, what happens if an admin (or even a user) tries to create an object that would create a duplicate
friendly_id? Well, the gem handles this case in a pretty cool way: it just appends a UUID to the generated slug to ensure that it will be a unique value:
1 2 3 4 5
Pretty awesome, right? It really does seem like this gem is a developer’s best friend!
As nice as it is that we have the functionality to append an UUID sequence to prevent non-unique slugs, it would also be nice if we had some control over how to modify a potential clash in
friendly_id identifiers. Well, our wish is this gem’s command! We can use a lovely “candidates” feature (new in version
5.0 of this gem!) to set up a list of alternate slugs that we can use to distinguish records in place of sequence.
We’ll first add two required columns to our
1 2 3 4 5 6
After we run
rake db:migrate, and make sure that these values are all populate in our pre-existing records, we’ll tell the
friendly_id base method to use the
slug_candidates method, which is going to be a set of instructions on how to construct the
slug for any given instance of our
1 2 3 4 5 6 7 8 9 10 11 12 13 14
You’ll remember that I mentioned that
friendly_id uses a method, and not a column in the database to generate a slug — well, this is exactly why it does that: so that we can override a method very easily! Now the
friendly_id base method will use first the
name attribute, then the
city attributes, followed by the
1 2 3 4 5
It’s worth noting that our method doesn’t have to be named
slug_candidates in the context of our class: this is just the base method of our
FriendlyId module, which means that we can mame it anything we want, so long as we pass it to our
friendly_id method when we tell it what we want to use to generate our
ids for finding our objects. The nice thing about using an array of symbols (as opposed to string literals or procs and lambdas), is that the
FriendlyId module will invoke a method of the same exact name on our
Publisher model, which can be helpful if we have a
country attribute on each of our
publisher instances, which maps to a column in our database.
Of course, if we do happen to have an edge case where two instances of a
Publisher have the exact same
friendly_id gem can handle this situation as well! What will it do, exactly? Here’s what the documentation says:
“If none of the [slug] candidates can generate a unique slug, then FriendlyId will append a UUID to the first candidate as a last resort.”
Nice! So we can always depend on our slugs being unique in some way or another — in the worst case (which probably won’t even happen that often!), it’ll just add a UUID at the end of the slug that matches another one, making it unique.
Saving Old Friends
We’ve been working mostly with the
slugged addon, but there are also quite a few other addons available to us. One of the most interesting ones is the
history addon, which allows us to save different versions of an instance’s
For example, if we had
Article instances that might allow for their
titles to be updated by admins, we wouldn’t want all of our urls to be broken when an admin changed a title on an article, right? Well, this addon helps us prevent that.
In order for us to implement this module, we need add a table to your database to store the slug records. Luckily,
friendly_id has a generator for this:
Now we just need to specify that our base method needs to use the
1 2 3 4
And now in our controller we can do something like this!
1 2 3 4 5 6 7 8 9 10
There are so many interesting use cases for this gem, and it turns out that it does a lot of the stuff we already know about under the hood. One quick example: it uses Rails’
ActiveSupport parameterize method, which is actually used by
to_param — and which we have already explored on our own!
So, there’s never just one way to do anything. As long as we’re willing to learn the fundamentals of how to solve a problem in one way, we can explore all the different solutions that people have already come up with. And when it comes to generating urls and handling strange situations with slugs, we’ve got it covered with our new best friend, the
friendly_idgem is a way to find objects and generate urls using strings instead of numerical ids. The documentation for this gem is fantastic, check it out!
- Here’s a very simple railscast that implements
friendly_idin its most basic capacity.
- Curious about how that
friendly_idbase method works? Check out the source code.