require 'erb' module Mack module Controller # :nodoc: # All controllers in a Mack application have to extend this class. I'll be honest, if they don't extend this class # then, well, things just won't work very well! # # Example: # class MyAwesomeController < Mack::Controller::Base # def index # render(:text => "Hello World!") # end # end class Base # See Mack::Request for more information. attr_reader :request # See Mack::Response for more information. attr_reader :response # The 'underscore' version of the controller requested. Example: 'my_awesome_controller' attr_reader :controller_name # The name of the action being requested. attr_reader :action_name # See Mack::CookieJar for more information. attr_reader :cookies def initialize(request, response, cookies) @request = request @response = response @render_options = {} @render_performed = false @controller_name = params(:controller) @action_name = params(:action) @cookies = cookies end # Gives access to all the parameters for this request. def params(key) self.request.params(key) end # Gives access to the session. See Mack::Session for more information. def session self.request.session end # This does the heavy lifting for controllers. It calls the action, and then completes the rendering # of the action to a String to send back to Rack. def run run_filters(:before) # check to see if this controller responds to this action. # only run public methods! if self.public_methods.include?(self.action_name) # call the action and capture the results to a variable. @result_of_action_called = self.send(self.action_name) else # there is no action on this controller, so call the render method # which will check the view directory and run action.html.erb if it exists. render(:action => self.action_name) end run_filters(:after) # do the work of rendering. @final_rendered_action = complete_layout_render(complete_action_render) run_filters(:after_render) @final_rendered_action end # This method can be called from within an action. This 'registers' the render that you # would like to happen once the action is completed. # # It's important to note that calling render in an action does NOT end the processing of # the action. The action will continue to process unless you explicity put 'return' before the # render call. # # If you call render twice in an action then a Mack::Errors::DoubleRender error will be thrown. # # An implicit render will happen if one is not specified in the action. # # Only :action and :text will get layouts wrapped around them. # # Examples: # class MyAwesomeController < Mack::Controller::Base # # This will render the text 'Hello World!' to the screen. # def index # render(:text => "Hello World!") # end # # # This will render MACK_ROOT/views/my_awesome_controller/foo.html.erb # def show # render(:action => :foo) # end # # # This will raise a Mack::Errors::DoubleRender error. # def edit # render(:text => "Hello World!") # render(:action => :foo) # end # # # This will render MACK_ROOT/views/my_awesome_controller/delete.html.erb # def delete # end # # # This will render the text 'Hello World!' to the screen. Assuming that # # there is no file: MACK_ROOT/views/my_awesome_controller/update.html.erb # # The reason for this is if the view for the action doesn't exist, and the # # last thing returned from the action is a String, that string will be returned. # def update # "Hello World!" # end # # # This will raise a Mack::Errors::InvalidRenderType error. Assuming that # # there is no file: MACK_ROOT/views/my_awesome_controller/create.html.erb # def create # @user = User.find(1) # end # # # This will raise a Errno::ENOENT error. Assuming that # # there is no file: MACK_ROOT/views/my_awesome_controller/bar.html.erb # def bar # render(:action => "bar") # end # # # This will render a file from the public directory. Files served from the # # public directory do NOT get layouts. The default file extension for files # # served from the public directory is .html. This can be overridden with the # # :ext => "." option. # def show_public_file # render(:public => "my/files/foo") # end # # # This will render a file from the public directory. Files served from the # # public directory do NOT get layouts. The default file extension for files # # served from the public directory is .html. This can be overridden with the # # :ext => "." option. # def show_public_xml_file # render(:public => "my/files/foo", :ext => ".xml") # end # # # This will render a partial. In this case it will look for: # # MACK_ROOT/views/my_awesome_controller/_latest_news.html.erb # # Partials do NOT get wrapped in layouts. # def latest_news # render(:partial => :latest_news) # end # # # This will render a partial. In this case it will look for: # # MACK_ROOT/views/some_other/_old_news.html.erb # # Partials do NOT get wrapped in layouts. # def latest_news # render(:partial => "some_other/old_news") # end # end def render(options = {:action => self.action_name}) raise Mack::Errors::DoubleRender.new if render_performed? unless options[:action] || options[:text] options = {:layout => false}.merge(options) end @render_options = options @render_performed = true end # This will redirect the request to the specified url. A default status of # 302, Moved Temporarily, is set if no status is specified. A simple HTML # page is rendered in case the redirect does not occur. A server side # redirect is also possible by using the option :server_side => true. # When a server side redirect occurs the url must be a 'local' url, not an # external url. The 'original' url of the request will NOT change. def redirect_to(url, options = {}) options = {:status => 302}.merge(options) raise Rack::ForwardRequest.new(url) if options[:server_side] response.status = options[:status] response[:location] = url render(:text => redirect_html(request.path_info, url, options[:status])) end # Returns true/false depending on whether the render action has been called yet. def render_performed? @render_performed end # Gives access to the MACK_DEFAULT_LOGGER. def logger MACK_DEFAULT_LOGGER end private def run_filters(type) filters = self.class.controller_filters[type] return true if filters.empty? filters.each do |filter| if filter.run?(self.action_name.to_sym) r = self.send(filter.filter_method) raise Mack::Errors::FilterChainHalted.new(filter.filter_method) unless r end end end def complete_layout_render(action_content) @content_for_layout = action_content # if @render_options[:action] || @render_options[:text] # only action and text should get a layout. # if a layout is specified, use that: # i use has_key? here because we want people # to be able to override layout with nil/false. if @render_options.has_key?(:layout) if @render_options[:layout] return Mack::ViewBinder.new(self).render(@render_options.merge({:action => "layouts/#{@render_options[:layout]}"})) else # someone has specified NO layout via nil/false return @content_for_layout end else layout # use the layout specified by the layout method return Mack::ViewBinder.new(self).render(@render_options.merge({:action => "layouts/#{layout}"})) end # end @content_for_layout end def complete_action_render if render_performed? return Mack::ViewBinder.new(self, @render_options).render(@render_options) else begin # try action.html.erb return Mack::ViewBinder.new(self).render({:action => self.action_name}) rescue Exception => e if @result_of_action_called.is_a?(String) @render_options[:text] = @result_of_action_called return Mack::ViewBinder.new(self).render(@render_options) end raise e end end end # complete_action_render def layout :application end public class << self # See Mack::Controller::Filter for more information. def before_filter(meth, options = {}) add_filter(:before, meth, options) end # See Mack::Controller::Filter for more information. def after_filter(meth, options = {}) add_filter(:after, meth, options) end # See Mack::Controller::Filter for more information. def after_render_filter(meth, options = {}) add_filter(:after_render, meth, options) end def add_filter(type, meth, options) # :nodoc: controller_filters[type.to_sym] << Mack::Controller::Filter.new(meth, options) end def controller_filters # :nodoc: @controller_filters = {:before => [], :after => [], :after_render => []} unless @controller_filters @controller_filters end # Sets a layout to be used by a particular controller. # # Example: # class MyAwesomeController < Mack::Controller::Base # # Sets all actions to use: "#{MACK_ROOT}/app/views/layouts/dark.html.erb" as they're layout. # layout :dark # # def index # # Sets this action to use: "#{MACK_ROOT}/app/views/layouts/bright.html.erb" as it's layout. # render(:text => "Welcome...", :layout => :bright) # end # # def index # # This will no use a layout. # render(:text => "Welcome...", :layout => false) # end # end # # The default layout is "#{MACK_ROOT}/app/views/layouts/application.html.erb". # # If a layout is specified, and it doesn't exist a Mack::Errors::UnknownLayout error will be raised. def layout(lay) self.class_eval do define_method(:layout) do lay end end end # layout end # class << self end # Base end # Controller end # Mack