Index: lib/merb/merb_view_context.rb =================================================================== --- lib/merb/merb_view_context.rb (revision 371) +++ lib/merb/merb_view_context.rb (working copy) @@ -6,6 +6,7 @@ PROTECTED_IVARS = %w[@_new_cookie @method @env + @controller @_body @_fingerprint_before @_session Index: lib/merb/merb_exceptions.rb =================================================================== --- lib/merb/merb_exceptions.rb (revision 371) +++ lib/merb/merb_exceptions.rb (working copy) @@ -4,6 +4,90 @@ end module Merb + + module ControllerExceptions + class Base < StandardError + include Merb::RenderMixin + include Merb::ControllerMixin + + def _template_root + @controller._template_root + end + + def set_controller(controller) + @controller = controller + @controller.status = status + end + + def request; @controller.request; end + def params; @controller.params; end + def cookies; @controller.cookies; end + def headers; @controller.headers; end + def session; @controller.session; end + def response; @controller.response; end + + def status + self.class.const_get(:STATUS) + end + + def call_action + # before filters? + out = merb_action + # after filters? + + # overwrite status changes during action + @controller.status = status + out + end + + def merb_action + "Error #{status}! (this would be something explaining the error code)" + end + end + + # created with: + # ruby -r rubygems -e "require 'mongrel'; puts Mongrel::HTTP_STATUS_CODES.keys.sort.map {|code| %Q{class #{Mongrel::HTTP_STATUS_CODES[code].gsub /[^\w]/,''} < Base; STATUS = #{code}; end} }.join(%Q{\n})" + # we could do some magic here, but i think it's more instructive to just + # have these values copied + class Continue < Base; STATUS = 100; end + class SwitchingProtocols < Base; STATUS = 101; end + class OK < Base; STATUS = 200; end + class Created < Base; STATUS = 201; end + class Accepted < Base; STATUS = 202; end + class NonAuthoritativeInformation < Base; STATUS = 203; end + class NoContent < Base; STATUS = 204; end + class ResetContent < Base; STATUS = 205; end + class PartialContent < Base; STATUS = 206; end + class MultipleChoices < Base; STATUS = 300; end + class MovedPermanently < Base; STATUS = 301; end + class MovedTemporarily < Base; STATUS = 302; end + class SeeOther < Base; STATUS = 303; end + class NotModified < Base; STATUS = 304; end + class UseProxy < Base; STATUS = 305; end + class BadRequest < Base; STATUS = 400; end + class Unauthorized < Base; STATUS = 401; end + class PaymentRequired < Base; STATUS = 402; end + class Forbidden < Base; STATUS = 403; end + class NotFound < Base; STATUS = 404; end + class MethodNotAllowed < Base; STATUS = 405; end + class NotAcceptable < Base; STATUS = 406; end + class ProxyAuthenticationRequired < Base; STATUS = 407; end + class RequestTimeout < Base; STATUS = 408; end + class Conflict < Base; STATUS = 409; end + class Gone < Base; STATUS = 410; end + class LengthRequired < Base; STATUS = 411; end + class PreconditionFailed < Base; STATUS = 412; end + class RequestEntityTooLarge < Base; STATUS = 413; end + class RequestURITooLarge < Base; STATUS = 414; end + class UnsupportedMediaType < Base; STATUS = 415; end + class InternalServerError < Base; STATUS = 500; end + class NotImplemented < Base; STATUS = 501; end + class BadGateway < Base; STATUS = 502; end + class ServiceUnavailable < Base; STATUS = 503; end + class GatewayTimeout < Base; STATUS = 504; end + class HTTPVersionnotsupported < Base; STATUS = 505; end + end + class MerbError < StandardError; end class Noroutefound < MerbError; end class MissingControllerFile < MerbError; end Index: lib/merb/merb_controller.rb =================================================================== --- lib/merb/merb_controller.rb (revision 371) +++ lib/merb/merb_controller.rb (working copy) @@ -96,15 +96,24 @@ def dispatch(action=:index) start = Time.now - if !self.class.callable_actions.include?(action.to_s) - set_status(404) - @_body = IO.read(DIST_ROOT / 'public/404.html') rescue "404: Not Found" - MERB_LOGGER.info "Action: #{action} not in callable_actions: #{self.class.callable_actions}" - else - setup_session - super(action) - finalize_session + begin + if !self.class.callable_actions.include?(action.to_s) + # raise ControllerExceptions::NotFound. no need for public/404.html + set_status(404) + @_body = IO.read(DIST_ROOT / 'public/404.html') rescue "404: Not Found" + # + MERB_LOGGER.info "Action: #{action} not in callable_actions: #{self.class.callable_actions}" + else + setup_session + super(action) + finalize_session + end + rescue ControllerExceptions::Base => e + # do not pass status, the exception will set that + e.set_controller(self) # for access to session, params, etc + @_body = e.call_action end + @_benchmarks[:action_time] = Time.now - start MERB_LOGGER.info("Time spent in #{self.class}##{action} action: #{@_benchmarks[:action_time]} seconds") end @@ -119,6 +128,11 @@ @_status end + # TODO - this is not the best stratagy + def status=(s) + @_status = s + end + # Accessor for @_request. Please use request and never @_request directly. def request @_request Index: lib/merb/mixins/render_mixin.rb =================================================================== --- lib/merb/mixins/render_mixin.rb (revision 371) +++ lib/merb/mixins/render_mixin.rb (working copy) @@ -91,7 +91,13 @@ # Renders the buffalo.xerb template for the current controller. # def render(opts={}, &blk) - action = opts[:action] || params[:action] + + action = if kind_of?(Merb::ControllerExceptions::Base) + self.class.name.snake_case.split('::').last + else + opts[:action] || params[:action] + end + opts[:layout] ||= _layout case @@ -234,6 +240,8 @@ def find_template(opts={}) if template = opts[:template] path = _template_root / template + elsif opts[:action] and kind_of?(Merb::ControllerExceptions::Base) + path = _template_root / 'exceptions' / opts[:action] elsif action = opts[:action] segment = self.class.name.snake_case.split('::').join('/') path = _template_root / segment / action Index: specs/merb/merb_dispatch_spec.rb =================================================================== --- specs/merb/merb_dispatch_spec.rb (revision 371) +++ specs/merb/merb_dispatch_spec.rb (working copy) @@ -462,4 +462,9 @@ controller.params[:post_id].should == '1' controller.body.should == :stats end + + it "should show the error page" do + controller, action = request(:get, '/foo/error') + controller.body.should == "oh no!" + end end Index: specs/merb/merb_render_spec.rb =================================================================== --- specs/merb/merb_render_spec.rb (revision 371) +++ specs/merb/merb_render_spec.rb (working copy) @@ -137,5 +137,21 @@ content = c.render(:action => "test") content.clean.should == "Hello!" end + + it "should render exception's view without layout" do + c = Examples.build @in.req, @in.env, {}, @res + x = AdminAccessRequired.new + x.set_controller(c) + content = x.render :layout => :none + content.clean.should == "An Error Occurred!" + end + + it "should render exception's view with layout" do + c = Examples.build @in.req, @in.env, {}, @res + x = AdminAccessRequired.new + x.set_controller(c) + content = x.render + content.clean.should == "An Error Occurred!" + end end Index: specs/fixtures/controllers/dispatch_spec_controllers.rb =================================================================== --- specs/fixtures/controllers/dispatch_spec_controllers.rb (revision 371) +++ specs/fixtures/controllers/dispatch_spec_controllers.rb (working copy) @@ -7,9 +7,20 @@ def bar "bar" end + + def error + raise AdminAccessRequired + "Hello World!" + end end +class AdminAccessRequired < Merb::ControllerExceptions::Unauthorized + def merb_action + "oh no!" + end +end + class Posts < Merb::Controller # GET /posts # GET /posts.xml Index: specs/fixtures/views/exceptions/admin_access_required.herb =================================================================== --- specs/fixtures/views/exceptions/admin_access_required.herb (revision 0) +++ specs/fixtures/views/exceptions/admin_access_required.herb (revision 0) @@ -0,0 +1 @@ +<%= "An Error Occurred!" %> \ No newline at end of file