Class: EncodedId::Encoders::Hashid
- Inherits:
-
Object
- Object
- EncodedId::Encoders::Hashid
- Includes:
- HashidConsistentShuffle
- Defined in:
- lib/encoded_id/encoders/hashid.rb
Overview
Implementation of HashId, optimised and adapted from the original ‘hashid.rb` gem
Instance Attribute Summary collapse
-
#alphabet ⇒ Object
readonly
: Alphabet.
-
#alphabet_ordinals ⇒ Object
readonly
: Array.
-
#blocklist ⇒ Object
readonly
: Blocklist?.
-
#guard_ordinals ⇒ Object
readonly
: Array.
-
#min_hash_length ⇒ Object
readonly
: Integer.
-
#salt ⇒ Object
readonly
: String.
-
#salt_ordinals ⇒ Object
readonly
: Array.
-
#separator_ordinals ⇒ Object
readonly
: Array.
Instance Method Summary collapse
-
#decode(hash) ⇒ Array<Integer>
Decode a hash string back into an array of integers.
-
#encode(numbers) ⇒ String
Encode an array of non-negative integers into a hash string.
-
#initialize(salt, min_hash_length = 0, alphabet = Alphabet.alphanum, blocklist = nil, blocklist_mode = :length_threshold, blocklist_max_length = 32) ⇒ Hashid
constructor
Initialize a new HashId encoder with custom parameters.
Methods included from HashidConsistentShuffle
Constructor Details
#initialize(salt, min_hash_length = 0, alphabet = Alphabet.alphanum, blocklist = nil, blocklist_mode = :length_threshold, blocklist_max_length = 32) ⇒ Hashid
Initialize a new HashId encoder with custom parameters.
The initialization process sets up the character sets (alphabet, separators, guards) that will be used for encoding and decoding. These character sets are:
-
Shuffled based on the salt for uniqueness
-
Balanced in ratios (alphabet:separators ≈ 3.5:1, alphabet:guards ≈ 12:1)
-
Made disjoint (no character appears in multiple sets)
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/encoded_id/encoders/hashid.rb', line 103 def initialize(salt, min_hash_length = 0, alphabet = Alphabet.alphanum, blocklist = nil, blocklist_mode = :length_threshold, blocklist_max_length = 32) unless min_hash_length.is_a?(Integer) && min_hash_length >= 0 raise ArgumentError, "The min length must be a Integer and greater than or equal to 0" end @min_hash_length = min_hash_length @salt = salt @alphabet = alphabet @blocklist = blocklist @blocklist_mode = blocklist_mode @blocklist_max_length = blocklist_max_length @separators_and_guards = HashidOrdinalAlphabetSeparatorGuards.new(alphabet, salt) @alphabet_ordinals = @separators_and_guards.alphabet @separator_ordinals = @separators_and_guards.seps @guard_ordinals = @separators_and_guards.guards @salt_ordinals = @separators_and_guards.salt # Pre-compute escaped versions for use with String#tr during decoding. # This escapes special regex characters like '-', '\\', and '^' for safe use in tr(). @escaped_separator_selector = @separators_and_guards.seps_tr_selector @escaped_guards_selector = @separators_and_guards.guards_tr_selector end |
Instance Attribute Details
#alphabet ⇒ Object (readonly)
: Alphabet
131 132 133 |
# File 'lib/encoded_id/encoders/hashid.rb', line 131 def alphabet @alphabet end |
#alphabet_ordinals ⇒ Object (readonly)
: Array
126 127 128 |
# File 'lib/encoded_id/encoders/hashid.rb', line 126 def alphabet_ordinals @alphabet_ordinals end |
#blocklist ⇒ Object (readonly)
: Blocklist?
132 133 134 |
# File 'lib/encoded_id/encoders/hashid.rb', line 132 def blocklist @blocklist end |
#guard_ordinals ⇒ Object (readonly)
: Array
128 129 130 |
# File 'lib/encoded_id/encoders/hashid.rb', line 128 def guard_ordinals @guard_ordinals end |
#min_hash_length ⇒ Object (readonly)
: Integer
133 134 135 |
# File 'lib/encoded_id/encoders/hashid.rb', line 133 def min_hash_length @min_hash_length end |
#salt ⇒ Object (readonly)
: String
130 131 132 |
# File 'lib/encoded_id/encoders/hashid.rb', line 130 def salt @salt end |
#salt_ordinals ⇒ Object (readonly)
: Array
129 130 131 |
# File 'lib/encoded_id/encoders/hashid.rb', line 129 def salt_ordinals @salt_ordinals end |
#separator_ordinals ⇒ Object (readonly)
: Array
127 128 129 |
# File 'lib/encoded_id/encoders/hashid.rb', line 127 def separator_ordinals @separator_ordinals end |
Instance Method Details
#decode(hash) ⇒ Array<Integer>
Decode a hash string back into an array of integers.
The decoding process:
-
Removes guards by replacing them with spaces and splitting
-
Extracts the lottery character (first character after guard removal)
-
Splits on separators to get individual encoded number segments
-
For each segment, shuffles the alphabet the same way as encoding and decodes
-
Verifies by re-encoding the result and comparing to the original hash
This verification step is critical for valid decoding: it ensures that random strings won’t decode to valid numbers. Only properly encoded hashes will pass.
182 183 184 185 186 |
# File 'lib/encoded_id/encoders/hashid.rb', line 182 def decode(hash) return [] if hash.nil? || hash.empty? internal_decode(hash) end |
#encode(numbers) ⇒ String
Encode an array of non-negative integers into a hash string.
The encoding process:
-
Validates all numbers are integers and non-negative
-
Calculates a “lottery” character based on the input numbers
-
For each number, shuffles the alphabet and encodes the number in that custom base
-
Inserts separator characters between encoded numbers
-
Adds guards and padding if needed to meet minimum length
-
Validates the result doesn’t contain blocklisted words
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/encoded_id/encoders/hashid.rb', line 150 def encode(numbers) numbers.all? { |n| Integer(n) } return "" if numbers.empty? || numbers.any? { |n| n < 0 } encoded = internal_encode(numbers) if check_blocklist?(encoded) blocked_word = contains_blocklisted_word?(encoded) if blocked_word raise EncodedId::BlocklistError, "Generated ID '#{encoded}' contains blocklisted word: '#{blocked_word}'" end end encoded end |