Project

louisville

0.0
Repository is archived
No release in over 3 years
Low commit activity in last 3 years
A simple and extensible slugging library for ActiveRecord.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Runtime

 Project Readme

Louisville

This is not a Swiss Army Bulldozer. This is not a Pseudocephalopod. Contrary to popular belief, this was not written in Kentucky. This is a moderately simple, moderately extensible, moderately opinionated slugging library.

Usage

Just need the most basic of slugging?

add_column :players, :slug, :string
add_index  :players, :slug, unique: true
class Player < ActiveRecord::Base
  include Louisville::Slugger

  slug :name
end

Need a litte more? The slug class method accepts an options hash.

Option Key Option Value Default Value What it does
:column Any String "slug" Configures the slug column. "slug" is the default, provide to override.
:finder true true Adds the finder extension. The finder extension allows class.find('slug') to work.
:finder false true Removes the finder option, disabling the class.find override.
:collision :string_sequence :none Handles collisions by appending a sequence to the slug. A generated slug which collides with an existing slug will gain a "--number". So if there was a record with "foobar" as it's slug and another record generated the slug "foobar", the second record would save as "foobar--2".
:collision :numeric_sequence :none Handles collisions my incrementing a numeric column named "#{slug_column}_sequence". With this configuration, the slug column may not be unique but the [slug, slug_sequence] combination would be.
:setter Any Valid Ruby Method String false Allows the slug generation to be short circuited by providing a setter. Think about a user choosing their username or a page having an seo title. Collisions with the provided value will not be resolved, meaning a validation error will occur if an existing slug is provided.
:history true false When a record's slug changes this will create a record in the slugs table. The finder and collision resolver extensions respect the existence of the history table if this option is enabled.

Collision Resolvers

Two collision resolvers are included in Louisville. You can decide which to use based on the profile of your app. If you're app is read heavy and/or rarely colliding on write, :string_sequence is fine for you. If your app is write heavy or deals with collisions often, :numeric_sequence is a better choice.

collision: :string_sequence

To use the string sequence collision resolver, configure your schema like so:

add_column :players, :slug, :string
add_index  :players, :slug, unique: true

collision: :numeric_sequence

To use this collision resolver configure your schema like so:

add_column :players, :slug, :string
add_column :players, :slug_sequence, :integer, default: 1
add_index :players, [:slug, :slug_sequence], unique: true

Setter

I found this to be a shortcoming of other libraries and intended to make it dead simple to implement. Many times you want you users to be able to choose their slugs, skipping any kind of collision resolution. In Louisville it's simple:

class Player
  slug :name, setter: :desired_slug
end

Now, you can simply do player.desired_slug = params[:username] and if available the record's slug will be set, otherwise the record will have a validation error.

History

You'll need to create a slug table (no model) for this to work:

create_table :slugs do |t|
  t.string   :sluggable_type
  t.integer  :sluggable_id
  t.string   :slug_base
  t.integer  :slug_sequence, :default => 1
 end

Now with the table created, you can change a records slug and the previous value(s) will be stored in the slugs table. Note that the current slug is not stored in the table.

Creating your own extension

You can provide your own extension to Louisville by creating a module within the Louisville::Extensions namespace. For instance.

module Louisville
  module Extensions
    module Upcase
      self.included(base)
        base.class_eval do
          alias_method_chain :sanitize_louisville_slug, :upcase
        end
      end

      protected

      def sanitize_louisville_slug_with_upcase(value)
        value = sanitize_louisville_slug_without_upcase(value).upcase
        value = value.gsub(/[\d]+/, '') if louisville_config.options_for(:upcase)[:remove_numbers]
        value
      end
    end
  end
end

Then, in your class you would do:

class Player
  include Louisville::Slugger

  slug :name, upcase: true
  # or if you wanted to provide options for the module...
  slug :name, upcase: {remove_numbers: true}
end

Classes with the Louisville::Slugger module have access to the louisville_config both at the class and instance level. The louisville config provides you with a way to grab the options for your (or any) extension as well as determine what extensions are enabled. For instance, if you wanted to know if the history extension was being used you would do louisville_config.option?(:history).