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 shared_accessor :layout, :session_id_key, :cache_templates include Merb::ControllerMixin include Merb::RenderMixin include Merb::ResponderMixin attr_accessor :status, :body MULTIPART_REGEXP = /\Amultipart\/form-data.*boundary=\"?([^\";,]+)/n.freeze CONTENT_DISPOSITION_REGEXP = /^Content-Disposition: form-data;/.freeze FIELD_ATTRIBUTE_REGEXP = /(?:\s(\w+)="([^"]+)")/.freeze CONTENT_TYPE_REGEXP = /^Content-Type: (.+?)(\r$|\Z)/m.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(req, env, args, method=(env['REQUEST_METHOD']||'GET')) env = ::MerbHash[env.to_hash] @status, @method, @env, @headers, @root = 200, method.downcase.to_sym, env, {'Content-Type' =>'text/html'}, env['SCRIPT_NAME'].sub(/\/$/,'') cookies = query_parse(env['HTTP_COOKIE'], ';,') querystring = query_parse(env['QUERY_STRING']) self.layout ||= :application self.session_id_key ||= :_session_id @in = req if MULTIPART_REGEXP =~ env['CONTENT_TYPE'] boundary_regexp = /(?:\r?\n|\A)#{Regexp::quote("--#$1")}(?:--)?\r$/ until @in.eof? attrs=MerbHash[] for line in @in case line when "\r\n" : break when CONTENT_DISPOSITION_REGEXP attrs.update ::MerbHash[*$'.scan(FIELD_ATTRIBUTE_REGEXP).flatten] when CONTENT_TYPE_REGEXP attrs[:type] = $1 end end name=attrs[:name] io_buffer=if attrs[:filename] io_buffer=attrs[:tempfile]=Tempfile.new(:Merb) io_buffer.binmode else attrs="" end while chunk=@in.read(16384) if chunk =~ boundary_regexp io_buffer << $`.chomp @in.seek(-$'.size, IO::SEEK_CUR) break end io_buffer << chunk end if name =~ /(.*)?\[\]/ (querystring[$1] ||= []) << attrs else querystring[name]=attrs if name end attrs[:tempfile].rewind if attrs.is_a?MerbHash end elsif @method == :post if ['application/json', 'text/x-json'].include?(env['CONTENT_TYPE']) MERB_LOGGER.info("JSON Request") json = JSON.parse(@in.read || "") || {} json = ::MerbHash.new(json) if json.is_a? Hash querystring.merge!(json) else querystring.merge!(query_parse(@in.read)) end end @cookies, @params = cookies.dup, querystring.dup.merge(args) @cookies.merge!(:_session_id => @params[:_session_id]) if @params.has_key?(:_session_id) @method = @params.delete(:_method).downcase.to_sym if @params.has_key?(:_method) @request = Request.new(@env, @method) MERB_LOGGER.info("Params: #{params.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 #{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 # shared_accessor sets up a class instance variable that can # be unique for each class but also inherits the meta attrs # from its superclasses. Since @@class variables are almost # global vars within an inheritance tree, we use # @class_instance_variables instead shared_accessor :before_filters shared_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) raise(ArgumentError, 'after filters can only be a Proc object' ) unless Proc === filter opts = shuffle_filters!(opts) (self.after_filters ||= []) << [filter, opts] 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