Class: IdCoder
- Inherits:
-
Object
- Object
- IdCoder
- Includes:
- ModalSupport::BracketConstructor
- Defined in:
- lib/id_coder.rb
Overview
Id-codes are relatively short codes to represent externally id internal numbers.
A bijection between ids and codes is used to assure uniqueness of codes and to avoid storing the codes in the database (codes and ids are computed on-the-fly from each other).
Codes are user-visible; they’re designed to be the user’s identification of some element.
Only digits an capital letters are used for the codes, excluding I,O,1,0 to avoid confusion, and a check digit can be used to detect most common transcription errors. This makes codes viable to be transmitted orally, etc.
The number of digits used is scalable: the minimum number of digits and the increment-size can be parameterized to render good-looking codes.
An IdCoder can be defined passing to it three optional parameters that define the lenght of the codes:
IdCoder[:num_digits=>4, :block_digits=>3, :check_digit=>false]
An additional parameter can be passed to define which characters will be used as digits for the codes and its order; this must be a String of IdCoder::RADIX distinct characters:
IdCoder[:code_digits=>"0123456789ABCDEFGHIJKLMNOPQRSTUV"]
Alternatively, a seed parameter can be passed to generate a randomized string
IdCoder[:seed=>8734112]
Since this might produce different results in different Ruby versions, the code_digits produced can be accessed and kept for portability:
IdCoder[:seed=>8734112].code_digits # -> "9GTFNJSZA6LQWXV3BEKHYMP75U8R24CD"
Defined Under Namespace
Classes: InvalidCode, InvalidCodeDigits, InvalidNumber
Constant Summary collapse
- RADIX =
Internal parameters
32
- DIGITS =
RADIX-digits used by Integer#to_s(RADIX), String#to_i(RADIX)
"0123456789abcdefghijklmnopqrstuv"
- CODE_DIGITS =
RADIX-digits used for the codes (permutation of 2-9,A-Z except I,O)
"BAFTQ4EJCNVYZKDPG37H5S8WML692RXU"
Instance Method Summary collapse
-
#block_digits ⇒ Object
Number of digits for incremental scaling-up blocks.
-
#check_digit? ⇒ Boolean
Use a check digit?: this increases the length of codes in one.
- #code_digits ⇒ Object
-
#code_length ⇒ Object
mininum code_length.
- #code_length_for(id) ⇒ Object
-
#code_to_id(code) ⇒ Object
Inverse encoding: compute the integer id for a Id-Code.
-
#id_to_code(id) ⇒ Object
Direct encoding: generate an Id-Code from an integer id.
-
#initialize(config = {}) ⇒ IdCoder
constructor
A new instance of IdCoder.
- #inspect ⇒ Object
-
#num_digits ⇒ Object
Minumum number of digits used; limits the number of valid ids before scaling up to RADIX**num_digits.
- #num_digits_for(id) ⇒ Object
-
#num_valid_codes ⇒ Object
Number of valid codes before scaling-up.
- #parameters ⇒ Object
-
#valid_code?(code) ⇒ Boolean
Check code: returns true (valid) or false (invalid).
Constructor Details
#initialize(config = {}) ⇒ IdCoder
Returns a new instance of IdCoder.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/id_coder.rb', line 41 def initialize(config={}) config = Settings[config] if config.code_digits @code_digits = config.code_digits raise InvalidCodeDigits, "Invalid digits" if @code_digits.bytes.to_a.uniq.size!=RADIX else @code_digits = CODE_DIGITS if config.seed srand config.seed @code_digits = @code_digits.bytes.to_a.shuffle.map(&:chr)*"" end end @num_digits = (config.num_digits || 6).to_i @block_digits = (config.block_digits || 2).to_i @check_digit = config.check_digit @check_digit = true if @check_digit.nil? end |
Instance Method Details
#block_digits ⇒ Object
Number of digits for incremental scaling-up blocks
76 77 78 |
# File 'lib/id_coder.rb', line 76 def block_digits @block_digits end |
#check_digit? ⇒ Boolean
Use a check digit?: this increases the length of codes in one
71 72 73 |
# File 'lib/id_coder.rb', line 71 def check_digit? @check_digit end |
#code_digits ⇒ Object
113 114 115 |
# File 'lib/id_coder.rb', line 113 def code_digits @code_digits end |
#code_length ⇒ Object
mininum code_length
83 84 85 |
# File 'lib/id_coder.rb', line 83 def code_length num_digits + (check_digit? ? 1 : 0) end |
#code_length_for(id) ⇒ Object
109 110 111 |
# File 'lib/id_coder.rb', line 109 def code_length_for(id) num_digits_for(id) + (check_digit? ? 1 : 0) end |
#code_to_id(code) ⇒ Object
Inverse encoding: compute the integer id for a Id-Code
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/id_coder.rb', line 147 def code_to_id(code) raise InvalidCode, "Codes must be strings" unless code.kind_of?(String) code = code.strip.upcase # tolerate case differences & surrounding whitespace raise InvalidCode, "Invalid code: #{code}" unless code =~ /\A[#{@code_digits}]+\Z/ # raise InvalidCode, "Invalid code length: #{code.size} for #{code} (must be #{code_length})" unless code.size==code_length if check_digit? cd = code[-1,1] code = code[0...-1] raise InvalidCode, "Invalid code: #{code+cd}" if cd!=check_digit(code) end nd = code.size sx = nd-num_digits raise InvalidCode, "Invalid code length: #{code.size} for #{code}" unless sx>=0 && (sx%block_digits)==0 i = 0 v = "" mask = 0 code.each_byte do |b| next_mask = @code_digits.index(b.chr) d = ((next_mask ^ mask)-i)%RADIX mask = next_mask d = DIGITS[d,1] v = d + v i += 1 end v.to_i(RADIX) end |
#id_to_code(id) ⇒ Object
Direct encoding: generate an Id-Code from an integer id.
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/id_coder.rb', line 128 def id_to_code(id) raise InvalidNumber, "Numbers to be coded must be passed as integers" unless id.kind_of?(Integer) raise InvalidNumber, "Negative numbers cannot be encoded: #{id}" if id<0 nd = num_digits_for(id) v = id.to_s(RADIX) v = "0"*(nd-v.size) + v i = 0 code = "" mask = 0 v.reverse.each_byte do |b| d = DIGITS.index(b.chr) code << @code_digits[mask = ((d+i)%RADIX ^ mask),1] i += 1 end code << check_digit(code) if check_digit? code end |
#inspect ⇒ Object
121 122 123 |
# File 'lib/id_coder.rb', line 121 def inspect "IdCoder[#{parameters.inspect.unwrap('[]')}]" end |
#num_digits ⇒ Object
Minumum number of digits used; limits the number of valid ids before scaling up to RADIX**num_digits
66 67 68 |
# File 'lib/id_coder.rb', line 66 def num_digits @num_digits end |
#num_digits_for(id) ⇒ Object
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/id_coder.rb', line 92 def num_digits_for(id) if id<num_valid_codes num_digits else # ((Math.log(id)/Math.log(RADIX)-code_length)/block_digits).ceil*block_digits + num_digits nd = num_digits loop do nd += block_digits nvc = RADIX**nd break if id<nvc end # check = ((Math.log(id)/Math.log(RADIX)-code_length)/block_digits).ceil*block_digits + num_digits # raise "nd=#{nd}; [#{check}] id=#{id}" unless nd==check nd end end |
#num_valid_codes ⇒ Object
Number of valid codes before scaling-up
88 89 90 |
# File 'lib/id_coder.rb', line 88 def num_valid_codes RADIX**num_digits end |
#parameters ⇒ Object
117 118 119 |
# File 'lib/id_coder.rb', line 117 def parameters { :num_digits=>@num_digits, :block_digits=>@block_digits, :check_digit=>@check_digit, :code_digits=>@code_digits } end |
#valid_code?(code) ⇒ Boolean
Check code: returns true (valid) or false (invalid)
175 176 177 178 179 180 181 182 183 |
# File 'lib/id_coder.rb', line 175 def valid_code?(code) is_valid = true begin code_to_id(code) rescue InvalidCode is_valid = false end is_valid end |