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