id_coder 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +44 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/id_coder.rb +203 -0
- data/test/helper.rb +18 -0
- data/test/test_id_coder.rb +152 -0
- metadata +156 -0
data/.document
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
git (1.2.5)
|
5
|
+
jeweler (1.8.3)
|
6
|
+
bundler (~> 1.0)
|
7
|
+
git (>= 1.2.5)
|
8
|
+
rake
|
9
|
+
rdoc
|
10
|
+
json (1.6.6)
|
11
|
+
modalsettings (1.0.0)
|
12
|
+
modalsupport (>= 0.8.1)
|
13
|
+
modalsupport (0.8.3)
|
14
|
+
rake (0.9.2.2)
|
15
|
+
rdoc (3.12)
|
16
|
+
json (~> 1.4)
|
17
|
+
shoulda (3.0.1)
|
18
|
+
shoulda-context (~> 1.0.0)
|
19
|
+
shoulda-matchers (~> 1.0.0)
|
20
|
+
shoulda-context (1.0.0)
|
21
|
+
shoulda-matchers (1.0.0)
|
22
|
+
|
23
|
+
PLATFORMS
|
24
|
+
ruby
|
25
|
+
|
26
|
+
DEPENDENCIES
|
27
|
+
bundler (~> 1)
|
28
|
+
jeweler (~> 1.8.3)
|
29
|
+
modalsettings
|
30
|
+
modalsupport
|
31
|
+
rdoc (~> 3.12)
|
32
|
+
shoulda
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Javier Goizueta
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
= id_coder
|
2
|
+
|
3
|
+
Id-codes are relatively short codes to represent externally id internal numbers.
|
4
|
+
|
5
|
+
A bijection between ids and codes is used to assure uniqueness of codes and to avoid storing the
|
6
|
+
codes in the database (codes and ids are computed on-the-fly from each other).
|
7
|
+
|
8
|
+
Codes are user-visible; they're designed to be the user's identification of some element.
|
9
|
+
|
10
|
+
Only digits an capital letters are used for the codes, excluding I,O,1,0 to avoid confusion,
|
11
|
+
and a check digit can be used to detect most common transcription errors. This makes codes
|
12
|
+
viable to be transmitted orally, etc.
|
13
|
+
|
14
|
+
The number of digits used is scalable: the minimum number of digits and the increment-size can be
|
15
|
+
parameterized to render good-looking codes.
|
16
|
+
|
17
|
+
An IdCoder can be defined passing to it three optional parameters that define the lenght of the codes:
|
18
|
+
|
19
|
+
IdCoder[:num_digits=>4, :block_digits=>3, :check_digit=>false]
|
20
|
+
|
21
|
+
An additional parameter can be passed to define which characters will be used as digits for the codes
|
22
|
+
and its order; this must be a String of IdCoder::RADIX distinct characters:
|
23
|
+
|
24
|
+
IdCoder[:code_digits=>"0123456789ABCDEFGHIJKLMNOPQRSTUV"]
|
25
|
+
|
26
|
+
Alternatively, a seed parameter can be passed to generate a randomized string
|
27
|
+
|
28
|
+
IdCoder[:seed=>8734112]
|
29
|
+
|
30
|
+
Since this might produce different results in different Ruby versions, the code_digits produced can be accessed
|
31
|
+
and kept for portability:
|
32
|
+
|
33
|
+
IdCoder[:seed=>8734112].code_digits # -> "9GTFNJSZA6LQWXV3BEKHYMP75U8R24CD"
|
34
|
+
|
35
|
+
Ids (integers) can be coded and decoded into alphanumeric strings like this:
|
36
|
+
|
37
|
+
id_coder = IdCoder[]
|
38
|
+
id_coder.id_to_code(7) # => "JEQJTE3"
|
39
|
+
id_coder.code_to_id("JEQJTE3") # => 7
|
40
|
+
|
41
|
+
== Copyright
|
42
|
+
|
43
|
+
Copyright (c) 2012 Javier Goizueta. See LICENSE.txt for
|
44
|
+
further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "id_coder"
|
18
|
+
gem.homepage = "http://github.com/jgoizueta/id_coder"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Alphanumeric code generator}
|
21
|
+
gem.description = %Q{Generates relatively short codes to represent id numbers externally}
|
22
|
+
gem.email = "jgoizueta@gmail.com"
|
23
|
+
gem.authors = ["Javier Goizueta"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :test
|
36
|
+
|
37
|
+
require 'rdoc/task'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "id_coder #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/lib/id_coder.rb
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'modalsupport'
|
3
|
+
require 'modalsettings'
|
4
|
+
|
5
|
+
# Id-codes are relatively short codes to represent externally id internal numbers.
|
6
|
+
#
|
7
|
+
# A bijection between ids and codes is used to assure uniqueness of codes and to avoid storing the
|
8
|
+
# codes in the database (codes and ids are computed on-the-fly from each other).
|
9
|
+
#
|
10
|
+
# Codes are user-visible; they're designed to be the user's identification of some element.
|
11
|
+
#
|
12
|
+
# Only digits an capital letters are used for the codes, excluding I,O,1,0 to avoid confusion,
|
13
|
+
# and a check digit can be used to detect most common transcription errors. This makes codes
|
14
|
+
# viable to be transmitted orally, etc.
|
15
|
+
#
|
16
|
+
# The number of digits used is scalable: the minimum number of digits and the increment-size can be
|
17
|
+
# parameterized to render good-looking codes.
|
18
|
+
#
|
19
|
+
# An IdCoder can be defined passing to it three optional parameters that define the lenght of the codes:
|
20
|
+
# IdCoder[:num_digits=>4, :block_digits=>3, :check_digit=>false]
|
21
|
+
# An additional parameter can be passed to define which characters will be used as digits for the codes
|
22
|
+
# and its order; this must be a String of IdCoder::RADIX distinct characters:
|
23
|
+
# IdCoder[:code_digits=>"0123456789ABCDEFGHIJKLMNOPQRSTUV"]
|
24
|
+
# Alternatively, a seed parameter can be passed to generate a randomized string
|
25
|
+
# IdCoder[:seed=>8734112]
|
26
|
+
# Since this might produce different results in different Ruby versions, the code_digits produced can be accessed
|
27
|
+
# and kept for portability:
|
28
|
+
# IdCoder[:seed=>8734112].code_digits # -> "9GTFNJSZA6LQWXV3BEKHYMP75U8R24CD"
|
29
|
+
#
|
30
|
+
class IdCoder
|
31
|
+
|
32
|
+
# Internal parameters
|
33
|
+
RADIX = 32
|
34
|
+
DIGITS = "0123456789abcdefghijklmnopqrstuv" # RADIX-digits used by Integer#to_s(RADIX), String#to_i(RADIX)
|
35
|
+
CODE_DIGITS = "BAFTQ4EJCNVYZKDPG37H5S8WML692RXU" # RADIX-digits used for the codes (permutation of 2-9,A-Z except I,O)
|
36
|
+
|
37
|
+
class InvalidCode < RuntimeError; end
|
38
|
+
class InvalidNumber < RuntimeError; end
|
39
|
+
class InvalidCodeDigits < RuntimeError; end
|
40
|
+
|
41
|
+
def initialize(config={})
|
42
|
+
config = Settings[config]
|
43
|
+
if config.code_digits
|
44
|
+
@code_digits = config.code_digits
|
45
|
+
raise InvalidCodeDigits, "Invalid digits" if @code_digits.bytes.to_a.uniq.size!=RADIX
|
46
|
+
else
|
47
|
+
@code_digits = CODE_DIGITS
|
48
|
+
if config.seed
|
49
|
+
srand config.seed
|
50
|
+
@code_digits = @code_digits.bytes.to_a.shuffle.map(&:chr)*""
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@num_digits = (config.num_digits || 6).to_i
|
54
|
+
@block_digits = (config.block_digits || 2).to_i
|
55
|
+
@check_digit = config.check_digit
|
56
|
+
@check_digit = true if @check_digit.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
include ModalSupport::BracketConstructor
|
60
|
+
|
61
|
+
include
|
62
|
+
|
63
|
+
# configurable parameters
|
64
|
+
|
65
|
+
# Minumum number of digits used; limits the number of valid ids before scaling up to RADIX**num_digits
|
66
|
+
def num_digits
|
67
|
+
@num_digits
|
68
|
+
end
|
69
|
+
|
70
|
+
# Use a check digit?: this increases the length of codes in one
|
71
|
+
def check_digit?
|
72
|
+
@check_digit
|
73
|
+
end
|
74
|
+
|
75
|
+
# Number of digits for incremental scaling-up blocks
|
76
|
+
def block_digits
|
77
|
+
@block_digits
|
78
|
+
end
|
79
|
+
|
80
|
+
# Properties of the codes
|
81
|
+
|
82
|
+
# mininum code_length
|
83
|
+
def code_length
|
84
|
+
num_digits + (check_digit? ? 1 : 0)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Number of valid codes before scaling-up
|
88
|
+
def num_valid_codes
|
89
|
+
RADIX**num_digits
|
90
|
+
end
|
91
|
+
|
92
|
+
def num_digits_for(id)
|
93
|
+
if id<num_valid_codes
|
94
|
+
num_digits
|
95
|
+
else
|
96
|
+
# ((Math.log(id)/Math.log(RADIX)-code_length)/block_digits).ceil*block_digits + num_digits
|
97
|
+
nd = num_digits
|
98
|
+
loop do
|
99
|
+
nd += block_digits
|
100
|
+
nvc = RADIX**nd
|
101
|
+
break if id<nvc
|
102
|
+
end
|
103
|
+
# check = ((Math.log(id)/Math.log(RADIX)-code_length)/block_digits).ceil*block_digits + num_digits
|
104
|
+
# raise "nd=#{nd}; [#{check}] id=#{id}" unless nd==check
|
105
|
+
nd
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def code_length_for(id)
|
110
|
+
num_digits_for(id) + (check_digit? ? 1 : 0)
|
111
|
+
end
|
112
|
+
|
113
|
+
def code_digits
|
114
|
+
@code_digits
|
115
|
+
end
|
116
|
+
|
117
|
+
def parameters
|
118
|
+
{ :num_digits=>@num_digits, :block_digits=>@block_digits, :check_digit=>@check_digit, :code_digits=>@code_digits }
|
119
|
+
end
|
120
|
+
|
121
|
+
def inspect
|
122
|
+
"IdCoder[#{parameters.inspect.unwrap('[]')}]"
|
123
|
+
end
|
124
|
+
|
125
|
+
# Code generatioon
|
126
|
+
|
127
|
+
# Direct encoding: generate an Id-Code from an integer id.
|
128
|
+
def id_to_code(id)
|
129
|
+
raise InvalidNumber, "Numbers to be coded must be passed as integers" unless id.kind_of?(Integer)
|
130
|
+
raise InvalidNumber, "Negative numbers cannot be encoded: #{id}" if id<0
|
131
|
+
nd = num_digits_for(id)
|
132
|
+
v = id.to_s(RADIX)
|
133
|
+
v = "0"*(nd-v.size) + v
|
134
|
+
i = 0
|
135
|
+
code = ""
|
136
|
+
mask = 0
|
137
|
+
v.reverse.each_byte do |b|
|
138
|
+
d = DIGITS.index(b.chr)
|
139
|
+
code << @code_digits[mask = ((d+i)%RADIX ^ mask),1]
|
140
|
+
i += 1
|
141
|
+
end
|
142
|
+
code << check_digit(code) if check_digit?
|
143
|
+
code
|
144
|
+
end
|
145
|
+
|
146
|
+
# Inverse encoding: compute the integer id for a Id-Code
|
147
|
+
def code_to_id(code)
|
148
|
+
raise InvalidCode, "Codes must be strings" unless code.kind_of?(String)
|
149
|
+
code = code.strip.upcase # tolerate case differences & surrounding whitespace
|
150
|
+
raise InvalidCode, "Invalid code: #{code}" unless code =~ /\A[#{@code_digits}]+\Z/
|
151
|
+
# raise InvalidCode, "Invalid code length: #{code.size} for #{code} (must be #{code_length})" unless code.size==code_length
|
152
|
+
if check_digit?
|
153
|
+
cd = code[-1,1]
|
154
|
+
code = code[0...-1]
|
155
|
+
raise InvalidCode, "Invalid code: #{code+cd}" if cd!=check_digit(code)
|
156
|
+
end
|
157
|
+
nd = code.size
|
158
|
+
sx = nd-num_digits
|
159
|
+
raise InvalidCode, "Invalid code length: #{code.size} for #{code}" unless sx>=0 && (sx%block_digits)==0
|
160
|
+
i = 0
|
161
|
+
v = ""
|
162
|
+
mask = 0
|
163
|
+
code.each_byte do |b|
|
164
|
+
next_mask = @code_digits.index(b.chr)
|
165
|
+
d = ((next_mask ^ mask)-i)%RADIX
|
166
|
+
mask = next_mask
|
167
|
+
d = DIGITS[d,1]
|
168
|
+
v = d + v
|
169
|
+
i += 1
|
170
|
+
end
|
171
|
+
v.to_i(RADIX)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Check code: returns true (valid) or false (invalid)
|
175
|
+
def valid_code?(code)
|
176
|
+
is_valid = true
|
177
|
+
begin
|
178
|
+
code_to_id(code)
|
179
|
+
rescue InvalidCode
|
180
|
+
is_valid = false
|
181
|
+
end
|
182
|
+
is_valid
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
# Check digits are computed using the Luhn Mod N Algorithm:
|
188
|
+
# http://en.wikipedia.org/wiki/Luhn_mod_N_algorithm
|
189
|
+
def check_digit(code)
|
190
|
+
factor = 2
|
191
|
+
sum = 0
|
192
|
+
code.each_byte do |b|
|
193
|
+
addend = factor * @code_digits.index(b.chr)
|
194
|
+
factor = (factor==2) ? 1 : 2
|
195
|
+
addend = (addend / RADIX) + (addend % RADIX)
|
196
|
+
sum += addend
|
197
|
+
end
|
198
|
+
remainder = sum % RADIX
|
199
|
+
@code_digits[(RADIX - remainder) % RADIX, 1]
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'id_coder'
|
16
|
+
|
17
|
+
class Test::Unit::TestCase
|
18
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestIdCoder < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@id_coder = IdCoder[
|
7
|
+
:num_digits => 6,
|
8
|
+
:block_digits => 2,
|
9
|
+
:check_digit => true
|
10
|
+
]
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_bijectivity
|
14
|
+
# sanity check
|
15
|
+
assert@id_coder.check_digit?
|
16
|
+
assert_equal 6,@id_coder.num_digits
|
17
|
+
assert_equal 2,@id_coder.block_digits
|
18
|
+
assert_equal 7,@id_coder.code_length
|
19
|
+
assert_equal 32, IdCoder::RADIX
|
20
|
+
assert_equal 32**6,@id_coder.num_valid_codes
|
21
|
+
|
22
|
+
# Test numbers within the basic range (num_digits)
|
23
|
+
test_numbers = (0..100).to_a + (99999900..100000020).to_a + [232211,9898774,1002,343332,1023,1024,1025]
|
24
|
+
test_numbers.each do |number|
|
25
|
+
code =@id_coder.id_to_code(number)
|
26
|
+
assert_equal 7, code.size # test also code length here
|
27
|
+
assert_equal number,@id_coder.code_to_id(code),"#{number} is converted to a code and back"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Test larger numbers
|
31
|
+
test_numbers = (1_999_999_990..2_000_000_010).to_a + (1_999_999_999_990..2_000_000_000_010).to_a
|
32
|
+
test_numbers.each do |number|
|
33
|
+
code =@id_coder.id_to_code(number)
|
34
|
+
assert code.size > 7
|
35
|
+
assert(
|
36
|
+
(code.size-(@id_coder.check_digit? ? 1 : 0)-@id_coder.num_digits)%@id_coder.block_digits == 0
|
37
|
+
)
|
38
|
+
assert_equal number,@id_coder.code_to_id(code),"#{number} is converted to a code and back"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_invalid_numbers
|
44
|
+
# sanity check
|
45
|
+
assert@id_coder.check_digit?
|
46
|
+
assert_equal 6,@id_coder.num_digits
|
47
|
+
assert_equal 2,@id_coder.block_digits
|
48
|
+
assert_equal 7,@id_coder.code_length
|
49
|
+
assert_equal 32, IdCoder::RADIX
|
50
|
+
assert_equal 32**6,@id_coder.num_valid_codes
|
51
|
+
# => ~1000000000 codes
|
52
|
+
|
53
|
+
assert_raise(IdCoder::InvalidNumber){@id_coder.id_to_code("1") }
|
54
|
+
assert_raise(IdCoder::InvalidNumber){@id_coder.id_to_code(-1) }
|
55
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(2000000000) }
|
56
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(0) }
|
57
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(1) }
|
58
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(100) }
|
59
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(1000000000) }
|
60
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(2000000000) }
|
61
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(100000000000000000000) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_invalid_codes
|
65
|
+
@id_coder = IdCoder[:num_digits=>4, :block_digits=>3, :check_digit=>false]
|
66
|
+
# sanity check
|
67
|
+
assert !@id_coder.check_digit?
|
68
|
+
assert_equal 4,@id_coder.num_digits
|
69
|
+
assert_equal 3,@id_coder.block_digits
|
70
|
+
assert_equal 4,@id_coder.code_length
|
71
|
+
assert_equal 32, IdCoder::RADIX
|
72
|
+
assert_equal 32**4,@id_coder.num_valid_codes
|
73
|
+
|
74
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(1023) }
|
75
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(234) }
|
76
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(2345) }
|
77
|
+
assert_nothing_raised(IdCoder::InvalidCode){@id_coder.code_to_id('2345') }
|
78
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('345') }
|
79
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('34567') }
|
80
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('3415') }
|
81
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('34I5') }
|
82
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('2A04') }
|
83
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('2AO4') }
|
84
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('2A-3') }
|
85
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('2A 3') }
|
86
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('2A)3') }
|
87
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('345678') }
|
88
|
+
assert_nothing_raised(IdCoder::InvalidCode){@id_coder.code_to_id('3456789') }
|
89
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('3456789A') }
|
90
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('3456789AB') }
|
91
|
+
assert_nothing_raised(IdCoder::InvalidCode){@id_coder.code_to_id('3456789ABC') }
|
92
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('3456789ABCD') }
|
93
|
+
assert_nothing_raised(IdCoder::InvalidCode){@id_coder.code_to_id('2A45') }
|
94
|
+
assert_nothing_raised(IdCoder::InvalidCode){@id_coder.code_to_id(' 2A43 ') }
|
95
|
+
assert_nothing_raised(IdCoder::InvalidCode){@id_coder.code_to_id('2a43') }
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_check_digits
|
99
|
+
# sanity check
|
100
|
+
assert@id_coder.check_digit?
|
101
|
+
assert_equal 6,@id_coder.num_digits
|
102
|
+
assert_equal 2,@id_coder.block_digits
|
103
|
+
assert_equal 7,@id_coder.code_length
|
104
|
+
assert_equal 32, IdCoder::RADIX
|
105
|
+
assert_equal 32**6,@id_coder.num_valid_codes
|
106
|
+
|
107
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(1023) }
|
108
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(234) }
|
109
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(2345938) }
|
110
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('2345938') }
|
111
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('345432') }
|
112
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('3454321') }
|
113
|
+
valid_code =@id_coder.id_to_code(234331)
|
114
|
+
assert_equal 7, valid_code.size
|
115
|
+
digits = %w{2 3 4 5 6 7 8 9 A B C D E F G H J K L M N P Q R S T U V W X Y Z}
|
116
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(valid_code[0...-1])}
|
117
|
+
# Check that changing the check digit makes the code invalid
|
118
|
+
digits.each do |digit|
|
119
|
+
next if digit == valid_code[-1,1]
|
120
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(valid_code[0...-1]+digit)}
|
121
|
+
end
|
122
|
+
# Check that any single-digit change makes the code invalid
|
123
|
+
(0..5).each do |i|
|
124
|
+
digits.each do |digit|
|
125
|
+
next if digit == valid_code[i,1]
|
126
|
+
invalid_code = valid_code[0,i] + digit + valid_code[i+1..-1]
|
127
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(invalid_code)}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
# Check that adjacent digit swapping makes the code invalid
|
131
|
+
# (there are some exceptions though, see http://en.wikipedia.org/wiki/Luhn_mod_N_algorithm)
|
132
|
+
# TODO: sistematic check avoiding exceptions
|
133
|
+
invalid_code = valid_code[0,2] + valid_code[3,1] + valid_code[2,1] + valid_code[4..-1]
|
134
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(invalid_code)}
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_internal_sanity
|
138
|
+
# Check the coded digits used: must be exactly IdCoder::RADIX unique uppercase characters
|
139
|
+
chars = []
|
140
|
+
IdCoder::CODE_DIGITS.each_byte{|b| chars << b.chr}
|
141
|
+
assert_equal IdCoder::RADIX, chars.uniq.size
|
142
|
+
assert_equal IdCoder::CODE_DIGITS.upcase, IdCoder::CODE_DIGITS
|
143
|
+
|
144
|
+
# Check the internally-used radix-32 digits to be consistent with Integer#to_s
|
145
|
+
assert_equal IdCoder::RADIX, IdCoder::DIGITS.size
|
146
|
+
(0...IdCoder::RADIX).each do |i|
|
147
|
+
assert_equal i.to_s(IdCoder::RADIX), IdCoder::DIGITS[i,1]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
end
|
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: id_coder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Javier Goizueta
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: modalsupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: modalsettings
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: shoulda
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rdoc
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.12'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '3.12'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: bundler
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: jeweler
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 1.8.3
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 1.8.3
|
110
|
+
description: Generates relatively short codes to represent id numbers externally
|
111
|
+
email: jgoizueta@gmail.com
|
112
|
+
executables: []
|
113
|
+
extensions: []
|
114
|
+
extra_rdoc_files:
|
115
|
+
- LICENSE.txt
|
116
|
+
- README.rdoc
|
117
|
+
files:
|
118
|
+
- .document
|
119
|
+
- Gemfile
|
120
|
+
- Gemfile.lock
|
121
|
+
- LICENSE.txt
|
122
|
+
- README.rdoc
|
123
|
+
- Rakefile
|
124
|
+
- VERSION
|
125
|
+
- lib/id_coder.rb
|
126
|
+
- test/helper.rb
|
127
|
+
- test/test_id_coder.rb
|
128
|
+
homepage: http://github.com/jgoizueta/id_coder
|
129
|
+
licenses:
|
130
|
+
- MIT
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ! '>='
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
segments:
|
142
|
+
- 0
|
143
|
+
hash: -1112580131298559240
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
requirements: []
|
151
|
+
rubyforge_project:
|
152
|
+
rubygems_version: 1.8.21
|
153
|
+
signing_key:
|
154
|
+
specification_version: 3
|
155
|
+
summary: Alphanumeric code generator
|
156
|
+
test_files: []
|