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 https://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"

Parameters:

  • the total length of the ID to generate

  • (defaults to: '')

    optional digits to use at the start of the ID

Returns:

  • a valid Luhn ID of exactly 'total' length

Raises:

  • 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

Parameters:

  • the ID to validate

Returns:

  • true if valid according to Luhn algorithm, false otherwise

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

Parameters:

  • the ID to validate, must be a string containing only digits

Returns:

  • true if valid according to Luhn algorithm, false otherwise

Raises:

  • 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