Thank you James for another fun quiz.
My solution is a more verbose version of the metaprogramming-based
routing used in why's excellent camping. But instead of regexen
matching urls to controller classes, here they are matching card
numbers to the card classes.
What I like about this approach is that you can add more card patterns
just by adding more classes with the appropriate regexen, and you can
override validation or other functionality as needed.
Regards,
Paul.
# cardvalidator.rb
#
require 'metaid'
module Card
@cards=[]
def Card.Base *u
c = @cards
Class.new {
meta_def(:patterns){u}
meta_def(:validity){|x|Card.validity(x)}
meta_def(:inherited){|x|c<<x}
}
end
def Card.validate cardnum
@cards.map { |k|
k.patterns.map { |x|
if cardnum =~ /^#{x}\/?$/
return [k.name.upcase, k.validity(cardnum)].join( " ")
end
}
}
raise "Unexpected Card Number pattern: '#{cardnum}'"
end
def Card.sum_of_digits s
s.split("").inject(0){|sum,x|sum + Integer(x)}
end
def Card.luhnSum s
temp = ""
r = 0..s.size
a = s.split("").reverse
r.each do |i|
if i%2==1
x = (Integer(a[i])*2).to_s
else
x = a[i]
end
temp << x.to_s
end
sum_of_digits temp
end
def Card.validity cardnum
if (Card.luhnSum(cardnum) % 10)==0
return "Valid"
else
return "Invalid"
end
end
end
# patterns will be tested for match in order defined
class Visa < Card.Base /4[0-9]{12,15}/
end
class Amex < Card.Base /3[4,7][0-9]{13}/
end
class Mastercard < Card.Base /5[1-5][0-9]{13}/
end
class Discover < Card.Base /6011[0-9]{12}/
end
# catch-all
class Unknown < Card.Base /.+/
end
p Card.validate(ARGV[0])