<NME> controller.rb
<BEF> # encoding: utf-8

# http://wiki.github.com/botanicus/rango/controllers

require "forwardable"
require "rango/router"
require "rango/exceptions"
require "rango/rack/request"
require "rango/environments"

  class Controller
    include Rango::UrlHelper
    extend Forwardable
    # [master] Change Merb::Controller to respond to #call and return a Rack Array. (wycats)http://rubyurl.com/BhoY
    # @since 0.0.2
    def self.call(env)
      Rango::Router.set_rack_env(env)
      request = Rango::Request.new(env)
      options = env["rango.router.params"] || raise("rango.router.params property has to be setup at least to empty hash")
      controller = self.new(env, options.merge(request.params))
      controller.to_response
    end

    def to_response
      method = request.env["rango.controller.action"].to_sym
      if self.respond_to?(method) # TODO: what about method_missing?
        self.process_action(method)
      else
        raise NotFound, "Controller #{self.class.name} doesn't have method #{method}"
      end
      return self.response.finish
    rescue HttpError => exception
      self.rescue_http_error(exception)
    end

    def process_action(method)
      # If you don't care about arguments or if you prefer usage of params.
      if self.method(method).arity.eql?(0)
        Rango.logger.debug("Calling method #{self.class.name}##{method} without arguments")
        self.response.body = self.method(method).call
      else
        args = self.params.values
        Rango.logger.debug("Calling method #{self.class.name}##{method} with arguments #{args.inspect}")
        self.response.body = self.method(method).call(*args)
      end
    end

    # for routers
    def self.dispatcher(action)
      lambda do |env|
        Rango.logger.info("Dispatching to #{self}##{action} [#{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}]")
        env["rango.controller.action"] = action
        return self.call(env)
      end
    end

    # @since 0.0.1
      self.rescue_http_error(exception)
    rescue Exception => exception # so we can be sure that all the exceptions which occures in controller can be captured by rescue_http_error method
      if Rango.development? or Rango.testing?
        raise exception
      else
        message = "#{exception.class}: #{exception.message}"
    def_delegators :response, :status, :status=
    def_delegators :response, :headers, :headers=

    # @since 0.0.1
    # @return [Rango::Request]
    # @see Rango::Request
    attr_accessor :request, :params, :cookies, :response
    # @since 0.0.1
    # @return [Hash] Hash with params from request. For example <code>{messages: {success: "You're logged in"}, post: {id: 2}}</code>
    attr_accessor :params

    # @since 0.0.2
    # @return [String] Escaped URL (which is RFC recommendation)
    def redirect(url, options = Hash.new)
    # @since 0.0.1
      self.headers["Location"] = URI.escape(url)
    end

    def initialize(env, params = Hash.new)
      @request  = Rango::Request.new(env)
      @response = Rack::Response.new
      @params   = params
      @cookies  = request.cookies
      @session  = request.session
      Rango.logger.inspect(params: params, cookies: cookies, session: session)
    end
    attr_reader :session

    # redefine this method for your controller if you want to provide custom error pages
    # returns response array for rack
    # if you need to change just body of error message, define render_http_error method
    # @api plugin
    def rescue_http_error(exception)
      status, headers, body = exception.to_response
      if self.respond_to?(:render_http_error)
        [status, headers, self.render_http_error(exception)]
      else
        [status, headers, body]
      end
    end
  end
end

    def response
      @response ||= Rack::Response.new
    end

    def_delegators :request, :cookies, :session

    def router_params
      @router_params ||= self.env["rango.router.params"]
    end

    def params
      @params ||= begin
        params = self.request.params
        params.merge!(self.router_params) if router_params
        params
      end
    end

    # redefine this method for your controller if you want to provide custom error pages
    # returns response array for rack
    # if you need to change just body of error message, define render_http_error method
    # @api plugin
    def rescue_http_error(exception)
      # we need to call it before we assign the variables
      body = self.render_http_error(exception)
      [exception.status, exception.headers, body]
    end

    def render_http_error(exception)
      exception.headers["Content-Type"] = "text/html"
      if Rango.production?
        <<-EOF
<h1>Application Error</h1>
<p>
  The application you are trying to reach has currently some issues. Please contact our team and tell us about the troubles. Thank you.
</p>
        EOF
      else
        <<-EOF
<h1>#{exception.class}: #{exception.message}</h1>
<ul>
  <li>#{exception.backtrace.join("</li><li>")}</li>
</ul>
        EOF
      end
    end
  end
end

<MSG> More modular Rango::Controller

<DFF> @@ -11,47 +11,49 @@ module Rango
   class Controller
     include Rango::UrlHelper
     extend Forwardable
+    # for routers
+    def self.dispatcher(action)
+      lambda do |env|
+        Rango.logger.info("Dispatching to #{self}##{action} [#{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}]")
+        env["rango.controller.action"] = action
+        return self.call(env)
+      end
+    end
+
     # [master] Change Merb::Controller to respond to #call and return a Rack Array. (wycats)http://rubyurl.com/BhoY
     # @since 0.0.2
     def self.call(env)
-      Rango::Router.set_rack_env(env)
-      request = Rango::Request.new(env)
-      options = env["rango.router.params"] || raise("rango.router.params property has to be setup at least to empty hash")
-      controller = self.new(env, options.merge(request.params))
+      Rango::Router.set_rack_env(env) # TODO: this shouldn't require router stuff, it might emit an event
+      controller = self.new(env)
       controller.to_response
     end
 
-    def to_response
-      method = request.env["rango.controller.action"].to_sym
-      if self.respond_to?(method) # TODO: what about method_missing?
-        self.process_action(method)
+    def run_action
+      if self.respond_to?(self.action)
+        self.invoke_action(self.action)
       else
-        raise NotFound, "Controller #{self.class.name} doesn't have method #{method}"
+        raise NotFound, "Controller #{self.class.name} doesn't have method #{self.action}"
       end
-      return self.response.finish
-    rescue HttpError => exception
-      self.rescue_http_error(exception)
     end
 
-    def process_action(method)
-      # If you don't care about arguments or if you prefer usage of params.
-      if self.method(method).arity.eql?(0)
-        Rango.logger.debug("Calling method #{self.class.name}##{method} without arguments")
-        self.response.body = self.method(method).call
-      else
-        args = self.params.values
-        Rango.logger.debug("Calling method #{self.class.name}##{method} with arguments #{args.inspect}")
-        self.response.body = self.method(method).call(*args)
-      end
+    # default, redefine in plugin if you need to
+    def invoke_action(action)
+      Rango.logger.debug("Calling method #{self.class.name}##{action} without arguments")
+      self.response.body = self.send(action)
     end
 
-    # for routers
-    def self.dispatcher(action)
-      lambda do |env|
-        Rango.logger.info("Dispatching to #{self}##{action} [#{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}]")
-        env["rango.controller.action"] = action
-        return self.call(env)
-      end
+    def action
+      env["rango.controller.action"].to_sym
+    rescue NoMethodError
+      raise "You have to setup env['rango.controller.action'] to name of action you want to call"
+    end
+
+    def to_response
+      self.run_action
+      self.response.finish
+      [response.status, response.headers, response.body] # this way we got real body rather than response object
+    rescue HttpError => exception
+      self.rescue_http_error(exception)
     end
 
     # @since 0.0.1
@@ -64,14 +66,6 @@ module Rango
     def_delegators :response, :status, :status=
     def_delegators :response, :headers, :headers=
 
-    # @since 0.0.1
-    # @return [Rango::Request]
-    # @see Rango::Request
-    attr_accessor :request, :params, :cookies, :response
-    # @since 0.0.1
-    # @return [Hash] Hash with params from request. For example <code>{messages: {success: "You're logged in"}, post: {id: 2}}</code>
-    attr_accessor :params
-
     # @since 0.0.2
     # @return [String] Escaped URL (which is RFC recommendation)
     def redirect(url, options = Hash.new)
@@ -79,27 +73,43 @@ module Rango
       self.headers["Location"] = URI.escape(url)
     end
 
-    def initialize(env, params = Hash.new)
-      @request  = Rango::Request.new(env)
-      @response = Rack::Response.new
-      @params   = params
-      @cookies  = request.cookies
-      @session  = request.session
-      Rango.logger.inspect(params: params, cookies: cookies, session: session)
+    attr_reader :env
+    def initialize(env)
+      @env = env
+    end
+
+    def request
+      @request ||= Rango::Request.new(env)
+    end
+
+    def response
+      @response ||= Rack::Response.new
+    end
+
+    def_delegators :request, :cookies, :session
+
+    def router_params
+      @router_params ||= begin
+        self.env["rango.router.params"].symbolize_keys || raise("rango.router.params property has to be setup at least to empty hash")
+      end
+    end
+
+    def params
+      @params ||= self.request.params.merge(self.router_params).symbolize_keys
     end
-    attr_reader :session
 
     # redefine this method for your controller if you want to provide custom error pages
     # returns response array for rack
     # if you need to change just body of error message, define render_http_error method
     # @api plugin
     def rescue_http_error(exception)
-      status, headers, body = exception.to_response
-      if self.respond_to?(:render_http_error)
-        [status, headers, self.render_http_error(exception)]
-      else
-        [status, headers, body]
-      end
+      # we need to call it before we assign the variables
+      body = self.render_http_error(exception)
+      [exception.status, exception.headers, body]
+    end
+
+    def render_http_error(exception)
+      "EXCEPTION"
     end
   end
 end

More modular Rango::Controller