View dla algorytmu Luhna

Witam, robię małą aplikację, która ma za zadanie sprawdzić poprawność numeru karty kredytowej.
Oto link do zadania :

Tutaj mam trzy modele

card_type.rb

# Method used to get the card type.
module Card_type
    def find_type(credit_card)
        # Making sure that the card number is passed as a string
        credit_card = credit_card.to_s
        # Set of statements to return the appropriate card type
        # Firstly the code checks the cards's length
        # Secondly regexp, returns 0 which signals validation
        return "AMEX"    if credit_card.length == 15 && (credit_card =~ /^(34|37)/) == 0
        return "Discover"    if credit_card.length == 16 && (credit_card =~ /^6011/) == 0
        return "MasterCard"    if credit_card.length == 16 && (credit_card =~ /^(5[1-5])/) == 0
        return "VISA"    if [13,16].include?(credit_card.length) && (credit_card =~ /^4/) == 0
        return "Unknown"
    end
end

luhn_validation.rb

# Method/ module used to check if card is valid/invalid
module Luhn_validation

  def luhn(cc_number)
    result = 0
    nums = cc_number.to_s.split("")
    nums.each_with_index do |item, index|
      if index.even?
        result += item.to_i * 2 >9 ? item.to_i*2-9 : item.to_i*2
      else
        result +=item.to_i
        end
      end
      if (result % 10) == 0
      return true
      else
      return false
    end
  end
  
end
    
#Tests
#puts luhn(4111111111111111)
#puts luhn(4111111111111)
#puts luhn(4012888888881881)
#puts luhn(378282246310005)
#puts luhn(6011111111111117)
#puts luhn(5105105105105100)
#puts luhn(5105105105105106)
#puts luhn(9111111111111111)

credit_card.rb

require File.dirname(__FILE__)+"/card_type.rb"
require File.dirname(__FILE__)+"/luhn_validation.rb"

class CreditCard < ActiveRecord::Base

    include Card_type
    include Luhn_validation

    # apply methods on the inputed number

    def self.initialize(cc_number)
        self.type = find_type(cc_number)
        self.validity = luhn_validation(cc_number) ? "valid" : "invalid"
    end

end

Mój problem polega na zrobieniu prostego view, do moich modeli.

Chciałbym zrobić prosty formularz label, field,button
a w indexie żeby wyświetlało wszystkie wprowadzone dane np:
numer wprowadzonej karty//typ karty// luhn działa czy nie

Próbowałem już stworzyć to co chcę za pomocą prostego scaffolda, ale nie potrafiłem accessować method z modelu w controllerze. Railsy wyświetlały komunikat na temat nie rozpoznanej metody “find_type()”

Kolejne pytanie, czy to dobra praktyka że zawarłem moje metody w modelu?
Jeżeli znajdują tutaj się błędy w kodzie czy jakieś “nie dobre nawyki”, też proszę o zwrócenie uwagi.

Proszę o wyrozumiałość, mam doświadczenie małe doświadczenie z innych języków programowania,
nie miałem też większych problemów z napisaniem kodu: :wink: , ale kiedy dochodzi do przełożenia tego na view kompletnie padam.

Dziękuje z góry za wasze odpowiedzi

Adrian

Skoro używasz ActiveRecord to powinieneś bardziej skupić się na tym, aby cały kod wykorzystywał wszystkie konwencje. Przede wszystkim do sprawdzenia poprawności numeru powinieneś użyć walidacji ActiveRecord (http://guides.rubyonrails.org/active_record_validations.html). Własną metodę (algorytm) możesz napisać zarówno w osobnym module, jak i w samum modelu CreditCard (na początek polecam to drugie).

Ogólna zasada jest taka, że walidacje robimy przed zapisaniem instancji, a nie po inicjalizacji. Zainicjalizować możemy dowolny model ustawiając mu dowolne atrybuty, a sprawdzenie poprawności powinno się odbyć przy próbie zapisania. Taka też jest logika, która powinna być odzwierciedlona w kontrolerze. Model powinien być tam zainicjalizowany wartościami z formularza, a kiedy nie uda się go zapisać przez błąd walidacji, wtedy użytkownikowi zwrócony zostanie odpowiedni komunikat. Wykorzystując konwencje w AR w modelu automatycznie ustawiony zostanie hash zawierający atrybuty i błędy, które wystąpiły, więc do formularza przekażemy konkretne błędy dotyczące konkretnych atrybutów.

Aby wykonać logikę przy inicjalizacji instancji modelu ActiveRecord nie definiujemy konstruktora (def initialize(…)), tylko używamy callbacków, np: :after_initialize (http://guides.rubyonrails.org/active_record_callbacks.html). Da oczywiście przeciążać konstruktor, ale to nie jest na pewno co Ty na obecnym etapie chcesz zrobić.

Co do samego formularza, to nie bardzo jestem w stanie cokolwiek napisać - spróbuj sobie na podstawie tutoriali stworzyć prosty formularz wykorzystując form_for i ew. wtedy napisz jakie wystąpiły problemy.

Propo dobrych nawyków:

if (result % 10) == 0
  return true
else
  return false
end

W Rubym wartość ostatniego wyrażenia w metodzie/funkcji będzie przez nią zwrócona, więc zamiast powyższej logiki zwyczajowo wystarczy na końcu napisać (nie potrzeba nawet return):

result % 10 == 0
1 Like

Włożyłem metody luhn i find_type to mojego credit_card.rb tak jak @mdrozdziel zasugerował.

class CreditCard < ActiveRecord::Base
  after_validation :find_type
  after_validation :luhn

#Methods for luhn validation

#First checking the type of card

    private

    def find_type
        # Making sure that the card number is passed as a string
        credit_card = self.number.delete(' ')
        # Set of statements to return the appropriate card type
        # Firstly the code checks the cards's length
        # Secondly regexp, returns 0 which signals validation
        self.provider = "Unknown"
        self.provider = "AMEX"    if credit_card.length == 15 && (credit_card =~ /^(34|37)/) == 0
        self.provider = "Discover"    if credit_card.length == 16 && (credit_card =~ /^6011/) == 0
        self.provider = "MasterCard"    if credit_card.length == 16 && (credit_card =~ /^(5[1-5])/) == 0
        self.provider = "VISA"    if [13,16].include?(credit_card.length) && (credit_card =~ /^4/) == 0
    end

#Secondly applying the Luhn algorithm on the number to check is the number valid or not

    def luhn
    result = 0
    nums = self.number.delete(' ').split("").reverse!
    nums.each_with_index do |item, index|
      if index.odd?
          if item.to_i*2>9
              result+= item.to_i*2-9
        else 
            result+= item.to_i*2
        end
      else
        result +=item.to_i
        end
      end
      if (result % 10) == 0
      self.validation = "valid"
      else
      self.validation = "invalid"
    end
  end

end

Był też wcześniej drobny błąd w logice. Iteracja przez numer powinna zaczynać się od ostatniej cyfry a nie od pierwszej.
Poprawiłem to i kod śmiga bez zarzutu.

Dziekuję uprzejmie za pomoc ! :smile:

Do ideału nadal daleko, zasadniczo nie o to chodziło. :slight_smile: Zrób sobie własny walidator:

class CreditCard < ActiveRecord::Base
  ...
  validate :verify_number_checksum

  def verify_number_checksum
    ...
    if (result % 10) != 0
      errors.add(:number, "Błędny numer karty")
    end
  end
end

W ten sposób masz generyczny model AR, nie potrzebujesz przechowywać informacji o statusie validacji w dodatkowej zmiennej #validation, zamiast tego możesz użyć w dowolnym momencie #valid?. Co więcej, przechowywanie tej informacji w zmiennej jest niebezpieczne, bo co będzie jak po zapisaniu wyniku zmieni się numer?

Tak samo nie widzę sensu istnienia atrybutu provider. Wystarczy że ta metoda będzie zwracała typ karty. Ta operacja nie jest ani tak mocno złożona obliczeniowo, ani też wykonywana tak często, aby jej wynik “keszować” w zmiennej.

Skoro chciałeś uwagi, to mam jeszcze jedną. Nazywanie metod w stylu lunh to też nie jest ogólnie dobra metoda. Nie każdy musi wiedzieć, jak nazywa się dany algorytm. Kiedy do kodu siądzie inny programista dużo szybciej załapie co to robi, kiedy będzie się nazywało np. verify_number_checksum :wink:

Tutaj chodziło mi o przeniesienie clasy i method z ruby command line do railsów.
Uzyskałem zamierzony efekt czyli mniej więcej coś takiego :

Miałem też na celu zapisywanie błędnych kart.
Wiem, że w prawdziwej aplikacji przydalaby się walidacja “z miejsca”.
Czyli taka żeby odrzucało odrazu błędne karty.

Cenię sobie twoje uwagi :smiley:

Miałem też na celu zapisywanie błędnych kart.

Jak dla mnie bezsensu, pisz tak jakbyś pisał to w ‘prawdziwej aplikacji’ - warto uczyć się dobrych nawyków.

Oto przyklad ficzera, w ktorym jest nim walidacja i nijak sie ma do ActiveRecord#validates :wink:

Wiec pierwszy przyklad podany przez autora byl calkiem dobry w sensie OOP :slight_smile:

PS. ten require(__FILE__.. jest mega brzydki/dlugi => require_relative './card_type'