Sluggi
Sluggi is a simple friendly_id-inspired slugging library for ActiveRecord models. It is faster than friendly_id
(see below for benchmarks).
It provides basic slugs, slug history, and the ability to define multiple slug candidates.
Sluggi 1.x works with Rails 5.1+. Sluggi 0.5.x works with Rails 4.0-5.0.x. Make sure to view the 0.5-stable branch's Readme for those versions.
Install
Add this line to your Gemfile:
gem 'sluggi'
Add a string column named slug
to any models you want to slug. You can generate a migration like so:
rails generate migration AddSlugToCats slug:string:uniq:index
rake db:migrate
To track slug history for any model, you must generate a migration to add the slugs
table:
rails generate sluggi
rake db:migrate
Usage
Sluggi is Magic Free(tm). Each slugged model must:
- Have a column named
slug
(see above). - Include the
Sluggi::Slugged
module - Override the
slug_value
method or theslug_candidates
method.
Simple Slugged Model
Specify the slug value by defining #slug_value
and #slug_value_changed?
.
class Cat < ActiveRecord::Base
include Sluggi::Slugged
def slug_value
name
end
def slug_value_changed?
name_changed?
end
end
cat = Cat.create(name: 'Tuxedo Stan')
cat.slug
=> 'tuxedo-stan'
cat.to_param
=> 'tuxedo-stan'
cat_path
=> 'cats/tuxedo-stan'
Cat.find_by_slug('tuxedo-stan')
=> cat
Model with Slugged History
To save slug history, include Sluggi::History
. You get a slugs
association. You can search for any
slug in the history using .find_slug!
.
You must also implement #saved_change_to_slug_value?
.
class Cat < ActiveRecord::Base
include Sluggi::Slugged
include Sluggi::History
def slug_value
name
end
def slug_value_changed?
name_changed?
end
def saved_change_to_slug_value?
saved_change_to_name?
end
end
cat = Cat.create(name: 'Tuxedo Stan')
cat.slug
=> 'tuxedo-stan'
cat.name = 'Tuxedo Bob'
cat.save
cat.slug
=> 'tuxedo-bob'
# use .find_slug! to search slug history:
Cat.find_slug!('tuxedo-bob')
=> cat
Cat.find_slug!('tuxedo-stan')
=> cat
# plain finders will not search history:
Cat.find_by_slug('tuxedo-bob')
=> cat
Cat.find_by_slug('tuxedo-stan')
=> RecordNotFound
Model with Slug Candidates
Override #slug_candidates
to define cascading candidate values for slugs.
This is useful to avoid uniqueness conflicts. Do not override #slug_value
-
the default implementation in Model
will call #slug_candidates
and
works with or without History
.
class Cat < ActiveRecord::Base
include Sluggi::Slugged
# The first unused value in the list is used.
# Each item may be a value or a lambda.
# Use a lambda to defer expensive unique value calculations.
def slug_candidates
[
name,
-> { "#{name}-#{Cat.count}" }
]
end
def slug_value_changed?
name_changed?
end
end
cat = Cat.create(name: 'Tuxedo Stan')
cat.slug
=> 'tuxedo-stan'
cat_2 = Cat.create(name: 'Tuxedo Stan')
cat_2.slug
=> 'tuxedo-stan-456'
Performance
Run the benchmark script: ruby bench.rb
. This script is based on the
benchmark script from friendly_id
.
Here are some anecdotal results using ruby 2.5.3:
SLUGGI FRIENDLY_ID
1) find (id) - direct ActiveRecord 0.092318 0.093049
2) find (in-table slug) 0.102773 0.259542
3) find (in-table slug; using finders module) 0.098183 0.108248
4) find (external slug) 0.670229 0.832791
5) insert (plain AR / no slug) 0.345077 0.345105
6) insert (in-table-slug) 0.666451 0.815505
7) insert (in-table-slug; using finders module) 0.668737 0.744433
8) insert (external slug) 2.480790 2.849761
Notes:
Sluggi is at least 10% faster in every benchmark.
- Baseline (does not use either gem)
- 0.44x
- 0.90x
- 0.80x
- Baseline (does not use either gem)
- 0.82x
- 0.90x
- 0.87x