Przypuśćmy, że mam jakiś model Model, który ma metodę awesome oraz atrybut status. Założenie jest takie, że metodę awesome można wywołać tylko wtedy, gdy status = “GOOD”.
Wiadomo, że w kontrolerze zrobię before_filter który sprawdzi czy mogę zrobić akcję, która prowadzi do wywołania metody awesome dla pewnej instancj Model. Pytanie: czy powinienem również w metodzie awesome rzucać wyjątek (lub jakoś inaczej nie pozwolić na to by metoda ta została wykonana). Na pewno to zabezpiecza przed sytuacją, w której kontroler nie jest odpowiednio zabezpieczony (lub metody wywoływane są jakimiś innymi drogami?). Ale czy coś takiego nie powoduje, że projekt przestaje być DRY (mamy zabezpieczenie w 2 miejscach).
Chciałbym poznać Wasze opinie, jak wy robicie w swoich projektach - czy zabezpieczacie wywoałania takich metod na poziomie modelu ?
Wszystko zależy od wymagań, ale tak w dużym skrócie to:
za spójność danych odpowiada warstwa modelu
za dostęp do logiki aplikacji odpowiada kontroler
Musisz zdecydować czy to, co robisz, jest bardziej kwestią spójności danych czy kwestią dostępu logiki aplikacji. Oraz rozpisać scenariusze obejścia każdego z obu zabezpieczeń.
Jeśli jakiejś metody modelu nie można wywołać w jakiejś sytuacji to oczywiście powinieneś rzucić wyjątek. W końcu może być ona wywołana także w konsoli przez zaspanego programistę w złym momencie.
Powinieneś rzucić także zapewne wyjątek w kontrolerze. Oraz ukryć akcję w widoku. Owszem nie jest jest zbytnio DRY ale tylko troszkę. W końcu albo aplikacja ma być bezpieczna albo nie.
[code]class MyModel < AR::Base # zapewne
def awesomable?
[true, false].random # Implement it…
end
def awesome
raise CannotBecomeAwesomeUnlessSomethingThatYouHaveNotDoneYet unless awesomable?
awesome!
end
private
def awesome!
set_status(:awesome) # or something…
end
end
class MyController
def update_awesome
raise MyModel::CannotBecomeAwesomeUnlessSomethingThatYouHaveNotDoneYet unless my_model.awesomable?
end
end
[quote=paneq][code]class MyModel < AR::Base # zapewne
def awesomable?
[true, false].random # Implement it…
end
def awesome
raise CannotBecomeAwesomeUnlessSomethingThatYouHaveNotDoneYet unless awesomable?
awesome!
end
private
def awesome!
set_status(:awesome) # or something…
end
end
class MyController
def update_awesome
raise MyModel::CannotBecomeAwesomeUnlessSomethingThatYouHaveNotDoneYet unless my_model.awesomable?
end
end
widok
link_to update_awesome_path(object) unless object.awesomable?[/code]
[/quote]
Po co 2 razy pisać ten sam kod (raise MyModel::CannotBecomeAwesomeUnlessSomethingThatYouHaveNotDoneYet unless my_model.awesomable?)?
Tak przykładowo tylko w kontrolerze. Pewnie normalnie by tam było coś w stylu redirect_to unauthorized_path
.
Rzucenie wyjątku w modelu i użyciu rescue_from w kontrolerze ma tą zaletę, że jest wydajnościowo efektywniejsze (sprawdzanie uprawnień następuje tylko raz).
Też fajne aczkolwiek jestem zwolennikiem unikania rzucania wyjątku bez potrzeby.
Z punktu widzenia HTTP masz rację aczkolwiek jeśli chcesz jeszcze jakieś biznesow rzeczy zrobić typu zalgowanie do bazy tego faktu ale wyświetlenie formularza, w którym można komuś wysłać prośbę do udostępnienie tego zasobu to nie wydaje mi się, by było to takie złe. Swoją drogę użyłbym bezwzględnej ścieżki w podanym przez Ciebie wypadku.
Bezwzględnej nie znam, bo to zależy od miejsca instalacji, a szkoda marnowania cykli na jej obliczanie. Ścieżka względna jest stała w danym kontrolerze.
A formularz, czy wszystkie biznesowe akcje też lepiej wykonać od razu, a nie polegać na redirect - co jeśli klient tam nie dotrze? Po co przekazywać wszystkie parametry innej akcji, skoro w tej mam je już dostępne?
Przecież render :action to nie jest statyczna strona błędu, tylko zwyczajny widok. Ma taką samą możliwość pokazania formularza co standardowy. A logowanie do bazy przez redirect? Tego nie rozumiem. Przecież logować do bazy trzeba od razu, w tym samym kontrolerze i w tej samej akcji.