tokenize_attr

Declarative secure token generation for ActiveRecord model attributes.

tokenize_attr adds a tokenize class macro to ActiveRecord models. When no prefix is needed it transparently delegates to Rails' built-in has_secure_token (Rails 5+). When a prefix is required — or when has_secure_token is unavailable — it installs a before_create callback backed by SecureRandom.base58 with configurable size, prefix, and retry logic.

Installation

Add to your Gemfile:

gem "tokenize_attr"

Then run the install task to wire the gem into your Rails app:

rails tokenize_attr:install

This creates config/initializers/tokenize_attr.rb, which calls ActiveSupport.on_load(:active_record) so that every model gains the tokenize macro automatically.

To remove the initializer:

rails tokenize_attr:uninstall

Usage

Basic — delegates to has_secure_token

When no prefix is provided and the model supports has_secure_token (every Rails 5+ ApplicationRecord), the built-in Rails implementation is used automatically.

class User < ApplicationRecord
  tokenize :api_token
end

user = User.create!
user.api_token # => "aBcD1234..." (64 base58 chars by default)

With prefix

When a prefix: is given the gem installs its own before_create callback.

class AccessToken < ApplicationRecord
  tokenize :token, prefix: "tok", size: 32
end

AccessToken.create!.token # => "tok-aBcD1234..." ("tok-" + 32 random chars)

Custom size (no prefix)

class Session < ApplicationRecord
  tokenize :session_id, size: 128
end

Custom retry budget

class InviteCode < ApplicationRecord
  # Retry up to 5 times before raising TokenizeAttr::RetryExceededError
  tokenize :code, prefix: "inv", retries: 5
end

Custom generator

Pass any callable as the second argument (or as a block) to replace SecureRandom.base58 with your own algorithm. The callable receives size and must return a String.

# Proc as second positional argument
class Order < ApplicationRecord
  tokenize :reference, proc { |size| SecureRandom.alphanumeric(size) },
           prefix: "ord", size: 12
end

Order.create!.reference # => "ord-aB3cD4eF5gH6"
# Method reference via &
class Order < ApplicationRecord
  def self.reference_generator(size) = SecureRandom.alphanumeric(size)
  tokenize :reference, &method(:reference_generator)
end
# Inline block (parentheses required)
class Order < ApplicationRecord
  tokenize(:reference) { |size| SecureRandom.alphanumeric(size) }
end

Note: Providing a generator always uses the callback path — even when no prefix: is given. The generator takes precedence over has_secure_token.

Multiple tokenized attributes

class ApiCredential < ApplicationRecord
  tokenize :public_key,  prefix: "pk", size: 32
  tokenize :private_key, prefix: "sk", size: 64
end

Options

Option Default Description
generator nil Proc/block called as generator.call(size) → String.
Overrides the default SecureRandom.base58 algorithm.
size 64 Length passed to the generator or to SecureRandom.base58.
prefix nil String prepended as "prefix-<token>".
retries 3 Max uniqueness-check attempts (custom callback path only).

Note: retries is ignored when delegating to has_secure_token because Rails does not perform uniqueness checks there. Use a DB unique index to enforce uniqueness and let the DB raise on collision.

Error handling

When the custom callback exhausts all retry attempts it raises TokenizeAttr::RetryExceededError (a subclass of TokenizeAttr::Error < StandardError).

begin
  InviteCode.create!
rescue TokenizeAttr::RetryExceededError => e
  Rails.logger.error(e.message)
  # => "Could not generate a unique token for :code after 5 retries"
end

Rails integration

Run rails tokenize_attr:install once after adding the gem. The generated initializer (config/initializers/tokenize_attr.rb) hooks into ActiveSupport.on_load(:active_record) so every model gains the tokenize macro without an explicit include.

For non-Rails classes that provide before_create and exists? include the concern manually:

class MyModel
  include TokenizeAttr::Concern
end

Add a unique index on tokenized columns to enforce uniqueness at the DB level:

add_column :access_tokens, :token, :string
add_index  :access_tokens, :token, unique: true

Development

bin/setup              # install dependencies
bundle exec rake test  # run the test suite
bin/console            # interactive prompt

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pniemczyk/tokenize_attr.

License

The gem is available as open source under the terms of the MIT License.