http://markdaggett.com/blog/2011/12/01/transactions-in-rails/
link stary ale jary
co robisz w validate_amount
- pobierash stock na podstawie ID
- sprawdzasz czy różnica stanu sell i stock da negatywa
jedyny błąd to ufanie parametrowi z kontrolera
- kontroler dostał request z parametrem stock_id
- kontroler znalazł rekord w Stock i wywołał Twój Sell_instance.save
pomiędzy znalezieniem Stock rekordu a wywołaniem Sell_instance.save inny proces mógł wejść w transakcję usuwania rekordu w Stock i go usunąć
Sell_instance.save zrobi
def validate_amount
a = Stock.find(stock_id)
#
# stock usunięty a == nil
#
errors.add(:amount, "Not enough in a stock") if( a.amount-self.amount) < 0
#
# if z errors.add wyrzuci unexpected nil
#
end
Kontroler zbiera dane z różnych miejsc dla response, nie ma pojęcia że:
- sprawdzenie stanu Stock
- utworzenie rekordu w Sell
- umniejszenie stanu stock
mają nastąpić “jednocześnie” i w razie błędu na którymkolwiek etapie bezpiecznie temat odkręcić
baza danych funkcjonuje nie tylko od COMMIT do COMMIT
transakcje dają możliwość stackować kilka operacji na różnych tabelach i bezpiecznie je cofać
kontroler wykonał jedną transakcję pytając Stock.find…
Sell_instance.save wykonał drugą transakcję…
Błąd bo pomiędzy nimi każdy inny proces może wcisnąć bazie swoją transakcję usuwającą rekord w stock
Inna sprawa to nie podoba mi się ręczne dodawanie błędów i brak walidatora w Stock
class Stock < ActiveRecord::Base
# jeżeli ilość może być ułamkowa typu 2.5kg wyrzuć only_integer: true
validates :amount, numericality: { only_integer: true, greater_than_or_equal: 0 }
end
z tym walidatorem możesz w Sell.before_save poprostu odjąć stan i spróbować Stock_instance.save… sam dorzuci błąd
podoba mi się rozwiązanie @Biscuit z amount_available?
Nawet jeśli to tylko demo “for science!” to właśnie teraz jest okazja na zapoznanie się z constraint / transaction / trigger. Przeczytaj cały artykuł o migracjach bo dorzucenie blokady null wartości domyślnej 0 dla amount w każdej tabeli to jest abecadło i constraint na amount > 0 odchudzi Ci walidacje w modelach
http://www.tutorialspoint.com/sqlite/sqlite_constraints.htm
constraint check z linka jest dla Ciebie
Magia railsów ActiveRecord pomaga, w zakresie w czym Cię wyręcza jest dobrze udokumentowana, niestety przysłania za dużo i SQL nie jest tak widoczny jak ruby