Module: Synthra::DeterministicIds

Defined in:
lib/synthra/deterministic_ids.rb

Overview

Deterministic ID Assignment

Generates stable, reproducible IDs based on context. Useful for testing and seeding where you need consistent IDs.

Examples:

DSL syntax

User:
  id: uuid(variant: "user", index: $index)

Ruby API

Synthra::DeterministicIds.uuid(variant: "user", index: 1, seed: 42)
# => "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" (always the same)

Constant Summary collapse

NAMESPACE =

UUID namespace for Synthra (generated once, fixed)

"6ba7b810-9dad-11d1-80b4-00c04fd430c8"

Class Method Summary collapse

Class Method Details

.batch(count, type: :uuid, variant: nil, seed: nil, **options) ⇒ Array<String|Integer>

Generate deterministic IDs for a batch

Parameters:

  • count (Integer)

    number of IDs

  • type (Symbol) (defaults to: :uuid)

    ID type (:uuid, :integer, :short_id, :slug)

  • variant (String) (defaults to: nil)

    variant name

  • seed (Integer, nil) (defaults to: nil)

    random seed

Returns:

  • (Array<String|Integer>)

    array of IDs



117
118
119
120
121
# File 'lib/synthra/deterministic_ids.rb', line 117

def batch(count, type: :uuid, variant: nil, seed: nil, **options)
  count.times.map do |i|
    send(type, variant: variant, index: i, seed: seed, **options)
  end
end

.build_input(variant, index, seed) ⇒ Object (private)



125
126
127
128
129
130
131
# File 'lib/synthra/deterministic_ids.rb', line 125

def build_input(variant, index, seed)
  parts = [NAMESPACE]
  parts << variant.to_s if variant
  parts << index.to_s
  parts << seed.to_s if seed
  parts.join("-")
end

.format_as_uuid(hash) ⇒ Object (private)



133
134
135
136
137
138
# File 'lib/synthra/deterministic_ids.rb', line 133

def format_as_uuid(hash)
  # RFC 4122 v4: the leading "4" sets the version nibble; the 17th nibble must encode variant
  # 10xx (i.e. 8/9/a/b) or strict UUID validators reject it. Mask hash[16] into that range.
  variant = ((hash[16].to_i(16) & 0x3) | 0x8).to_s(16)
  "#{hash[0, 8]}-#{hash[8, 4]}-4#{hash[13, 3]}-#{variant}#{hash[17, 3]}-#{hash[20, 12]}"
end

.integer(variant: nil, index: 0, seed: nil, max: 2_147_483_647) ⇒ Integer

Generate a deterministic integer ID

Parameters:

  • variant (String) (defaults to: nil)

    variant name

  • index (Integer) (defaults to: 0)

    index

  • seed (Integer, nil) (defaults to: nil)

    random seed

  • max (Integer) (defaults to: 2_147_483_647)

    maximum value

Returns:

  • (Integer)

    deterministic ID



51
52
53
54
55
56
57
# File 'lib/synthra/deterministic_ids.rb', line 51

def integer(variant: nil, index: 0, seed: nil, max: 2_147_483_647)
  input = build_input(variant, index, seed)
  hash = Digest::MD5.hexdigest(input)
  
  # Convert first 8 bytes to integer
  hash[0, 16].to_i(16) % max
end

.short_id(variant: nil, index: 0, seed: nil, length: 11) ⇒ String

Generate a deterministic short ID (like YouTube IDs)

Parameters:

  • variant (String) (defaults to: nil)

    variant name

  • index (Integer) (defaults to: 0)

    index

  • seed (Integer, nil) (defaults to: nil)

    random seed

  • length (Integer) (defaults to: 11)

    ID length

Returns:

  • (String)

    deterministic short ID



67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/synthra/deterministic_ids.rb', line 67

def short_id(variant: nil, index: 0, seed: nil, length: 11)
  alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
  input = build_input(variant, index, seed)
  hash = Digest::SHA256.hexdigest(input)
  
  result = +""
  length.times do |i|
    byte = hash[(i * 2), 2].to_i(16)
    result << alphabet[byte % alphabet.length]
  end
  
  result
end

.slug(variant: nil, index: 0, seed: nil, words: 3) ⇒ String

Generate a deterministic slug

Parameters:

  • variant (String) (defaults to: nil)

    variant name

  • index (Integer) (defaults to: 0)

    index

  • seed (Integer, nil) (defaults to: nil)

    random seed

  • words (Integer) (defaults to: 3)

    number of words

Returns:

  • (String)

    deterministic slug



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/synthra/deterministic_ids.rb', line 89

def slug(variant: nil, index: 0, seed: nil, words: 3)
  word_list = %w[
    alpha beta gamma delta epsilon zeta eta theta iota kappa
    lambda mu nu xi omicron pi rho sigma tau upsilon
    able baker charlie delta echo foxtrot golf hotel india juliet
    quick brown fox jumps over lazy dog hello world test
  ]
  
  input = build_input(variant, index, seed)
  hash = Digest::SHA256.hexdigest(input)
  
  selected = []
  words.times do |i|
    byte = hash[(i * 2), 2].to_i(16)
    selected << word_list[byte % word_list.length]
  end
  
  selected.join("-")
end

.uuid(variant: nil, index: 0, seed: nil) ⇒ String

Generate a deterministic UUID

Examples:

DeterministicIds.uuid(variant: "user", index: 0)
# => "f47ac10b-58cc-4372-a567-0e02b2c3d479"

Parameters:

  • variant (String) (defaults to: nil)

    variant name (e.g., "user", "order")

  • index (Integer) (defaults to: 0)

    index for sequence

  • seed (Integer, nil) (defaults to: nil)

    random seed

Returns:

  • (String)

    deterministic UUID



35
36
37
38
39
40
41
# File 'lib/synthra/deterministic_ids.rb', line 35

def uuid(variant: nil, index: 0, seed: nil)
  input = build_input(variant, index, seed)
  hash = Digest::SHA256.hexdigest(input)
  
  # Format as UUID v4
  format_as_uuid(hash)
end