noid 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 50e4fa485ed73bb6f4d313b5fda35a6ecfeb08e7
4
- data.tar.gz: 763d9e2e9f279a0afa7461b9f2ddee88d00e1318
3
+ metadata.gz: acd76906cdc40cfe6803fcf7ca54bf88ce0ca981
4
+ data.tar.gz: 90961988a2c041a1c7946cb57c61923fa303f3ac
5
5
  SHA512:
6
- metadata.gz: b5d19a31918d22133f8c282e3deb6baab51e27cbe7fb65797a503bffff474b34964bef5d9e81d657f591653d807e0be33b5ab2bab644ac633a9904561fecf66c
7
- data.tar.gz: 48e622908ddedc36c1296ecdbb9818694f7f3a680f5d610fc9115254d630bcb6489519bf00903ad1f6d751254fa5816ab4c989a93014b73439b1d99c8dae73d9
6
+ metadata.gz: 3c481bd56ec9435a09875416e0e24751f07f348eebf1234ede326bef81a031582e9574fa51e457507d231696724cc167e1978e39aeb88c6672a625766e58ea6e
7
+ data.tar.gz: c93b2e4e8a68a6bc7c17bafbe8131dbe479fdfcaff85954aa0712a050882a1fcbbd6c4c3118ba6637859f908b7600639a56d84df62cc866d19268989ffa5c2d4
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.2
1
+ 0.8.0
@@ -1,19 +1,58 @@
1
1
  module Noid
2
+ # Minters come in two varieties: stateful and stateless. A stateless minter --
3
+ # typically used with random rather than sequential templates, since minting
4
+ # in a sequence requires state to know the current position in the sequence --
5
+ # mints random identifiers and **will** mint duplicates eventually, depending
6
+ # upon the size of the identifier space in the provided template.
7
+ #
8
+ # A stateful minter is a minter that has been initialized with parameters
9
+ # reflecting its current state. (A common way to store state between mintings
10
+ # is to call the minter `#dump` method which serializes the necessary parts of
11
+ # minter state to a hash, which may be persisted on disk or in other
12
+ # back-ends.) The parameters that are included are:
13
+ #
14
+ # * template, a string setting the identifier pattern
15
+ # * counters, a hash of "buckets" each with a current and max value
16
+ # * seq, an integer reflecting how far into the sequence the minter is
17
+ # * rand, a random number generator
18
+ #
19
+ # Minters using random templates use a number of containers, each with a
20
+ # similar number of identifiers to split the identifier space into manageable
21
+ # chunks (or "buckets") and to increase the appearance of randomness in the
22
+ # identifiers.
23
+ #
24
+ # As an example, let's assume a random identifier template that has 100
25
+ # possible values. It might have 10 buckets, each with 10 identifiers that
26
+ # look similar because they have similar numeric values. Every call to `#mint`
27
+ # will use the random number generator stored in the minter's state to select
28
+ # a bucket at random. Stateless minters will select a bucket at random as
29
+ # well.
30
+ #
31
+ # The difference between stateless and stateful minters in this context is
32
+ # that stateful random minters are *replayable* as long as you have persisted
33
+ # the minter's state, which includes a random number generator part of which
34
+ # is its original seed, which may be used over again in the future to replay
35
+ # the sequence of identifiers in this minter
2
36
  class Minter
3
- attr_reader :seed, :seq, :template
37
+ attr_reader :template, :seq
4
38
  attr_writer :counters
5
39
 
6
40
  def initialize(options = {})
7
- if options[:state]
8
- # Only set the sequence ivar if this is a stateful minter
9
- @seq = options[:seq]
10
- else
11
- @max_counters = options[:max_counters]
12
- @after_mint = options[:after_mint]
13
- end
14
- @counters = options[:counters]
15
41
  @template = Template.new(options[:template])
16
- seed(options[:seed], options[:seq])
42
+
43
+ @counters = options[:counters]
44
+ @max_counters = options[:max_counters]
45
+
46
+ # callback when an identifier is minted
47
+ @after_mint = options[:after_mint]
48
+
49
+ # used for random minters
50
+ @rand = options[:rand] if options[:rand].is_a? Random
51
+ @rand ||= Marshal.load(options[:rand]) if options[:rand]
52
+ @rand ||= Random.new(options[:seed] || Random.new_seed)
53
+
54
+ # used for sequential minters
55
+ @seq = options[:seq] || 0
17
56
  end
18
57
 
19
58
  ##
@@ -21,10 +60,19 @@ module Noid
21
60
  def mint
22
61
  n = next_in_sequence
23
62
  id = template.mint(n)
63
+ next_sequence if random?
24
64
  @after_mint.call(self, id) if @after_mint
25
65
  id
26
66
  end
27
67
 
68
+ ##
69
+ # Reseed the RNG
70
+ def seed(seed_number, sequence = 0)
71
+ @rand = Random.new(seed_number)
72
+ sequence.times { next_random }
73
+ @rand
74
+ end
75
+
28
76
  ##
29
77
  # Is the identifier valid under the template string and checksum?
30
78
  # @param [String] id
@@ -34,25 +82,18 @@ module Noid
34
82
  end
35
83
 
36
84
  ##
37
- # Seed the random number generator with a seed and sequence offset
38
- # @return [Random]
39
- def seed(seed_number = nil, seq = 0)
40
- @rand = seed_number ? Random.new(seed_number) : Random.new
41
- @seed = @rand.seed
42
- @seq = seq || 0
43
-
44
- seq.times { next_random } if seq
45
-
46
- @rand
85
+ # Returns the number of identifiers remaining in the minter
86
+ # @return [Fixnum]
87
+ def remaining
88
+ return Float::INFINITY if unbounded?
89
+ template.max - seq
47
90
  end
48
91
 
49
92
  def next_in_sequence
50
- n = @seq
51
- @seq += 1
52
- if template.generator == 'r'
93
+ if random?
53
94
  next_random
54
95
  else
55
- n
96
+ next_sequence
56
97
  end
57
98
  end
58
99
 
@@ -65,6 +106,10 @@ module Noid
65
106
  n
66
107
  end
67
108
 
109
+ def next_sequence
110
+ seq.tap { @seq += 1 }
111
+ end
112
+
68
113
  def random_bucket
69
114
  @rand.rand(counters.size)
70
115
  end
@@ -73,7 +118,7 @@ module Noid
73
118
  # Counters to use for quasi-random NOID sequences
74
119
  def counters
75
120
  return @counters if @counters
76
- return [] unless template.generator == 'r'
121
+ return [] unless random?
77
122
 
78
123
  percounter = template.max / (@max_counters || Noid::MAX_COUNTERS) + 1
79
124
  t = 0
@@ -93,7 +138,20 @@ module Noid
93
138
  end
94
139
 
95
140
  def dump
96
- { state: true, seq: @seq, seed: @seed, template: template.template, counters: Marshal.load(Marshal.dump(counters)) }
141
+ {
142
+ template: template.template,
143
+ counters: Marshal.load(Marshal.dump(counters)),
144
+ seq: seq,
145
+ rand: Marshal.dump(@rand) # we would Marshal.load this too, but serializers don't persist the internal state correctly
146
+ }
147
+ end
148
+
149
+ def random?
150
+ template.generator == 'r'
151
+ end
152
+
153
+ def unbounded?
154
+ template.generator == 'z'
97
155
  end
98
156
  end
99
157
  end
@@ -164,7 +164,7 @@ describe Noid::Minter do
164
164
  minter = described_class.new(template: '.rddd')
165
165
  d = minter.dump
166
166
  expect(d[:seq]).to eq 0
167
- expect(d[:seed]).to eq(minter.instance_variable_get('@seed'))
167
+ expect(described_class.new(d).instance_variable_get('@rand').seed).to eq(minter.instance_variable_get('@rand').seed)
168
168
  end
169
169
 
170
170
  it "allows a random identifier minter to be 'replayed' accurately" do
@@ -198,6 +198,69 @@ describe Noid::Minter do
198
198
  end
199
199
  end
200
200
 
201
+ describe '#remaining' do
202
+ context 'with a sequential template' do
203
+ subject { described_class.new(template: '.sed') }
204
+ context 'with a new minter' do
205
+ it 'returns 290' do
206
+ expect(subject.remaining).to eq 290
207
+ end
208
+ end
209
+ context 'with an in-progress minter' do
210
+ before { 100.times { subject.mint } }
211
+ it 'returns 190' do
212
+ expect(subject.remaining).to eq 190
213
+ end
214
+ end
215
+ context 'with an exhausted minter' do
216
+ before { 290.times { subject.mint } }
217
+ it 'returns 0' do
218
+ expect(subject.remaining).to eq 0
219
+ end
220
+ end
221
+ end
222
+ context 'with a random template' do
223
+ subject { described_class.new(template: '.reek') }
224
+ context 'with a new minter' do
225
+ it 'returns 841' do
226
+ expect(subject.remaining).to eq 841
227
+ end
228
+ end
229
+ context 'with an in-progress minter' do
230
+ before { 441.times { subject.mint } }
231
+ it 'returns 400' do
232
+ expect(subject.remaining).to eq 400
233
+ end
234
+ end
235
+ context 'with an exhausted minter' do
236
+ before { 841.times { subject.mint } }
237
+ it 'returns 0' do
238
+ expect(subject.remaining).to eq 0
239
+ end
240
+ end
241
+ end
242
+ context 'with an unlimited template' do
243
+ subject { described_class.new(template: '.zdd') }
244
+ context 'with a new minter' do
245
+ it 'returns unlimited ' do
246
+ expect(subject.remaining).to eq Float::INFINITY
247
+ end
248
+ end
249
+ context 'with an in-progress minter' do
250
+ before { 51.times { subject.mint } }
251
+ it 'returns unlimited' do
252
+ expect(subject.remaining).to eq Float::INFINITY
253
+ end
254
+ end
255
+ context 'with a minter that appears to be exhausted' do
256
+ before { 101.times { subject.mint } }
257
+ it 'returns unlimited' do
258
+ expect(subject.remaining).to eq Float::INFINITY
259
+ end
260
+ end
261
+ end
262
+ end
263
+
201
264
  describe 'multithreading-safe example' do
202
265
  def stateful_minter
203
266
  File.open('minter-state', File::RDWR | File::CREAT, 0644) do |f|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: noid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Beer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-03 00:00:00.000000000 Z
11
+ date: 2015-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler