API Reference

Table of contents

  1. EncodedId::Rails::Model
    1. Instance Methods
      1. #encoded_id
      2. #slugged_encoded_id
      3. #annotation_for_encoded_id
      4. #name_for_encoded_id_slug
    2. Class Methods
      1. .find_by_encoded_id(encoded_id)
      2. .find_by_encoded_id!(encoded_id)
      3. .find_all_by_encoded_id(encoded_id)
      4. .where_encoded_id(*encoded_ids)
      5. .encode_encoded_id(id)
  2. EncodedId::Rails::PathParam
    1. Instance Methods
      1. #to_param
  3. EncodedId::Rails::SluggedPathParam
    1. Instance Methods
      1. #to_param
  4. EncodedId::Rails::ActiveRecordFinders
    1. Class Methods (Overridden)
      1. .find
      2. .find_by_id
    2. Example Usage
  5. EncodedId::Rails::Persists
    1. Instance Methods
      1. #set_normalized_encoded_id!
  6. EncodedId::Rails::Configuration
    1. Configuration Options
  7. EncodedId::Rails::AnnotatedId
  8. EncodedId::Rails::SluggedId
  9. Single Table Inheritance (STI) Considerations
    1. Default Behavior: Incompatible Encoded IDs
    2. Solution: Share Salt Across STI Hierarchy
    3. When to Use Shared Salt
    4. Important Notes

EncodedId::Rails::Model

The main module to include in your ActiveRecord models.

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

Including this module adds the following instance methods to your model:

Instance Methods

#encoded_id

Returns the encoded ID for this record, with optional annotation.

user = User.find(123)
user.encoded_id  # => "user_p5w9-z27j"

The annotation prefix is determined by the #annotation_for_encoded_id method, which by default returns the model’s parameterized name.

#slugged_encoded_id

Returns the encoded ID with a human-readable slug prepended.

user = User.find(123)
user.name = "John Doe"
user.slugged_encoded_id  # => "john-doe--user_p5w9-z27j"

The slug is generated by the #name_for_encoded_id_slug method, which must be implemented in your model.

#annotation_for_encoded_id

Returns the annotation prefix for the encoded ID. Override this method to customize.

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

user = User.find(123)
user.encoded_id  # => "usr_p5w9-z27j"

By default, returns the underscored model name.

#name_for_encoded_id_slug

Returns the string to use as the slug part of #slugged_encoded_id. Must be implemented in your model.

class User < ApplicationRecord
  include EncodedId::Rails::Model
  
  def name_for_encoded_id_slug
    full_name.presence || "user-#{id}"
  end
end

If not implemented, calling #slugged_encoded_id will raise an error.

Class Methods

.find_by_encoded_id(encoded_id)

Finds a record by its encoded ID.

# These all work:
User.find_by_encoded_id("user_p5w9-z27j")  # Annotation included
User.find_by_encoded_id("p5w9-z27j")       # Just the hash
User.find_by_encoded_id("john-doe--user_p5w9-z27j")  # Slugged version

Returns nil if no record is found.

.find_by_encoded_id!(encoded_id)

Same as find_by_encoded_id, but raises ActiveRecord::RecordNotFound if no record is found.

User.find_by_encoded_id!("user_p5w9-z27j")
# => #<User id: 123, name: "John Doe">

User.find_by_encoded_id!("invalid-id")
# => ActiveRecord::RecordNotFound

.find_all_by_encoded_id(encoded_id)

Finds all records with the given encoded ID. Useful when the encoded ID encodes multiple IDs.

# Encoded ID that contains multiple record IDs
User.find_all_by_encoded_id("7aq6-0zqw")
# => [#<User id: 78>, #<User id: 45>]

.where_encoded_id(*encoded_ids)

Returns an ActiveRecord relation for the given encoded ID(s). Accepts multiple arguments or an array of encoded IDs.

# Single encoded ID
User.where_encoded_id("user_p5w9-z27j").where(active: true)

# Multiple encoded IDs as arguments
User.where_encoded_id("user_p5w9-z27j", "user_a2k8-3xqz")

# Array of encoded IDs
encoded_ids = ["user_p5w9-z27j", "user_a2k8-3xqz"]
User.where_encoded_id(encoded_ids)

# Mix with other query methods
User.where_encoded_id("user_p5w9-z27j", "user_a2k8-3xqz").where(active: true).order(:created_at)

This method handles all encoded ID formats:

  • Annotated IDs: "user_p5w9-z27j"
  • Slugged IDs: "john-doe--user_p5w9-z27j"
  • Hash only: "p5w9-z27j"

If any encoded ID is blank/nil, this will raise ActiveRecord::RecordNotFound. If an encoded ID fails to decode, it returns an empty relation.

.encode_encoded_id(id)

Encodes a record ID using the model’s configuration.

User.encode_encoded_id(123)  # => "p5w9-z27j"

EncodedId::Rails::PathParam

Module to include in your model to make it use encoded IDs in URL helpers.

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

user = User.find(123)
user.to_param  # => "user_p5w9-z27j"

Instance Methods

#to_param

Returns the encoded ID for use in URLs.

# In routes:
# resources :users, param: :encoded_id

# In views:
link_to "View User", user_path(user)  # => "/users/user_p5w9-z27j"

EncodedId::Rails::SluggedPathParam

Module to include in your model to make it use slugged encoded IDs in URL helpers.

class User < ApplicationRecord
  include EncodedId::Rails::Model
  include EncodedId::Rails::SluggedPathParam
  
  def name_for_encoded_id_slug
    full_name
  end
end

user = User.find(123)
user.name = "John Doe"
user.to_param  # => "john-doe--user_p5w9-z27j"

Instance Methods

#to_param

Returns the slugged encoded ID for use in URLs.

# In routes:
# resources :users, param: :encoded_id

# In views:
link_to "View User", user_path(user)  # => "/users/john-doe--user_p5w9-z27j"

EncodedId::Rails::ActiveRecordFinders

Module to include in your model to automatically handle encoded IDs in standard ActiveRecord finder methods.

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

Important: This module should NOT be used with models that use string-based primary keys (e.g., UUIDs) as it will cause conflicts between string IDs and encoded IDs.

Class Methods (Overridden)

.find

Overrides the default ActiveRecord find method to automatically handle encoded IDs.

# Standard ActiveRecord finds by ID
Product.find(123)  # => #<Product id: 123>

# Now also works with encoded IDs
Product.find("product_p5w9-z27j")  # => #<Product id: 123>
Product.find("p5w9-z27j")  # => #<Product id: 123>
Product.find("cool-product--product_p5w9-z27j")  # => #<Product id: 123>

# Also handles encoded IDs containing multiple IDs
Product.find("z2j7-0dmw")  # => [#<Product id: 78>, #<Product id: 45>]

Raises ActiveRecord::RecordNotFound if the ID can’t be found or if the encoded ID fails to decode.

.find_by_id

Overrides the default ActiveRecord find_by_id method to handle encoded IDs.

# Standard behavior
Product.find_by_id(123)  # => #<Product id: 123>

# Now handles encoded IDs
Product.find_by_id("product_p5w9-z27j")  # => #<Product id: 123>

# Returns nil for non-existent records (instead of raising an error)
Product.find_by_id("invalid-id")  # => nil

Example Usage

class Product < ApplicationRecord
  include EncodedId::Rails::Model
  include EncodedId::Rails::ActiveRecordFinders
  include EncodedId::Rails::SluggedPathParam  # Optional
end

# Create a product and get its encoded ID
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">

# Use with slugged IDs
slugged_id = product.slugged_encoded_id  # => "example-product--product_p5w9-z27j"
Product.find(slugged_id)                 # => #<Product id: 1, name: "Example Product">

# In controllers, you can simply use params[:id] directly
def show
  @product = Product.find(params[:id])  # Works with regular IDs and encoded IDs
end

EncodedId::Rails::Persists

Module to include in your model to persist encoded IDs in the database.

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

Requires the following database columns:

  • normalized_encoded_id (string): The encoded ID without formatting
  • prefixed_encoded_id (string): The complete encoded ID with annotation

Instance Methods

#set_normalized_encoded_id!

Updates the persisted encoded ID columns for this record.

user = User.find(123)
user.set_normalized_encoded_id!

This method is called automatically when a record is created.

EncodedId::Rails::Configuration

Configuration for the Rails integration.

# In config/initializers/encoded_id.rb
EncodedId::Rails.configure do |config|
  config.salt = "my-secret-salt"
  config.min_hash_length = 8
  config.split_at = 4
  config.split_with = "-"
  config.alphabet = EncodedId::Alphabet.modified_crockford
  config.annotation_method_name = :annotation_for_encoded_id
  config.annotated_id_separator = "_"
  config.slug_value_method_name = :name_for_encoded_id_slug
  config.slugged_id_separator = "--"
  config.model_to_param_returns_encoded_id = false
end

Configuration Options

Option Type Default Description
salt String Required The salt used for encoding
id_length Integer 8 Minimum length of the encoded hash
character_group_size Integer or nil 4 Split encoded string every X characters
group_separator String or nil "-" Character to split with
alphabet EncodedId::Alphabet EncodedId::Alphabet.modified_crockford The alphabet to use for encoding
annotation_method_name Symbol or nil :annotation_for_encoded_id Method to call for annotation prefix
annotated_id_separator String "_" Separator between annotation and ID
slug_value_method_name Symbol :name_for_encoded_id_slug Method to call for slug value
slugged_id_separator String "--" Separator between slug and ID
model_to_param_returns_encoded_id Boolean false Whether all models should override to_param
encoder Symbol :hashids ID encoding engine (:hashids or :sqids)
blocklist Array, Set, or nil nil Words to prevent in encoded IDs
hex_digit_encoding_group_size Integer 4 For hex encoding (experimental)

EncodedId::Rails::AnnotatedId

Class for creating annotated IDs.

annotated_id = EncodedId::Rails::AnnotatedId.new(
  id_part: "p5w9-z27j",
  annotation: "user",
  separator: "_"
)
annotated_id.annotated_id  # => "user_p5w9-z27j"

EncodedId::Rails::SluggedId

Class for creating slugged IDs.

slugged_id = EncodedId::Rails::SluggedId.new(
  id_part: "user_p5w9-z27j",
  slug_part: "john-doe",
  separator: "--"
)
slugged_id.slugged_id  # => "john-doe--user_p5w9-z27j"

Single Table Inheritance (STI) Considerations

When using encoded_id with ActiveRecord Single Table Inheritance (STI), there’s an important consideration regarding salt generation and encoded ID compatibility.

Default Behavior: Incompatible Encoded IDs

By default, each class in an STI hierarchy generates its own unique salt based on the class name. This means:

  • Encoded IDs are not compatible across classes in the same inheritance hierarchy
  • A parent class cannot decode a child class’s encoded ID
  • A child class cannot decode a parent class’s encoded ID
  • Sibling classes cannot decode each other’s encoded IDs

Example:

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

class Dog < Animal
end

class Cat < Animal
end

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

# Each class has its own salt
Animal.encoded_id_salt  # => "Animal/your-configured-salt"
Dog.encoded_id_salt     # => "Dog/your-configured-salt"
Cat.encoded_id_salt     # => "Cat/your-configured-salt"

# Encoded IDs are incompatible
dog_encoded_id = dog.encoded_id

# Parent class cannot decode child's ID correctly
Animal.decode_encoded_id(dog_encoded_id)  # => Different ID than dog.id

# Finder methods won't work across classes
Animal.find_by_encoded_id(dog_encoded_id)  # => Won't find the dog

Solution: Share Salt Across STI Hierarchy

To make encoded IDs compatible across an STI hierarchy, override the encoded_id_salt method to return the same salt for all classes:

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

class Dog < Animal
  def self.encoded_id_salt
    # Use the parent class's salt
    EncodedId::Rails::Salt.new(Animal, EncodedId::Rails.configuration.salt).generate!
  end
end

class Cat < Animal
  def self.encoded_id_salt
    # Use the parent class's salt
    EncodedId::Rails::Salt.new(Animal, EncodedId::Rails.configuration.salt).generate!
  end
end

# Now all classes share the same salt
Animal.encoded_id_salt == Dog.encoded_id_salt  # => true
Animal.encoded_id_salt == Cat.encoded_id_salt  # => true

# Encoded IDs are now compatible
dog = Dog.create(name: "Buddy")
dog_encoded_id = dog.encoded_id

# Parent class can decode child's ID
Animal.decode_encoded_id(dog_encoded_id)  # => [dog.id]

# Finder methods work (parent can query children due to STI)
Animal.find_by_encoded_id(dog_encoded_id)  # => #<Dog id: 1, name: "Buddy">

When to Use Shared Salt

Use shared salt if:

  • You need to pass encoded IDs between different classes in the hierarchy
  • Your API accepts encoded IDs that could be from any class in the hierarchy
  • You want a single encoded ID format for the entire hierarchy

Keep separate salts if:

  • You want to prevent cross-class ID usage (additional layer of type safety)
  • Different classes in the hierarchy should have completely independent encoded ID spaces
  • You’re not passing encoded IDs between classes

Important Notes

  1. Annotations still differ: Even with shared salts, each class has its own annotation (e.g., animal_p5w9-z27j vs dog_p5w9-z27j)

  2. ActiveRecord scoping: Remember that ActiveRecord still applies normal STI scoping:
    • Parent class queries can see child records
    • Child class queries cannot see parent records (where type is the parent class name)
    • Sibling class queries cannot see each other’s records
  3. Consistency is key: If you share salts, ensure all classes in the hierarchy use the same base class for salt generation
# Good: Consistent base class
class Animal < ApplicationRecord
  include EncodedId::Rails::Model
end

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

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

# Bad: Inconsistent base classes
class Puppy < Dog
  def self.encoded_id_salt
    EncodedId::Rails::Salt.new(Dog, EncodedId::Rails.configuration.salt).generate!  # ❌ Different from siblings
  end
end

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