Skip to content

Instantly share code, notes, and snippets.

@pietromoro
Last active May 10, 2023 10:05
Show Gist options
  • Save pietromoro/6cfbd500b8cec4b79e1383f6ebccba32 to your computer and use it in GitHub Desktop.
Save pietromoro/6cfbd500b8cec4b79e1383f6ebccba32 to your computer and use it in GitHub Desktop.
Sluggable rails concern: use slugs instead of ids in the url!
module Sluggable
extend ActiveSupport::Concern
cattr_reader :slugging
included do
before_save :generate_slug
def self.sluggable
all.extending(Sluggable::Finder)
end
def self.slugging attribute
@@slugging = attribute.to_sym
end
end
def to_param?
return nil unless persisted?
slug
end
module Finder
def find(*args)
id = args.first
return super if args.count != 1
resource = resource_by_slug(id)
resource ||= super
raise ActiveRecord::RecordNotFound unless resource
resource
end
private
def resource_by_slug(id)
find_by(slug: id)
end
end
private
def generate_slug
return unless send("#{slugging}_changed?".to_sym)
self.slug = send(slugging).parameterize
end
end
@pietromoro
Copy link
Author

Usage

Add slug:string column to your model (e.g. Post)
$ rails g migration AddSlugToPost slug:string

Then in your model class

include Sluggable
# Set the attribute from which the slug would be generated
slugging :title

And in your controller do

Post.sluggable.find(params[:id])

@artyrcheek
Copy link

Awesome, thanks!

@LucasKendi
Copy link

Awesome!!!

@LucasKendi
Copy link

I ran into some issues implementing this. The @@slugging is a class variable shared throughout the classes that includes the Slug concern. This becomes an issue if I want to use the concern on more than one class, for example:

class Person < ApplicationRecord
  include Sluggable
  slugging :name
  
  ...
end

class Company < ApplicationRecord
  include Sluggable
  slugging :alias
end

When rails loads my classes, the slugging method sets the @@slugging == :name at first and then sets @@slugging == :alias, making it only work when I am dealing with Company objects. If I want to update the slug of a Person, I get a undefined method 'alias_changed?' error for a Person.

I think that the best approach would be defining the class variable for each class using the concern with an cattr_accessor.

@pietromoro
Copy link
Author

Hey! yes, you are right, I came across the same problem long time ago and always forgot to fix it in this gist. Did it now! Thanks for the heads up!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment