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 id
s. Doesn’t this make you super excited? Time to find out more about this approach and become friends with the friendly_id
gem!
Making Friends With Friendly_Id
The 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 bundle
:
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 friendly_id
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 id
:
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:
Author.find_each(&:save)
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 FriendlyId
module:
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’ ActiveRecord
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 finders
:
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 ActiveRecord
’s 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.
Duplicate Ids
By default, 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!
Slug Candidates
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 publishers
database, city
and country
:
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 Publisher
class:
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 name
and city
attributes, followed by the name
, city
, and country
attributes.
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 id
s 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 city
and 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 name
, city
, and country
, the 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 slug
attributes!
For example, if we had Article
instances that might allow for their title
s 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:
1 2 |
|
Now we just need to specify that our base method needs to use the history
addon:
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_id
gem.
tl;dr?
- The
friendly_id
gem 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_id
in its most basic capacity. - Curious about how that
friendly_id
base method works? Check out the source code.