Examples

Table of contents

  1. Basic Usage
    1. Including in a Model
    2. Encoding and Decoding IDs
  2. URL Helpers with Encoded IDs
    1. Using PathParam
    2. Using SluggedPathParam
  3. Routes and Controllers
  4. Custom Annotations
  5. Persisting Encoded IDs
  6. Using the Sqids Encoder
  7. Per-Model Encoder Configuration
  8. Blocklist Configuration
    1. Global Blocklist
    2. Per-Model Blocklist
  9. Seamless ActiveRecord Integration
    1. In Controllers
  10. Combining Multiple Features
  11. Single Table Inheritance (STI)
    1. Example 1: Default Behavior (Separate Salts)
    2. Example 2: Shared Salt for Compatibility
    3. Example 3: API Endpoint with STI
    4. When to Share Salts in STI
  1. This page provides various examples of using EncodedId::Rails in different scenarios.

Basic Usage

Including in a Model

class User < ApplicationRecord
  include EncodedId::Rails::Model
end

Encoding and Decoding IDs

# Create a user
user = User.create(name: "John Doe")

# Get the encoded ID
user.encoded_id
# => "user_p5w9-z27j"

# Find by encoded ID
User.find_by_encoded_id("user_p5w9-z27j")
# => #<User id: 1, name: "John Doe">

# Works with just the hash part too
User.find_by_encoded_id("p5w9-z27j")
# => #<User id: 1, name: "John Doe">

# Find by encoded ID (raises if not found)
User.find_by_encoded_id!("user_p5w9-z27j")
# => #<User id: 1, name: "John Doe">

# Encode a specific ID
User.encode_encoded_id(123)
# => "p5w9-z27j"

# Decode an encoded ID
User.decode_encoded_id("user_p5w9-z27j")
# => [1]

URL Helpers with Encoded IDs

Using PathParam

class User < ApplicationRecord
  include EncodedId::Rails::Model
  include EncodedId::Rails::PathParam
end

# Then in routes
# resources :users

user = User.create(name: "John Doe")

# URL helpers will use encoded ID
Rails.application.routes.url_helpers.user_path(user)
# => "/users/user_p5w9-z27j"

Using SluggedPathParam

class User < ApplicationRecord
  include EncodedId::Rails::Model
  include EncodedId::Rails::SluggedPathParam
  
  def name_for_encoded_id_slug
    name.parameterize
  end
end

user = User.create(name: "John Doe")

# URL helpers will use slugged encoded ID
Rails.application.routes.url_helpers.user_path(user)
# => "/users/john-doe--user_p5w9-z27j"

Routes and Controllers

# In routes.rb
Rails.application.routes.draw do
  resources :users, param: :encoded_id
end

# In UsersController
class UsersController < ApplicationController
  def show
    @user = User.find_by_encoded_id!(params[:encoded_id])
    # Now @user contains the user found by encoded ID
  end
end

Custom Annotations

class User < ApplicationRecord
  include EncodedId::Rails::Model
  
  def annotation_for_encoded_id
    "usr"  # Custom annotation prefix
  end
end

user = User.create(name: "John Doe")
user.encoded_id
# => "usr_p5w9-z27j"

Persisting Encoded IDs

First, generate the migration:

rails generate encoded_id:rails:add_columns User

Then include the Persists module:

class User < ApplicationRecord
  include EncodedId::Rails::Model
  include EncodedId::Rails::Persists
end

# Create a user
user = User.create(name: "John Doe")

# Get the persisted encoded IDs
user.normalized_encoded_id  # => "p5w9z27j" (without formatting)
user.prefixed_encoded_id    # => "user_p5w9-z27j" (with annotation)

# Query by normalized encoded ID
User.where(normalized_encoded_id: "p5w9z27j").first
# => #<User id: 1, name: "John Doe">

Using the Sqids Encoder

First, add the sqids gem to your Gemfile:

# In your Gemfile
gem 'sqids'

Then configure EncodedId::Rails to use Sqids:

# In config/initializers/encoded_id.rb
EncodedId::Rails.configure do |config|
  config.salt = "your-application-salt"
  config.encoder = :sqids  # Use Sqids instead of HashIds
end

# In your model
class User < ApplicationRecord
  include EncodedId::Rails::Model
end

# Now encoded IDs will use Sqids
user = User.create(name: "John Doe")
user.encoded_id
# => "user_k6jR-8Myo"  # Different from HashIds encoding

# Finding works the same way
User.find_by_encoded_id("user_k6jR-8Myo")
# => #<User id: 1, name: "John Doe">

Per-Model Encoder Configuration

class Product < ApplicationRecord
  include EncodedId::Rails::Model
  
  # Override the encoded_id_coder method to use a different encoder for this model
  def self.encoded_id_coder(options = {})
    super(options.merge(encoder: :sqids))
  end
end

# Now Product models will use Sqids regardless of global configuration
product = Product.create(name: "Example Product")
product.encoded_id
# => "product_k6jR-8Myo"  # Uses Sqids

# But User models will use the global configuration
user = User.create(name: "John Doe")
user.encoded_id
# => "user_p5w9-z27j"  # Uses HashIds (if that's the global config)

Blocklist Configuration

Global Blocklist

# In config/initializers/encoded_id.rb
EncodedId::Rails.configure do |config|
  config.salt = "your-application-salt"
  config.blocklist = ["bad", "word", "offensive"]
end

Per-Model Blocklist

class Product < ApplicationRecord
  include EncodedId::Rails::Model
  
  # Override the encoded_id_coder method to use a custom blocklist
  def self.encoded_id_coder(options = {})
    super(options.merge(blocklist: ["product", "item"]))
  end
end

Seamless ActiveRecord Integration

The ActiveRecord module allows you to use encoded IDs with standard ActiveRecord finder methods:

class Product < ApplicationRecord
  include EncodedId::Rails::Model
  include EncodedId::Rails::ActiveRecord
end

# Create a product
product = Product.create(name: "Example Product")
encoded_id = product.encoded_id  # => "product_p5w9-z27j"

# Now you can use standard ActiveRecord methods with encoded IDs
Product.find(encoded_id)           # => #<Product id: 1, name: "Example Product">
Product.find_by_id(encoded_id)     # => #<Product id: 1, name: "Example Product">
Product.where(id: encoded_id)      # => #<ActiveRecord::Relation [#<Product id: 1>]>

# It will still work with regular IDs too
Product.find(1)                    # => #<Product id: 1, name: "Example Product">

# And with multiple IDs
multiple_encoded_id = Product.encode_encoded_id([1, 2, 3])
Product.find(multiple_encoded_id)  # => [#<Product id: 1>, #<Product id: 2>, #<Product id: 3>]

In Controllers

class ProductsController < ApplicationController
  # Your model must include EncodedId::Rails::ActiveRecord
  def show
    # Works with both numeric IDs and encoded IDs
    @product = Product.find(params[:id])
  end
  
  def bulk_update
    # Works with an encoded ID containing multiple IDs
    @products = Product.find(params[:ids])
    # Process @products...
  end
end

Important: This module should NOT be used with models that use string-based primary keys (e.g., UUIDs).

Combining Multiple Features

class Product < ApplicationRecord
  include EncodedId::Rails::Model
  include EncodedId::Rails::SluggedPathParam
  include EncodedId::Rails::Persists
  include EncodedId::Rails::ActiveRecord

  def name_for_encoded_id_slug
    name.parameterize
  end

  def self.encoded_id_coder(options = {})
    super(options.merge(
      encoder: :sqids,
      blocklist: ["offensive", "words"],
      id_length: 10
    ))
  end
end

# Now you have:
# 1. Slugged, encoded IDs in URLs
# 2. Persisted encoded IDs for efficient lookups
# 3. Seamless ActiveRecord integration
# 4. Custom encoder (Sqids)
# 5. Custom blocklist
# 6. Custom ID length

Single Table Inheritance (STI)

When using EncodedId with Single Table Inheritance, you need to decide whether child classes should share the same salt as the parent.

Example 1: Default Behavior (Separate Salts)

By default, each class in an STI hierarchy has its own salt:

class Vehicle < ApplicationRecord
  include EncodedId::Rails::Model
end

class Car < Vehicle
end

class Motorcycle < Vehicle
end

# Create vehicles
car = Car.create(make: "Toyota", model: "Camry")
motorcycle = Motorcycle.create(make: "Honda", model: "CBR")

# Each class has different encoded IDs for the same numeric ID
car.encoded_id
# => "car_p5w9-z27j"

motorcycle_id = motorcycle.id
Car.encode_encoded_id(motorcycle_id)
# => "car_x3k8-m9yz"  # Different encoding than motorcycle's

Motorcycle.encode_encoded_id(motorcycle_id)
# => "motorcycle_a7b2-q4wx"  # Different from Car's encoding

# Cross-class lookups won't work
Vehicle.find_by_encoded_id(car.encoded_id)
# => Won't find the car (different salt used for decoding)

Example 2: Shared Salt for Compatibility

To make encoded IDs work across the STI hierarchy, share the salt:

class Vehicle < ApplicationRecord
  include EncodedId::Rails::Model
  include EncodedId::Rails::SluggedPathParam

  def name_for_encoded_id_slug
    "#{make}-#{model}".parameterize
  end
end

class Car < Vehicle
  def self.encoded_id_salt
    # Use parent's salt for compatibility
    EncodedId::Rails::Salt.new(Vehicle, EncodedId::Rails.configuration.salt).generate!
  end
end

class Motorcycle < Vehicle
  def self.encoded_id_salt
    # Use parent's salt for compatibility
    EncodedId::Rails::Salt.new(Vehicle, EncodedId::Rails.configuration.salt).generate!
  end
end

# Now encoded IDs are compatible across the hierarchy
car = Car.create(make: "Toyota", model: "Camry")
motorcycle = Motorcycle.create(make: "Honda", model: "CBR")

# Parent can find children by their encoded IDs
Vehicle.find_by_encoded_id(car.encoded_id)
# => #<Car id: 1, make: "Toyota", model: "Camry">

Vehicle.find_by_encoded_id(motorcycle.encoded_id)
# => #<Motorcycle id: 2, make: "Honda", model: "CBR">

# Children can decode parent's encoded IDs
vehicle = Vehicle.create(make: "Generic", model: "Vehicle")
Car.decode_encoded_id(vehicle.encoded_id)
# => [3]  # Successfully decodes

# Query across hierarchy works
vehicle_ids = [car.encoded_id, motorcycle.encoded_id]
Vehicle.where_encoded_id(vehicle_ids)
# => [#<Car id: 1>, #<Motorcycle id: 2>]

Example 3: API Endpoint with STI

Here’s a practical example using STI with an API:

# Models
class Animal < ApplicationRecord
  include EncodedId::Rails::Model
  include EncodedId::Rails::ActiveRecordFinders
end

class Dog < Animal
  def self.encoded_id_salt
    EncodedId::Rails::Salt.new(Animal, EncodedId::Rails.configuration.salt).generate!
  end
end

class Cat < Animal
  def self.encoded_id_salt
    EncodedId::Rails::Salt.new(Animal, EncodedId::Rails.configuration.salt).generate!
  end
end

# Controller
class AnimalsController < ApplicationController
  def show
    # Accept encoded IDs for any animal type
    @animal = Animal.find(params[:id])  # Works for Dog, Cat, or Animal

    render json: {
      id: @animal.encoded_id,
      type: @animal.type,
      name: @animal.name
    }
  end

  def bulk_show
    # Accept multiple encoded IDs
    animal_ids = params[:ids]  # Array of encoded IDs
    @animals = Animal.where_encoded_id(animal_ids)

    render json: @animals.map { |animal|
      {
        id: animal.encoded_id,
        type: animal.type,
        name: animal.name
      }
    }
  end
end

# Usage
dog = Dog.create(name: "Buddy")
cat = Cat.create(name: "Whiskers")

# GET /animals/dog_p5w9-z27j
# => { id: "dog_p5w9-z27j", type: "Dog", name: "Buddy" }

# GET /animals/cat_a2k8-3xqz
# => { id: "cat_a2k8-3xqz", type: "Cat", name: "Whiskers" }

# POST /animals/bulk_show?ids[]=dog_p5w9-z27j&ids[]=cat_a2k8-3xqz
# => [
#      { id: "dog_p5w9-z27j", type: "Dog", name: "Buddy" },
#      { id: "cat_a2k8-3xqz", type: "Cat", name: "Whiskers" }
#    ]

When to Share Salts in STI

Share salts when:

  • You need a unified API that accepts any type in the hierarchy
  • Parent class needs to find children by their encoded IDs
  • You’re building flexible polymorphic endpoints
  • You want to query multiple types at once

Keep separate salts when:

  • You want strict type checking (additional safety)
  • Different types should never cross-reference
  • You want to prevent confusion between similar IDs of different types

EncodedId | Copyright © 2025. Licensed under the MIT License.