Since Merb has the lovely property of rendering the output of an
action, using ruby-level exceptions to render error pages in Merb
could be a cute way to approach error handling.
Suppose one has an action for editing a product which you would like
to restrict to administrators:
def edit
raise AdminAccessReqired unless session[:user] and session[:user].admin?
@product = Product.find(params[:id])
raise UnknownProduct if @product.nil?
render
end
Where AdminAccessReqired and UnknownProduct are ControllerExceptions.
The controller exception hierarchy would be rooted with the base
class, Merb::ControllerException. The Merb dispatcher would rescue any
exception which was a kind_of ControllerException. Derived from
ControllerException would be an exception for each of the HTTP error
codes. For example:
module ControllerExceptions
class Unauthorized < ControllerException
STATUS_CODE = 401
# ...
class Forbidden < ControllerException
STATUS_CODE = 403
# ...
class NotFound < ControllerException
STATUS_CODE = 404
# ...
Application authors could place addition derivations into
dist/app/exceptions. These must be children of an already defined Merb
controller exception class. The user defined exceptions would have a
method called ''action'' which acts like typical controller
action. It
is called to render a page when the exception is raised. For example
# dist/app/exceptions/admin_access_required.rb
class AdminAccessRequired < Merb::ControllerExceptions::Unauthorized
def action
if session[:user].nil?
redirect ''/login''
else
render # views/exceptions/admin_access_required.rhtml
end
end
end
If the user is logged in but does not have administrative access, this
will render a page describing the problem with the proper HTTP status
code 401.
The UnknownProduct exception might look like this:
# dist/app/exceptions/unknown_product.rb
class UnknownProduct < Merb::ControllerExceptions::NotFound
def action
@id = params[:id]
render # views/exceptions/unknown_product.rhtml
end
end
If passed a bad id, the server will respond with 404 and a page that
is specific to missing a product.
The advantages of this scheme are
- Simplifies controller action definitions by placing exceptional
logic elsewhere
- Eases the conformity to HTTP by returning proper error codes
- Could allow for before and after filters around a restricted type of
exceptions (eg, for logging purposes)
- Further modularizes testing code: one needs only to check that an
action raise a particular exception
Thoughts?
I would gladly implement this and submit a patch if the list thinks
this that it is a worth feature.
The idea is from Robert Hahn:
http://blog.roberthahn.ca/articles/2007/06/22/setting-http-response-codes-with-exceptions
ry
Hi~ On Jul 31, 2007, at 10:57 AM, ry dahl wrote:> Since Merb has the lovely property of rendering the output of an > action, using ruby-level exceptions to render error pages in Merb > could be a cute way to approach error handling. > > Suppose one has an action for editing a product which you would like > to restrict to administrators: > > def edit > raise AdminAccessReqired unless session[:user] and session > [:user].admin? > @product = Product.find(params[:id]) > raise UnknownProduct if @product.nil? > render > end > > Where AdminAccessReqired and UnknownProduct are ControllerExceptions. > > The controller exception hierarchy would be rooted with the base > class, Merb::ControllerException. The Merb dispatcher would rescue any > exception which was a kind_of ControllerException. Derived from > ControllerException would be an exception for each of the HTTP error > codes. For example: > > module ControllerExceptions > > class Unauthorized < ControllerException > STATUS_CODE = 401 > # ... > > class Forbidden < ControllerException > STATUS_CODE = 403 > # ... > > class NotFound < ControllerException > STATUS_CODE = 404 > # ... > > Application authors could place addition derivations into > dist/app/exceptions. These must be children of an already defined Merb > controller exception class. The user defined exceptions would have a > method called ''action'' which acts like typical controller action. It > is called to render a page when the exception is raised. For example > > # dist/app/exceptions/admin_access_required.rb > class AdminAccessRequired < Merb::ControllerExceptions::Unauthorized > def action > if session[:user].nil? > redirect ''/login'' > else > render # views/exceptions/admin_access_required.rhtml > end > end > end > > If the user is logged in but does not have administrative access, this > will render a page describing the problem with the proper HTTP status > code 401. > > The UnknownProduct exception might look like this: > > # dist/app/exceptions/unknown_product.rb > class UnknownProduct < Merb::ControllerExceptions::NotFound > def action > @id = params[:id] > render # views/exceptions/unknown_product.rhtml > end > end > > If passed a bad id, the server will respond with 404 and a page that > is specific to missing a product. > > The advantages of this scheme are > - Simplifies controller action definitions by placing exceptional > logic elsewhere > - Eases the conformity to HTTP by returning proper error codes > - Could allow for before and after filters around a restricted type of > exceptions (eg, for logging purposes) > - Further modularizes testing code: one needs only to check that an > action raise a particular exception > > Thoughts? > I would gladly implement this and submit a patch if the list thinks > this that it is a worth feature. > > The idea is from Robert Hahn: > http://blog.roberthahn.ca/articles/2007/06/22/setting-http-response- > codes-with-exceptionsI like the idea in general but I am slightly concerned about using exceptions extensively for flow control like this because ruby exceptions are really resource intensive and slow as molasses. I''ve seen exception handling be the cpu bottleneck in a lot of production rails applications. Exceptions have to unwind the stack and setup a bunch of cointext stuff that makes them have a lot of overhead. I''d be willing to entertain the idea though as it does seem like a very nice api, but it would require benchmarking before I would put it in merb core. Can you make a small proof of concept patch that we can benchmark a bit and then decide? Cheers- -- Ezra Zygmuntowicz -- Founder & Ruby Hacker -- ez at engineyard.com -- Engine Yard, Serious Rails Hosting -- (866) 518-YARD (9273)
> Can you make a small proof of concept patch that we can benchmark a > bit and then decide?Attached is a patch which implements the controller exception api. It''s hacky and not meant for production. In merb_exceptions.rb is the definition of Merb::ControllerExceptions::Base. That is an abstract base class. Derived from Base are a bunch of other classes: class NoContent < Base; STATUS = 204; end class ResetContent < Base; STATUS = 205; end class PartialContent < Base; STATUS = 206; end class MultipleChoices < Base; STATUS = 300; end To use this, one would derive a class from one of these and write a method :merb_action (i didn''t use :action because of name conflicts) which should work more or less like a typical controller action. The patch contains specs showing it in action with render :layout This should be enough to benchmark? The erb file for MyException#merb_action is looked for at app/views/exceptions/my_exception.erb ry -------------- next part -------------- A non-text attachment was scrubbed... Name: controller_exceptions.diff Type: application/octet-stream Size: 9315 bytes Desc: not available Url : http://rubyforge.org/pipermail/merb-devel/attachments/20070802/e035470e/attachment.obj