Zabezpieczanie wywołań metod na poziomie modelu

Witam,

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 ?

Dzięki.

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

widok

link_to update_awesome_path(object) unless object.awesomable?[/code]

A co powiedziecie na rzucenie wyjątku w modelu i użyciu rescue_from w kontrolerze?

Coś jak:

[code]class TooWeakException < Exception
end

class Hero < AR::Base
def become_super!
raise TooWeakException.new
end
end

class SuperHerosController

rescue_from ‘TooWeakException’ do |exception|
render :text => ‘You are too weak man!’
end

def create
@hero = Hero.find(params[:hero_id])
@hero.become_super!
end
end[/code]

[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.

Redirect jest mało elegancki. Ładniej jest zrobić render :action => "../errors/forbidden", :status => 403

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.