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 +4 -4
- data/VERSION +1 -1
- data/lib/noid/minter.rb +84 -26
- data/spec/lib/minter_spec.rb +64 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: acd76906cdc40cfe6803fcf7ca54bf88ce0ca981
|
4
|
+
data.tar.gz: 90961988a2c041a1c7946cb57c61923fa303f3ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c481bd56ec9435a09875416e0e24751f07f348eebf1234ede326bef81a031582e9574fa51e457507d231696724cc167e1978e39aeb88c6672a625766e58ea6e
|
7
|
+
data.tar.gz: c93b2e4e8a68a6bc7c17bafbe8131dbe479fdfcaff85954aa0712a050882a1fcbbd6c4c3118ba6637859f908b7600639a56d84df62cc866d19268989ffa5c2d4
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.8.0
|
data/lib/noid/minter.rb
CHANGED
@@ -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 :
|
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
|
-
|
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
|
-
#
|
38
|
-
# @return [
|
39
|
-
def
|
40
|
-
|
41
|
-
|
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
|
-
|
51
|
-
@seq += 1
|
52
|
-
if template.generator == 'r'
|
93
|
+
if random?
|
53
94
|
next_random
|
54
95
|
else
|
55
|
-
|
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
|
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
|
-
{
|
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
|
data/spec/lib/minter_spec.rb
CHANGED
@@ -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
|
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.
|
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-
|
11
|
+
date: 2015-08-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|