Module: ConcernsOnRails::Models::Tokenizable

Extended by:
ActiveSupport::Concern
Defined in:
lib/concerns_on_rails/models/tokenizable.rb

Overview

Generates and manages security tokens (API keys, invite codes, share links).

class User < ApplicationRecord
  include ConcernsOnRails::Tokenizable

  tokenizable_by :api_token                              # 32-char URL-safe
  tokenizable_by :reset_password_token, length: 24
  tokenizable_by :invite_code, type: :alphanumeric, length: 8
end

user = User.create!                       # tokens auto-generated on create
user.regenerate_api_token!                # new value, persisted
user.revoke_api_token!                    # sets the column to nil
user.api_token?                           # true if present

User.find_by_api_token(token)             # Rails default
User.authenticate_by_api_token(token)     # timing-safe lookup, returns record or nil

Unlike Hashable, one model can declare multiple token fields, generation is URL-safe by default, and ‘assign_tokenizable_value` retries on uniqueness collisions before insert (best-effort; pair with a unique DB index).

Constant Summary collapse

VALID_TYPES =
%i[urlsafe hex alphanumeric numeric].freeze
ALPHANUMERIC_ALPHABET =
(("A".."Z").to_a + ("a".."z").to_a + ("0".."9").to_a).freeze
NUMERIC_ALPHABET =
("0".."9").to_a.freeze
MAX_GENERATION_ATTEMPTS =
10

Instance Method Summary collapse

Instance Method Details

#assign_tokenizable_value(field) ⇒ Object

Assigns the generated value only when blank, so callers can pass an explicit one. Retries up to MAX_GENERATION_ATTEMPTS times if the in-Ruby uniqueness check hits a collision — useful for short codes; a unique DB index is still the real guarantee.



123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/concerns_on_rails/models/tokenizable.rb', line 123

def assign_tokenizable_value(field)
  return if self[field].present?

  MAX_GENERATION_ATTEMPTS.times do
    candidate = self.class.generate_tokenizable_value(field)
    unless self.class.unscoped.exists?(field => candidate)
      self[field] = candidate
      return
    end
  end

  raise "ConcernsOnRails::Models::Tokenizable: could not generate a unique value for '#{field}' " \
        "after #{MAX_GENERATION_ATTEMPTS} attempts — consider a longer length or a larger alphabet"
end