Module: SecId::Checkable
Overview
Provides check-digit validation and calculation for securities identifiers. Include this module in classes that have a check digit as part of their format.
Including classes must implement:
-
calculate_check_digitmethod that returns the calculated check digit value
This module provides:
-
Character-to-digit mapping constants
-
Luhn algorithm variants for check-digit calculation
-
valid?override that validates the check digit -
restore!method to calculate and set the check digit -
check_digitattribute -
Class-level convenience methods:
restore!,check_digit
Defined Under Namespace
Modules: ClassMethods
Constant Summary collapse
- CHAR_TO_DIGITS =
Character-to-digit mapping for Luhn algorithm variants. Maps alphanumeric characters to digit arrays for multi-digit expansion. Used by ISIN for check-digit calculation.
{ '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, 'A' => [1, 0], 'B' => [1, 1], 'C' => [1, 2], 'D' => [1, 3], 'E' => [1, 4], 'F' => [1, 5], 'G' => [1, 6], 'H' => [1, 7], 'I' => [1, 8], 'J' => [1, 9], 'K' => [2, 0], 'L' => [2, 1], 'M' => [2, 2], 'N' => [2, 3], 'O' => [2, 4], 'P' => [2, 5], 'Q' => [2, 6], 'R' => [2, 7], 'S' => [2, 8], 'T' => [2, 9], 'U' => [3, 0], 'V' => [3, 1], 'W' => [3, 2], 'X' => [3, 3], 'Y' => [3, 4], 'Z' => [3, 5], '*' => [3, 6], '@' => [3, 7], '#' => [3, 8] }.freeze
- CHAR_TO_DIGIT =
Character-to-digit mapping for single-digit conversion. Maps alphanumeric characters to values 0-38 (A=10, B=11, …, Z=35, *=36, @=37, #=38). Used by CUSIP, FIGI, SEDOL, LEI, and IBAN for check-digit calculations.
{ '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15, 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20, 'L' => 21, 'M' => 22, 'N' => 23, 'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31, 'W' => 32, 'X' => 33, 'Y' => 34, 'Z' => 35, '*' => 36, '@' => 37, '#' => 38 }.freeze
Class Method Summary collapse
- .included(base) ⇒ Object private
Instance Method Summary collapse
-
#calculate_check_digit ⇒ Integer
Subclasses must override this method to implement their check-digit algorithm.
-
#luhn_sum_double_add_double(digits) ⇒ Integer
CUSIP/CEI style: “Double Add Double” algorithm.
-
#luhn_sum_indexed(digits) ⇒ Integer
FIGI style: index-based doubling algorithm.
-
#luhn_sum_standard(digits) ⇒ Integer
ISIN style: standard Luhn with subtract-9 for values > 9.
-
#restore! ⇒ String
Calculates and sets the check digit, updating full_number.
-
#reversed_digits_multi(id) ⇒ Array<Integer>
Converts identifier characters to reversed digit array using multi-digit mapping.
-
#reversed_digits_single(id) ⇒ Array<Integer>
Converts identifier characters to reversed digit array using single-digit mapping.
- #to_s ⇒ String (also: #to_str)
-
#valid? ⇒ Boolean
Validates format and check digit.
Class Method Details
.included(base) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
59 60 61 62 |
# File 'lib/sec_id/concerns/checkable.rb', line 59 def self.included(base) base.attr_reader :check_digit base.extend(ClassMethods) end |
Instance Method Details
#calculate_check_digit ⇒ Integer
Subclasses must override this method to implement their check-digit algorithm.
106 107 108 |
# File 'lib/sec_id/concerns/checkable.rb', line 106 def calculate_check_digit raise NotImplementedError end |
#luhn_sum_double_add_double(digits) ⇒ Integer
CUSIP/CEI style: “Double Add Double” algorithm. Processes pairs of digits, doubling the first (even-positioned from right), then summing both digit’s div10mod10 values.
122 123 124 125 126 127 |
# File 'lib/sec_id/concerns/checkable.rb', line 122 def luhn_sum_double_add_double(digits) digits.each_slice(2).reduce(0) do |sum, (even, odd)| double_even = (even || 0) * 2 sum + div10mod10(double_even) + div10mod10(odd || 0) end end |
#luhn_sum_indexed(digits) ⇒ Integer
FIGI style: index-based doubling algorithm. Doubles odd-indexed digits (from right), then sums div10mod10 values.
134 135 136 137 138 139 |
# File 'lib/sec_id/concerns/checkable.rb', line 134 def luhn_sum_indexed(digits) digits.each_with_index.reduce(0) do |sum, (digit, index)| digit *= 2 if index.odd? sum + div10mod10(digit) end end |
#luhn_sum_standard(digits) ⇒ Integer
ISIN style: standard Luhn with subtract-9 for values > 9. Processes pairs of digits, doubling the first (even-positioned from right), subtracting 9 if result > 9.
147 148 149 150 151 152 153 |
# File 'lib/sec_id/concerns/checkable.rb', line 147 def luhn_sum_standard(digits) digits.each_slice(2).reduce(0) do |sum, (even, odd)| double_even = (even || 0) * 2 double_even -= 9 if double_even > 9 sum + double_even + (odd || 0) end end |
#restore! ⇒ String
Calculates and sets the check digit, updating full_number.
96 97 98 99 |
# File 'lib/sec_id/concerns/checkable.rb', line 96 def restore! @check_digit = calculate_check_digit @full_number = to_s end |
#reversed_digits_multi(id) ⇒ Array<Integer>
Converts identifier characters to reversed digit array using multi-digit mapping. Used by ISIN where letters expand to two digits.
169 170 171 |
# File 'lib/sec_id/concerns/checkable.rb', line 169 def reversed_digits_multi(id) id.each_char.flat_map { |c| CHAR_TO_DIGITS.fetch(c) }.reverse! end |
#reversed_digits_single(id) ⇒ Array<Integer>
Converts identifier characters to reversed digit array using single-digit mapping. Used by CUSIP, CEI, FIGI, and SEDOL.
160 161 162 |
# File 'lib/sec_id/concerns/checkable.rb', line 160 def reversed_digits_single(id) id.each_char.map { |c| CHAR_TO_DIGIT.fetch(c) }.reverse! end |
#to_s ⇒ String Also known as: to_str
111 112 113 |
# File 'lib/sec_id/concerns/checkable.rb', line 111 def to_s "#{identifier}#{check_digit}" end |
#valid? ⇒ Boolean
Validates format and check digit.
86 87 88 89 90 |
# File 'lib/sec_id/concerns/checkable.rb', line 86 def valid? return false unless valid_format? check_digit == calculate_check_digit end |