Skip to content

Instantly share code, notes, and snippets.

@pat
Created December 20, 2014 02:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pat/e0d88b6ce8ee24f7c098 to your computer and use it in GitHub Desktop.
Save pat/e0d88b6ce8ee24f7c098 to your computer and use it in GitHub Desktop.
Slugging logic out of the model.
# For when you're adding the slug column to a model with existing records:
SlugGenerator.slug User, :slug
# Need a slug to be there, be unique, and follow an appropriate format.
validates :slug, presence: true, uniqueness: true,
format: {
with: /\A[a-z0-9\-]+\z/,
message: 'must be lowercase letters, numbers and dashes only'
}
# Generate slug by the name column
before_validation SlugCallback.new(:slug, [:name]), on: :create
# Update the slug if the name changes
before_validation SlugCallback.new(:slug, [:name]), on: :update, if: :name_changed?
class SlugCallback
attr_reader :column, :parts
def initialize(column, parts = [:name])
@column, @parts = column, parts
end
def before_validation(instance)
SlugGenerator.new(column, parts, instance).call
end
end
class SlugGenerator
CLEAN_REGEX_PAIRS = [[/[\s_]+/, '-'], [/[^\w\-]/, ''], [/\-+/, '-']]
def self.slug(klass, column, parts = [:name])
klass.where(column => nil).each do |instance|
new(column, parts, instance).call
instance.save!
end
end
def initialize(column, parts, instance)
@column, @parts, @instance = column, parts, instance
end
def call
instance.send "#{column}=", unique_slug
end
private
attr_reader :column, :parts, :instance
def generated_slug
slug_parts.compact.collect { |part| sluggify part }.join('-')
end
def sluggify(string)
CLEAN_REGEX_PAIRS.inject(string.downcase) do |string, pair|
string.gsub pair.first, pair.last
end
end
def slug_available?(slug)
existing = instance.class.where(column => slug).first
existing.nil? || existing.id == instance.id
end
def slug_parts
parts.collect { |part| instance.send part }
end
def unique_slug
slug = generated_slug
return slug if slug_available?(slug)
index = 1
index += 1 until slug_available?("#{slug}-#{index}")
"#{slug}-#{index}"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment