https://github.com/dcalixto/friendly_id
FriendlyId for Crystal - Create human-readable URLs and slugs
https://github.com/dcalixto/friendly_id
crystal crystal-lang seo seo-optimization slug slug-generator slugify
Last synced: 5 months ago
JSON representation
FriendlyId for Crystal - Create human-readable URLs and slugs
- Host: GitHub
- URL: https://github.com/dcalixto/friendly_id
- Owner: dcalixto
- Created: 2024-12-02T04:59:11.000Z (over 1 year ago)
- Default Branch: master
- Last Pushed: 2024-12-22T01:25:14.000Z (over 1 year ago)
- Last Synced: 2025-03-30T23:13:51.596Z (12 months ago)
- Topics: crystal, crystal-lang, seo, seo-optimization, slug, slug-generator, slugify
- Language: Crystal
- Homepage: https://github.com/dcalixto/friendly_id
- Size: 4.17 MB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# 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.
[](https://github.com/dcalixto/friendly_id/actions/workflows/crystal-test.yml)
## Installation
1. Add the dependency to your `shard.yml`:
```yaml
dependencies:
friendly_id:
github: dcalixto/friendly_id
```
> [!NOTE]
> Make sure your database table has a slug column:
```yaml
ALTER TABLE posts ADD COLUMN slug VARCHAR;
```
2. Run
```yaml
shards install
```
Generate and run the required migrations:
```crystal
crystal ../friendly_id/src/friendly_id/install.cr
```
This will create the necessary database tables and indexes for FriendlyId to work:
```crystal
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
```crystal
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:
```crystal
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:
```crystal
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
```crystal
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
```crystal
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
```crystal
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
```crystal
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
```crystal
class Post
include FriendlyId::Finders
end
```
Finding Records
```crystal
# 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
```crystal
def should_generate_new_friendly_id?
title_changed? || slug.nil?
end
```
```crystal
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
```crystal
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:
```crystal
# In your Controller
include FriendlyId::UrlHelper
```
```crystal
<%= post.title %>
```
## 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
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.
```
```