1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
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)
1 2 3 4 5
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".