def validate_ssn
# Kontrollera antal siffror i personnummret.
if ssn.size != 10
errors.add_to_base "Personnummret innehåller fel antal siffror. (ÅÅMMDDXXXX)"
end
# Räkna ut kontrollsiffran
sum = tmp = 0
(0..8).each do |i|
if i % 2 == 0
if (tmp = 2 * ssn[i,1].to_i) > 9
sum = sum + tmp - 9
else
sum = sum + tmp
end
else
sum = sum + ssn[i,1].to_i
end
end
if (sum % 10) != 0
if (10 - (sum % 10)) != ssn[9,1].to_i
errors.add_to_base 'Personnummret måste vara riktigt.'
end
else
if ssn[9,1] != 0
errors.add_to_base 'Personnummret måste vara riktigt.'
end
end
end
Refactorings
No refactoring yet !
Marc-Andre
April 7, 2009, April 07, 2009 18:26, permalink
My Swedish is a bit rusty, but isn't that simply Luhn's check?
You can add all the numbers and check the sum (mod 10) is 0.
You might want to add a check that only numbers have been passed.
I didn't check the following code:
def passes_luhn_check?(str)
sum = 0
numbers = str.split(//).map(&:to_i)
numbers.each_with_index do |n, i|
n *= 2 if i.even?
n -= 9 if n >= 10
sum += n
end
sum % 10 == 0
end
def validate_ssn
# Kontrollera antal siffror i personnummret.
if ssn.size != 10
errors.add_to_base "Personnummret innehåller fel antal siffror. (ÅÅMMDDXXXX)"
end
# Räkna ut kontrollsiffran
errors.add_to_base 'Personnummret måste vara riktigt.' unless passes_luhn_check?(ssn)
end
Joel Junström
August 18, 2010, August 18, 2010 07:58, permalink
I'd wrap it in a class, it's a bit more verbose but you can reuse it instead of just having it as a validation.
require 'enumerator'
module Luhn
class CivicNumber
attr_reader :civic_number
def initialize(civic_number)
@civic_number = civic_number.to_s.gsub(/\D/, '')
end
def valid?
civic_number.length == 10 && checksum(civic_number) % 10 == 0
end
def control_digit
10 - checksum(civic_number[0...9]) % 10
end
def sex
valid? ? (civic_number[8...9].to_i.even? ? 'female' : 'male') : 'unknown'
end
def female?
sex == 'female'
end
def male?
sex == 'male'
end
private
def checksum(value)
sum = 0
value.split(//).enum_for(:each_with_index).map do |c, i|
n = c.to_i
n *= 2 if i.even?
n -= 9 if n >= 10
sum += n
end
sum
end
end
end
class String
def as_civic_number
Luhn::CivicNumber.new(self)
end
end
class Numeric
def as_civic_number
Luhn::CivicNumber.new(self.to_s)
end
end
Joel Junström
August 18, 2010, August 18, 2010 08:13, permalink
Err, "slight" bug in previous example. Just remembered that the algorithm for the swedish civic number does not reduce the factor by 9 if it's >= 10 but instead adds each number (ie if first is 8 then 8 * 2 = 16 then the final checksum is calculated 1 + 6)
def checksum(value)
value.split(//).enum_for(:each_with_index).map do |n, i|
n.to_i * (i.even? ? 2 : 1)
end.to_s.split(//).inject(0) { |sum, c| sum += c.to_i }
end
Code for checking swedish "personnummer".