Class: Noid::Minter

Inherits:
Object
  • Object
show all
Defined in:
lib/noid/minter.rb

Overview

Minters come in two varieties: stateful and stateless. A stateless minter – typically used with random rather than sequential templates, since minting in a sequence requires state to know the current position in the sequence – mints random identifiers and will mint duplicates eventually, depending upon the size of the identifier space in the provided template.

A stateful minter is a minter that has been initialized with parameters reflecting its current state. (A common way to store state between mintings is to call the minter ‘#dump` method which serializes the necessary parts of minter state to a hash, which may be persisted on disk or in other back-ends.) The parameters that are included are:

* template, a string setting the identifier pattern
* counters, a hash of "buckets" each with a current and max value
* seq, an integer reflecting how far into the sequence the minter is
* rand, a random number generator

Minters using random templates use a number of containers, each with a similar number of identifiers to split the identifier space into manageable chunks (or “buckets”) and to increase the appearance of randomness in the identifiers.

As an example, let’s assume a random identifier template that has 100 possible values. It might have 10 buckets, each with 10 identifiers that look similar because they have similar numeric values. Every call to ‘#mint` will use the random number generator stored in the minter’s state to select a bucket at random. Stateless minters will select a bucket at random as well.

The difference between stateless and stateful minters in this context is that stateful random minters are replayable as long as you have persisted the minter’s state, which includes a random number generator part of which is its original seed, which may be used over again in the future to replay the sequence of identifiers in this minter

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Minter

Returns a new instance of Minter.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/noid/minter.rb', line 40

def initialize(options = {})
  @template = Template.new(options[:template])

  @counters = options[:counters]
  @max_counters = options[:max_counters]

  # callback when an identifier is minted
  @after_mint = options[:after_mint]

  # used for random minters
  @rand = options[:rand] if options[:rand].is_a? Random
  @rand ||= Marshal.load(options[:rand]) if options[:rand]
  @rand ||= Random.new(options[:seed] || Random.new_seed)

  # used for sequential minters
  @seq = options[:seq] || 0
end

Instance Attribute Details

#countersObject

Counters to use for quasi-random NOID sequences



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/noid/minter.rb', line 119

def counters
  return @counters if @counters
  return [] unless random?

  percounter = template.max / (@max_counters || Noid::MAX_COUNTERS) + 1
  t = 0
  @counters = []

  while t < template.max
    counter = {}
    counter[:value] = t
    counter[:max] = [t + percounter, template.max].min

    t += percounter

    @counters << counter
  end

  @counters
end

#seqObject (readonly)

Returns the value of attribute seq.



37
38
39
# File 'lib/noid/minter.rb', line 37

def seq
  @seq
end

#templateObject (readonly)

Returns the value of attribute template.



37
38
39
# File 'lib/noid/minter.rb', line 37

def template
  @template
end

Instance Method Details

#dumpObject



140
141
142
143
144
145
146
147
# File 'lib/noid/minter.rb', line 140

def dump
  {
    template: template.template,
    counters: Marshal.load(Marshal.dump(counters)),
    seq: seq,
    rand: Marshal.dump(@rand) # we would Marshal.load this too, but serializers don't persist the internal state correctly
  }
end

#mintObject

Mint a new identifier



60
61
62
63
64
65
66
# File 'lib/noid/minter.rb', line 60

def mint
  n = next_in_sequence
  id = template.mint(n)
  next_sequence if random?
  @after_mint.call(self, id) if @after_mint
  id
end

#next_in_sequenceObject



92
93
94
95
96
97
98
# File 'lib/noid/minter.rb', line 92

def next_in_sequence
  if random?
    next_random
  else
    next_sequence
  end
end

#next_randomObject



100
101
102
103
104
105
106
107
# File 'lib/noid/minter.rb', line 100

def next_random
  raise 'Exhausted noid sequence pool' if counters.size == 0
  i = random_bucket
  n = counters[i][:value]
  counters[i][:value] += 1
  counters.delete_at(i) if counters[i][:value] == counters[i][:max]
  n
end

#next_sequenceObject



109
110
111
# File 'lib/noid/minter.rb', line 109

def next_sequence
  seq.tap { @seq += 1 }
end

#random?Boolean

Returns:

  • (Boolean)


149
150
151
# File 'lib/noid/minter.rb', line 149

def random?
  template.generator == 'r'
end

#random_bucketObject



113
114
115
# File 'lib/noid/minter.rb', line 113

def random_bucket
  @rand.rand(counters.size)
end

#remainingFixnum

Returns the number of identifiers remaining in the minter

Returns:

  • (Fixnum)


87
88
89
90
# File 'lib/noid/minter.rb', line 87

def remaining
  return Float::INFINITY if unbounded?
  template.max - seq
end

#seed(seed_number, sequence = 0) ⇒ Object

Reseed the RNG



70
71
72
73
74
# File 'lib/noid/minter.rb', line 70

def seed(seed_number, sequence = 0)
  @rand = Random.new(seed_number)
  sequence.times { next_random }
  @rand
end

#unbounded?Boolean

Returns:

  • (Boolean)


153
154
155
# File 'lib/noid/minter.rb', line 153

def unbounded?
  template.generator == 'z'
end

#valid?(id) ⇒ Boolean

Is the identifier valid under the template string and checksum?

Parameters:

  • id (String)

Returns:

  • (Boolean)

    bool



80
81
82
# File 'lib/noid/minter.rb', line 80

def valid?(id)
  template.valid?(id)
end