require File.dirname(__FILE__)+'/mixins/controller_mixin' require File.dirname(__FILE__)+'/mixins/render_mixin' require File.dirname(__FILE__)+'/mixins/responder_mixin' require File.dirname(__FILE__)+'/merb_request' module Merb # All of your controllers will inherit from Merb::Controller. This # superclass takes care of parsing the incoming headers and body into # params and cookies and headers. If the request is a file upload it will # stream it into a tempfile and pass in the filename and tempfile object # to your controller via params. It also parses the ?query=string and # puts that into params as well. class Controller class_inheritable_accessor :_layout, :_session_id_key, :_template_extensions, :_template_root, :_layout_root self._layout = :application self._session_id_key = :_session_id self._template_extensions = { } self._template_root = File.expand_path(MERB_ROOT / "dist/app/views") self._layout_root = File.expand_path(MERB_ROOT / "dist/app/views/layout") include Merb::ControllerMixin include Merb::RenderMixin include Merb::ResponderMixin attr_accessor :status, :body, :request MULTIPART_REGEXP = /\Amultipart\/form-data.*boundary=\"?([^\";,]+)/n.freeze # parses the http request into params, headers and cookies # that you can use in your controller classes. Also handles # file uploads by writing a tempfile and passing a reference # in params. def initialize(request, env, args, response) @env = MerbHash[env.to_hash] @status, @method, @response, @headers = 200, (env[Mongrel::Const::REQUEST_METHOD]||Mongrel::Const::GET).downcase.to_sym, response, {'Content-Type' =>'text/html'} cookies = query_parse(@env[Mongrel::Const::HTTP_COOKIE], ';,') querystring = query_parse(@env[Mongrel::Const::QUERY_STRING]) if MULTIPART_REGEXP =~ @env[Mongrel::Const::UPCASE_CONTENT_TYPE] && @method == :post querystring.update(parse_multipart(request, $1)) elsif @method == :post if [Mongrel::Const::APPLICATION_JSON, Mongrel::Const::TEXT_JSON].include?(@env[Mongrel::Const::UPCASE_CONTENT_TYPE]) MERB_LOGGER.info("JSON Request") json = JSON.parse(request.read || "") || {} json = MerbHash.new(json) if json.is_a? Hash querystring.update(json) else querystring.update(query_parse(request.read)) end end @cookies, @params = cookies, querystring.update(args) @cookies[_session_id_key] = @params[_session_id_key] if @params.key?(_session_id_key) allow = [:post, :put, :delete] allow << :get if MERB_ENV == 'development' if @params.key?(:_method) && allow.include?(@method) @method = @params.delete(:_method).downcase.intern end @request = Request.new(@env, @method) MERB_LOGGER.info("Params: #{params.inspect}\nCookies: #{cookies.inspect}") end def dispatch(action=:to_s) start = Time.now setup_session if respond_to?:setup_session cought = catch(:halt) { call_filters(before_filters) } @body = case cought when :filter_chain_completed send(action) when String cought when nil filters_halted when Symbol send(cought) when Proc cought.call(self) else raise MerbControllerError, "The before filter chain is broken dude. wtf?" end call_filters(after_filters) finalize_session if respond_to?:finalize_session MERB_LOGGER.info("Time spent in #{self.class}##{action} action: #{Time.now - start} seconds") end # override this method on your controller classes to specialize # the output when the filter chain is halted. def filters_halted "

Filter Chain Halted!

" end # accessor for @request. Please use request and # never @request directly. def request @request end # accessor for @params. Please use params and # never @params directly. def params @params end # accessor for @cookies. Please use cookies and # never @cookies directly. def cookies @cookies end # accessor for @headers. Please use headers and # never @headers directly. def headers @headers end # accessor for @session. Please use session and # never @session directly. def session @session end # accessor for @response. Please use response and # never @response directly. def response @response end # lookup the trait[:template_extensions] for the extname of the filename # you pass. Answers with the engine that matches the extension, Template::Erubis # is used if none matches. def engine_for(file) extension = File.extname(file)[1..-1] _template_extensions[extension] rescue ::Merb::Template::Erubis end # This method is called by templating-engines to register themselves with # a list of extensions that will be looked up on #render of an action. def self.register_engine(engine, *extensions) [extensions].flatten.uniq.each do |ext| _template_extensions[ext] = engine end end # shared_accessor sets up a class instance variable that can # be unique for each class but also inherits the shared attrs # from its superclasses. Since @@class variables are almost # global vars within an inheritance tree, we use # @class_instance_variables instead class_inheritable_accessor :before_filters class_inheritable_accessor :after_filters # calls a filter chain according to rules. def call_filters(filter_set) (filter_set || []).each do |(filter, rule)| ok = false if rule.has_key?(:only) if rule[:only].include?(params[:action].intern) ok = true end elsif rule.has_key?(:exclude) if !rule[:exclude].include?(params[:action].intern) ok = true end else ok = true end case filter when Symbol, String send(filter) if ok when Proc filter.call(self) if ok end end return :filter_chain_completed end # #before is a class method that allows you to specify before # filters in your controllers. Filters can either be a symbol # or string that corresponds to a method name to call, or a # proc object. if it is a method name that method will be # called and if it is a proc it will be called with an argument # of self where self is the current controller object. When # you use a proc as a filter it needs to take one parameter. # # examples: # before :some_filter # before :authenticate, :exclude => [:login, :signup] # before Proc.new {|c| c.some_method }, :only => :foo # # You can use either :only => :actionname or :exclude => [:this, :that] # but not both at once. :only will only run before the listed actions # and :exclude will run for every action that is not listed. # # Merb's before filter chain is very flixible. To halt the # filter chain you use throw :halt . If throw is called with # only one argument of :halt the return of the method filters_halted # will be what is rendered to the view. You can overide filters_halted # in your own controllers to control what it outputs. But the throw # construct is much more powerful then just that. throw :halt can # also take a second argument. Here is what that second arg can be # and the behavior each type can have: # # when the second arg is a string then that string will be what # is rendered to the browser. Since merb's render method returns # a string you can render a template or just use a plain string: # # String: # throw :halt, "You don't have permissions to do that!" # throw :halt, render(:action => :access_denied) # # if the second arg is a symbol then the method named after that # symbol will be called # Symbol: # throw :halt, :must_click_disclaimer # # If the second arg is a Proc, it will be called and its return # value will be what is rendered to the browser: # Proc: # throw :halt, Proc.new {|c| c.access_denied } # throw :halt, Proc.new {|c| Tidy.new(c.index) } # def self.before(filter, opts={}) raise(ArgumentError, "You can specify either :only or :exclude but not both at the same time for the same filter." ) if opts.has_key?(:only) && opts.has_key?(:exclude) opts = shuffle_filters!(opts) case filter when Symbol, String, Proc (self.before_filters ||= []) << [filter, opts] else raise(ArgumentError, 'filters need to be either a Symbol, String or a Proc' ) end end # #after is a class method that allows you to specify after # filters in your controllers. Filters can either be a symbol # or string that corresponds to a method name or a proc object. # if it is a method name that method will be called and if it # is a proc it will be called with an argument of self. When # you use a proc as a filter it needs to take one parameter. # you can gain access to the response body like so: # after Proc.new {|c| Tidy.new(c.body) }, :only => :index # def self.after(filter, opts={}) raise(ArgumentError, "You can specify either :only or :exclude but not both at the same time for the same filter." ) if opts.has_key?(:only) && opts.has_key?(:exclude) opts = shuffle_filters!(opts) case filter when Symbol, Proc, String (self.after_filters ||= []) << [filter, opts] else raise(ArgumentError, 'After filters need to be either a Symbol, String or a Proc' ) end end def self.shuffle_filters!(opts={}) if opts[:only] && opts[:only].is_a?(Symbol) opts[:only] = [opts[:only]] end if opts[:exclude] && opts[:exclude].is_a?(Symbol) opts[:exclude] = [opts[:exclude]] end return opts end end end