[QUIZ] Checking Credit Cards (#122)

Hi everyone, like most of the other quiz solvers, I am also new to Ruby and
the mailing list. This is my second Ruby program, so I am eagerly awaiting
any comments on improvements, flaws, etc. Without further ado, my code:

···

------------------------------------------------------

@card_types = [
  ["Mastercard",/^5[1-5]\d{14}$/],
  ["Visa",/^4(\d{12}|\d{15})$/],
  ["Discover",/^6011\d{12}$/],
  ["AMEX",/^3[47]\d{13}$/],
  ["Unknown",/^\d*$/]
]

def card_type( card_number )
  card_number.gsub!( /\s/, '')
  @card_types.each do |card_type|
    return card_type[0] if card_type[1] =~ card_number
  end
  raise "Invalid characters in input"
end

def luhn( card_number )
  sum = 0
  card_number.length.downto( 1 ) do |i|
    doubled = ( i%2 + 1 ) * ( card_number[ i-1, 1 ].to_i )
    if doubled >= 10
      doubled = (doubled % 10 ) + 1
    end
    sum += doubled
  end
  sum % 10 == 0
end

def validate( card_number )
  return card_type( card_number ), luhn( card_number )
end

p validate( ARGV.join.gsub( /\s/, '') )

--------------------------------

Ruy

Here is my version... Note that it is probably incorrect w.r.t. the
valid/invalid and known/unknown issue discussed earlier... However,
after finishing my initial version, and despite how easy it would be
to fix, I am pretty swamped with other tasks and so won't fix it.

class Integer
  def digitSum
    self.to_s.split(//).inject(0) { |s, d| s + d.to_i }
  end
end

class CreditCard

  TYPE_MATCH = {
    :visa => /^4/,
    :discover => /^6011/,
    :amex => /^3(4|7)/,
    :mastercard => /^5[1-5]/
  }

  SIZE_MATCH = {
    :visa => [13, 16],
    :discover => [16],
    :amex => [15],
    :mastercard => [16]
  }

  CARD_NAME = {
    :visa => "Visa",
    :discover => "Discover",
    :amex => "American Express",
    :mastercard => "MasterCard",
    :unknown => "unknown",
    nil => "invalid"
  }

  def initialize(cc)
    @cc = cc.delete(" ")
  end

  def valid?
    self.type && (self._luhn % 10).zero?
  end

  def type
    TYPE_MATCH.each do |k, v|
      if @cc =~ v
        if SIZE_MATCH[k].find { |n| @cc.size == n }
          return k
        else
          return nil
        end
      end
    end
    :unknown
  end

  def name
    CARD_NAME[self.type]
  end

  def to_s
    @cc
  end

  def _luhn
    sum = 0
    cc = @cc.split(//)
    until cc.empty?
      a, b = cc.pop.to_i, cc.pop.to_i
      sum += a + (2 * b).digitSum
    end
    sum
  end
end

cc = CreditCard.new(ARGV.join)
if !cc.valid?
  puts "Card #{cc} is invalid."
elsif cc.type != :unknown
  puts "Card #{cc} is a valid #{cc.name}."
else
  puts "Card #{cc} is unknown type, may be valid."
end