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.
The gemspec includes the activerecord
version dependency, so the correct version of sluggi
will be installed to match your activerecord
version.
sluggi | activerecord |
---|---|
0.5 | 4.0-5.0 |
1.0 | 5.1-5.2 |
2.0 | 6.x |
3.0 | 7.x |
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
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.
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
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
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'
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