Best way to generate slugs (human-readable IDs) in Rails

Ruby on-RailsSlug

Ruby on-Rails Problem Overview


You know, like myblog.com/posts/donald-e-knuth.

Should I do this with the built in parameterize method?

What about a plugin? I could imagine a plugin being nice for handling duplicate slugs, etc. Here are some popular Github plugins -- does anyone have any experience with them?

  1. http://github.com/rsl/stringex/tree/master
  2. http://github.com/norman/friendly_id/tree/master

Basically it seems like slugs are a totally solved problem, and I don't to reinvent the wheel.

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

In Rails you can use #parameterize

For example:

> "Foo bar`s".parameterize 
=> "foo-bar-s"

Solution 2 - Ruby on-Rails

The best way to generate slugs is to use the Unidecode gem. It has by far the largest transliteration database available. It has even transliterations for Chinese characters. Not to mention covering all European languages (including local dialects). It guarantees a bulletproof slug creation.

For example, consider those:

"Iñtërnâtiônàlizætiøn".to_slug
=> "internationalizaetion"

>> "中文測試".to_slug
=> "zhong-wen-ce-shi"

I use it in my version of the String.to_slug method in my ruby_extensions plugin. See ruby_extensions.rb for the to_slug method.

Solution 3 - Ruby on-Rails

I use the following, which will

  • translate & --> "and" and @ --> "at"
  • doesn't insert an underscore in place of an apostrophe, so "foo's" --> "foos"
  • doesn't include double-underscores
  • doesn't create slug that begins or ends with an underscore


def to_slug
#strip the string
ret = self.strip



#blow away apostrophes
ret.gsub! /['`]/,""

# @ --> at, and & --> and
ret.gsub! /\s*@\s*/, " at "
ret.gsub! /\s*&\s*/, " and "

#replace all non alphanumeric, underscore or periods with underscore
 ret.gsub! /\s*[^A-Za-z0-9\.\-]\s*/, '_'  
 
 #convert double underscores to single
 ret.gsub! /_+/,"_"

 #strip off leading/trailing underscore
 ret.gsub! /\A[_\.]+|[_\.]+\z/,""
 
 ret




end

end

so, for example:


>> s = "mom & dad @home!"
=> "mom & dad @home!"
>> s.to_slug
> "mom_and_dad_at_home"

Solution 4 - Ruby on-Rails

Here is what I use:

class User < ActiveRecord::Base
  before_create :make_slug
  private
  
  def make_slug
    self.slug = self.name.downcase.gsub(/[^a-z1-9]+/, '-').chomp('-')
  end
end

Pretty self explanatory, although the only problem with this is if there is already the same one, it won't be name-01 or something like that.

Example:

".downcase.gsub(/[^a-z1-9]+/, '-').chomp('-')".downcase.gsub(/[^a-z1-9]+/, '-').chomp('-')

Outputs: -downcase-gsub-a-z1-9-chomp

Solution 5 - Ruby on-Rails

I modified it a bit to create dashes instead of underscores, if anyone is interested:

def to_slug(param=self.slug)

    # strip the string
    ret = param.strip

    #blow away apostrophes
    ret.gsub! /['`]/, ""

    # @ --> at, and & --> and
    ret.gsub! /\s*@\s*/, " at "
    ret.gsub! /\s*&\s*/, " and "

    # replace all non alphanumeric, periods with dash
    ret.gsub! /\s*[^A-Za-z0-9\.]\s*/, '-'

    # replace underscore with dash
    ret.gsub! /[-_]{2,}/, '-'

    # convert double dashes to single
    ret.gsub! /-+/, "-"

    # strip off leading/trailing dash
    ret.gsub! /\A[-\.]+|[-\.]+\z/, ""

    ret
  end

Solution 6 - Ruby on-Rails

The main issue for my apps has been the apostrophes - rarely do you want the -s sitting out there on it's own.

class String

  def to_slug
    self.gsub(/['`]/, "").parameterize
  end

end

Solution 7 - Ruby on-Rails

The Unidecoder gem hasn't been updated since 2007.

I'd recommend the stringex gem, which includes the functionality of the Unidecoder gem.

https://github.com/rsl/stringex

Looking at it's source code, it seems to repackage the Unidecoder source code and add new functionality.

Solution 8 - Ruby on-Rails

We use to_slug http://github.com/ludo/to_slug/tree/master. Does everything we need it to do (escaping 'funky characters'). Hope this helps.

EDIT: Seems to be breaking my link, sorry about that.

Solution 9 - Ruby on-Rails

Recently I had the same dilemma.

Since, like you, I don't want to reinvent the wheel, I chose friendly_id following the comparison on The Ruby Toolbox: Rails Permalinks & Slugs.

I based my decision on:

  • number of github watchers
  • no. of github forks
  • when was the last commit made
  • no. of downloads

Hope this helps in taking the decision.

Solution 10 - Ruby on-Rails

I found the Unidecode gem to be much too heavyweight, loading nearly 200 YAML files, for what I needed. I knew iconv had some support for the basic translations, and while it isn't perfect, it's built in and fairly lightweight. This is what I came up with:

require 'iconv' # unless you're in Rails or already have it loaded
def slugify(text)
  text.downcase!
  text = Iconv.conv('ASCII//TRANSLIT//IGNORE', 'UTF8', text)

  # Replace whitespace characters with hyphens, avoiding duplication
  text.gsub! /\s+/, '-'

  # Remove anything that isn't alphanumeric or a hyphen
  text.gsub! /[^a-z0-9-]+/, ''

  # Chomp trailing hyphens
  text.chomp '-'
end

Obviously you should probably add it as an instance method on any objects you'll be running it on, but for clarity, I didn't.

Solution 11 - Ruby on-Rails

With Rails 3, I've created an initializer, slug.rb, in which I've put the following code:

class String
  def to_slug
    ActiveSupport::Inflector.transliterate(self.downcase).gsub(/[^a-zA-Z0-9]+/, '-').gsub(/-{2,}/, '-').gsub(/^-|-$/, '')
  end
end

Then I use it anywhere I want in the code, it is defined for any string.

The transliterate transforms things like é,á,ô into e,a,o. As I am developing a site in portuguese, that matters.

Solution 12 - Ruby on-Rails

I know this question has some time now. However I see some relatively new answers.

Saving the slug on the database is problematic, and you save redundant information that is already there. If you think about it, there is no reason for saving the slug. The slug should be logic, not data.

I wrote a post following this reasoning, and hope is of some help.

http://blog.ereslibre.es/?p=343

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTom LehmanView Question on Stackoverflow
Solution 1 - Ruby on-RailsgrosserView Answer on Stackoverflow
Solution 2 - Ruby on-RailsPaweł GościckiView Answer on Stackoverflow
Solution 3 - Ruby on-RailsklochnerView Answer on Stackoverflow
Solution 4 - Ruby on-RailsGarrettView Answer on Stackoverflow
Solution 5 - Ruby on-RailsVictor SView Answer on Stackoverflow
Solution 6 - Ruby on-RailsMark SwardstromView Answer on Stackoverflow
Solution 7 - Ruby on-RailsTiloView Answer on Stackoverflow
Solution 8 - Ruby on-RailstheIVView Answer on Stackoverflow
Solution 9 - Ruby on-RailsMarius ButucView Answer on Stackoverflow
Solution 10 - Ruby on-RailscoreywardView Answer on Stackoverflow
Solution 11 - Ruby on-RailsThiago GanzarolliView Answer on Stackoverflow
Solution 12 - Ruby on-RailsereslibreView Answer on Stackoverflow