noid 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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