Natural Born Slugger
A gem for managing composite attributes, especially natural keys and url slugs.
Supports automatically-updated natural keys/slugs. Has ORM and Rack extensions.
Installation
Using bundler, add to your Gemfile
:
gem 'natural_born_slugger'
Otherwise:
gem install natural_born_slugger
and load it:
require 'natural_born_slugger'
Usage
Composing Attributes
At its core, NaturalBornSlugger
is a simple, well-tested DSL to compose dynamic attributes dependent on other fields:
Person = Struct.new(:first_name, :middle_name, :last_name) do
include NaturalBornSlugger
has_slug first_name: nil, middle_name: nil, last_name: nil
end
bambino = Person.new('George', 'Herman', 'Ruth')
bambino.slug #=> "GeorgeHermanRuth"
bambino.first_name, bambino.middle_name = 'Babe', nil
bambino.slug #=> "BabeRuth"
Setting Transformations
NaturalBornSlugger
also lets you set tranformations on each dependency:
Person = Struct.new(:id, :name) do
include NaturalBornSlugger
has_natural_key name: :dashify, id: :hashify, join_with: '-'
end
buster = Person.new(23, 'Lou Gehrig')
buster.natural_key #=> "lou-gehrig-37693cf"
buster.name = 'The Iron Horse'
buster.natural_key #=> "the-iron-horse-37693cf"
NaturalBornSlugger
interprets many objects as valid transformations:
Symbols:
- Converts dependency to string and sends the symbol tranformation to the
String
instance. NaturalBornSlugger
extendsString
with two methods:hashify
- an alias for ActiveSupports's parameterize methoddashify
- convert string to an MD5 hash and use the first 7 digits
- Examples:
:hashify
,:dashify
,:capitalize
,:chomp
,:downcase
,:reverse
,:squeeze
,:strip
,:upcase
, etc
Strings:
- Attempts to use the string tranformation as a format string for the dependency.
- If the string tranformation isn't a format string, it will replace the dependency in the slug, which probably isn't what you want.
- Examples:
id: "%05d"
,cost: "$%.02f"
Regexps:
- Scans the dependency using the regex tranformation and joins all matches.
- Joins matches with the same
join_with
used in the rest of the slug. - Examples: Can't think of any uses
Procs:
- Passes the dependency into the provided
lambda
orProc
. - Allows custom tranformations. The
Proc
must accept one parameter and return a string or nil. - Examples: compacting arrays of related objects, ie:
followers: ->(followers){ "%05d" % followers.count }
nil, false, or anything else:
- Convert dependency to string and leave untouched.
nil
is preferred.
Composed Attribute Options
After including NaturalBornSlugger
into your class, you can define an composite attribute through one of three methods:
has_slug
has_natural_key
has_composed_attribute
Each of these methods accepts the same arguments: a name and a hash of options. has_slug
and has_natural_key
will use default names of 'slug'
and 'natural_key'
respectively if you do not supply one. has_composed_attribute
requires the name.
The options hash is where you define the attribute's dependencies. It also uses special keys to customize the attribute's behavior:
:join_with
- String to use for joining dependencies.
- Default: ""
- Example:
Person = Struct.new(:first_name, :middle_name, :last_name) do
include NaturalBornSlugger
has_slug first_name: nil, middle_name: nil, last_name: nil, join_with: '-'
end
bambino = Person.new('Babe', nil, 'Ruth')
bambino.slug #=> "Babe-Ruth"
##### `:require_all`
- Checks to make sure all attributes exist before composing attribute.
- Makes attribute return nil until all dependencies exist.
- Default: false
- Example:
Person = Struct.new(:first_name, :middle_name, :last_name) do
include NaturalBornSlugger
has_slug first_name: nil, middle_name: nil, last_name: nil, require_all: true
end
bambino = Person.new('Babe', nil, 'Ruth')
bambino.slug #=> nil
bambino.first_name, bambino.middle_name = 'George', 'Herman'
bambino.slug #=> "George-Herman-Ruth"
##### `:callback`
- Provides a callback function `#{attribute_name}_change` to allow you to persist old slugs as you please.
- Default: false
- Example:
Person = Struct.new(:first_name, :middle_name, :last_name) do
include NaturalBornSlugger
attr_accessor :formerly_known_as
has_slug first_name: nil, middle_name: nil, last_name: nil, callback: ->(old_name, new_name) do
self.formerly_known_as ||= []
self.formerly_known_as << old_name
end
end
bambino = Person.new('George', 'Herman', 'Ruth')
bambino.slug #=> "GeorgeHermanRuth"
bambino.first_name, bambino.middle_name = 'Babe', nil
bambino.slug #=> "BabeRuth"
bambino.formerly_known_as #=> ["GeorgeHermanRuth"]
##### _Note:_
If you happen to have methods or attributes called `join_with`, `require_all`, or `callback`, you can still build composite attributes out of them. Just use a string instead of a symbol when declaring your attribute's dependencies. `NaturalBornSlugger` only looks for symbols when reading your dependencies for these settings.