Class: Amka::Luhn

Inherits:
Object
  • Object
show all
Defined in:
lib/amka/luhn.rb

Overview

Implements the Luhn algorithm as described in the wikipedia article en.wikipedia.org/wiki/Luhn_algorithm

The Luhn algorithm (also known as the “modulus 10” or “mod 10” algorithm) is a simple checksum formula used to validate various identification numbers, such as credit card numbers, IMEI numbers, and national identification numbers (like AMKA in Greece).

## Algorithm Steps

  1. Starting from the rightmost digit, double the value of every second digit

  2. If doubling a number results in a two-digit number (>9), subtract 9 from the result

  3. Sum all digits (both doubled and non-doubled)

  4. If the total modulo 10 is 0, then the number is valid

## Uses This class provides both validation of existing IDs and generation of new valid IDs that follow the Luhn algorithm.

Examples:

Validate a Luhn ID

Amka::Luhn.valid?('79927398713')  #=> true

Generate a new Luhn ID of specified length

Amka::Luhn.generate(10)  #=> "7992739871"

Generate a Luhn ID with a specific prefix

Amka::Luhn.generate(16, '4532')  #=> "4532015112830366"

Since:

  • 1.0.0

Class Method Summary collapse

Class Method Details

.generate(total, id_start = '') ⇒ String

Generates a valid Luhn ID

Creates a number that passes the Luhn check by:

  1. Taking the provided prefix (or creating a random one)

  2. Calculating what the check digit should be for the number to be valid

  3. Appending the check digit to create a valid Luhn number

Examples:

Generate a 16-digit number (like a credit card)

Amka::Luhn.generate(16)  #=> "4532015112830366"

Generate a number with a specific prefix

Amka::Luhn.generate(10, '123456')  #=> "1234567897"

Special case for length 1

Amka::Luhn.generate(1)  #=> "0"

Raises:

  • (ArgumentError)

    if arguments are invalid

Since:

  • 1.0.0



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/amka/luhn.rb', line 97

def self.generate(total, id_start = '')
  validate_generate_args_or_fail(total, id_start)
  return '0' if id_start.empty? && total == 1

  last_digits_length = total - id_start.length
  # Use String.new to create an unfrozen string
  last_digits_except_check = String.new
  # subtract by one to account for the check digit
  (last_digits_length - 1).times { last_digits_except_check << rand(0..9).to_s }
  # Using + instead of << for string concatenation to prevent frozen string issues
  luhn_id_except_check_digit = id_start + last_digits_except_check

  digits_sum = calculate_digits_sum(luhn_id_except_check_digit, generate: true)

  check_digit = (digits_sum * 9) % 10

  luhn_id_except_check_digit + check_digit.to_s
end

.safe_valid?(luhn_id) ⇒ Boolean

Validates if a given ID follows the Luhn algorithm, without raising exceptions

This is a safe version of valid? that returns false for any invalid input instead of raising exceptions. It’s designed for use in validation pipelines where exceptions would need to be caught.

Examples:

Safe validation with clean inputs

Amka::Luhn.safe_valid?('4532015112830366')  #=> true

Safe validation with problematic inputs

Amka::Luhn.safe_valid?(nil)  #=> false
Amka::Luhn.safe_valid?(12345)  #=> false
Amka::Luhn.safe_valid?('abc-123')  #=> false

Since:

  • 1.0.0



67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/amka/luhn.rb', line 67

def self.safe_valid?(luhn_id)
  # Return false for non-string input
  return false unless luhn_id.is_a?(String)
  # Return false for strings with non-digits
  return false unless luhn_id.match(/\A\d+\Z/)

  digits_sum = calculate_digits_sum(luhn_id)
  (digits_sum % 10).zero?
rescue StandardError
  # Catch any unexpected errors and return false
  false
end

.valid?(luhn_id) ⇒ Boolean

Validates if a given ID follows the Luhn algorithm

Applies the standard Luhn algorithm to check if a number is valid:

  1. From the rightmost digit, double every second digit

  2. Sum the digits (if any doubled digit > 9, subtract 9)

  3. If the sum is divisible by 10, the number is valid

Examples:

Validating a credit card number

Amka::Luhn.valid?('4532015112830366')  #=> true

Validating an invalid number

Amka::Luhn.valid?('1234567890')  #=> false

Raises:

  • (ArgumentError)

    if the ID is not a string of digits

Since:

  • 1.0.0



45
46
47
48
49
50
51
# File 'lib/amka/luhn.rb', line 45

def self.valid?(luhn_id)
  Utils.string_with_digits_or_fail(luhn_id)

  digits_sum = calculate_digits_sum(luhn_id)

  (digits_sum % 10).zero?
end