friendly_id

FriendlyId for Crystal - Create human-readable URLs and slugs

FriendlyId

A Crystal shard for creating human-readable URLs and slugs. FriendlyId lets you create pretty URLs and slugs for your resources, with support for history tracking and customization.

Crystal Test

Installation

  1. Add the dependency to your shard.yml:
dependencies:
  friendly_id:
    github: dcalixto/friendly_id

[!NOTE] Make sure your database table has a slug column:

ALTER TABLE posts ADD COLUMN slug VARCHAR;
  1. Run
shards install

Generate and run the required migrations:

crystal ../friendly_id/src/friendly_id/install.cr

This will create the necessary database tables and indexes for FriendlyId to work:

CREATE TABLE friendly_id_slugs (
  id BIGSERIAL PRIMARY KEY,
  slug VARCHAR NOT NULL,
  sluggable_id BIGINT NOT NULL,
  sluggable_type VARCHAR(50) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Setup

Configure FriendlyId in your application:

[!NOTE] set a initializer # friendly_id.cr

require "friendly_id"
FriendlyId.configure do |config|
config.migration_dir = "db/migrations"
end

Update your model's save method to include the generate_slug method:


class Post
  include FriendlyId::Slugged
  friendly_id :title
  # Model-level slug generation
  def save
  generate_slug  # Generate the slug before saving
    @updated_at = Time.utc

    if id
      @@db.exec <<-SQL, title, slug, body, user_id, created_at, updated_at, id
        UPDATE posts
        SET title = ?, slug = ?, body = ?, user_id = ?, created_at = ?, updated_at = ?
        WHERE id = ?
      SQL
    else
      @@db.exec <<-SQL, title, slug, body, user_id, created_at, updated_at
        INSERT INTO posts (title, slug, body, user_id, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?)
      SQL
    end
    self
  end
end

Or Update your controller save method to include the generate_slug method:

class PostsController
  def create(env)
    title = env.params.body["title"]
    body = env.params.body["body"]
    user_id = current_user(env).id

    post = Post.new(
      title: title,
      body: body,
      user_id: user_id
    )

    # Controller-level slug generation
    post.generate_slug # Generate the slug before saving

    if post.save
      env.redirect "/posts/#{post.slug}"
    else
      env.redirect "/posts/new"
    end
  end
end

Usage

Basic Slugging

class Post
  include FriendlyId::Slugged
  include FriendlyId::Finders

  property id : Int64?
  property title : String
  property slug : String?
end

post = Post.new("Hello World!")
post.slug # => "hello-world"

The Slug is Update Automatically

post = Post.new("Hello World!")
post.save
puts post.slug # => "hello-world"

post.title = "Updated Title"
post.save
puts post.slug # => "updated-title"

With History Tracking

class Post
  include FriendlyId::Slugged
  include FriendlyId::History

  property id : Int64?
  property title : String
  property slug : String?

  def initialize(@title)
  end
end

post = Post.new("Hello World!")
post.save
post.slug # => "hello-world"

post.title = "Updated Title"
post.save
post.slug_history # => ["hello-world"]

Using a Custom Attribute

class User
  include FriendlyId::Slugged

  property id : Int64?
  property name : String
  property slug : String?
  friendly_id :name  # Use name instead of title for slugs

  def initialize(@name); end
end

user = User.new("John Doe")
user.save
puts user.slug # => "john-doe"

Friendly ID Support

The FriendlyId::Finders module provides smart URL slug handling with ID and Historical Slug fallback:

lookup records by:

  • Current slug
  • Numeric ID
  • Historical slugs
class Post
  include FriendlyId::Finders
end

Finding Records

# All these will work:
Post.find_by_friendly_id("my-awesome-post")  # Current slug
Post.find_by_friendly_id("123")              # ID
Post.find_by_friendly_id("old-post-slug")    # Historical slug
# Regular find still works
post = Post.find(1)

Configuration

def should_generate_new_friendly_id?
  title_changed? || slug.nil?
end
class Post
  include DB::Serializable
  include FriendlyId::Slugged
  include FriendlyId::Finders
  include FriendlyId::History

  # ... your existing code ...

  def should_generate_new_friendly_id?
    title_changed? || slug.nil?
  end
end

Custom Slug Generation

class Post
  include FriendlyId::Slugged
   def normalize_friendly_id(value)
   value.downcase.gsub(/\s+/, "-")
  end
end

URL Helpers

To use friendly URLs in your controller, include the FriendlyId::UrlHelper module:

# In your Controller
include FriendlyId::UrlHelper
<a href="/posts/<%= friendly_path(post) %>">
  <%= post.title %>
</a>

Features

  • Slug generation from specified fields
  • SEO-friendly URL formatting
  • History tracking of slug changes
  • Custom slug normalization
  • Special character handling
  • Database-backed slug storage

Run tests

crystal spec

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors

Daniel Calixto - creator and maintainer

License

MIT License. See LICENSE for details.


Repository

friendly_id

Owner
Statistic
  • 1
  • 0
  • 0
  • 0
  • 3
  • about 14 hours ago
  • December 2, 2024
License

Links
Synced at

Sun, 22 Dec 2024 01:34:09 GMT

Languages