module Merb # Merb::RouteMatcher is the request routing mapper for the merb framework. # You can define placeholder parts of the url with the :symbol notation. # so r.add '/foo/:bar/baz/:id', :class => 'Bar', :method => 'foo' # will match against a request to /foo/123/baz/456. It will then # use the class Bar as your merb controller and call the foo method on it. # the foo method will recieve a hash with {:bar => '123', :id => '456'} # as the content. So the :placeholders sections of your routes become # a hash of arguments to your controller methods. This route maps # the default /controller/action and /controller/action/id urls: # r.add '/:class/:method/:id' class RouteMatcher attr_accessor :sections @@section_regexp = /(?::([a-z*_]+))/.freeze # setup the router and yield it out to # add routes to it. Then compile all routes def self.prepare @@routes = Array.new @@compiled_statement = String.new @@compiled_regexen = Array.new yield self compile_router end # the final compiled lambda that gets used # as the body of the route_request method. def self.compiled_statement @@compiled_statement end def self.compiled_regexen @@compiled_regexen end def compiled_statement @@compiled_statement end # add a route to be compiled def self.add(*route) @@routes << [route[0], (route[1] || {})] end # build up a string that defines a lambda # that does a case statement on the PATH_INFO # against each of the compiled routes in turn. # first route that matches wins. def self.compile_router router_lambda = @@routes.inject("lambda{|path| \n sections={}\n case path\n") { |m,r| m << compile(r) } <<" else\n return {:controller=>'Noroutefound', :action=>'noroute'}\n end\n}" @@compiled_statement = router_lambda meta_def(:route_request, &eval(router_lambda)) end # compile each individual route into a when /.../ # component of the case statement. Takes /:sections # of the route def that start with : and turns them # into placeholders for whatever urls match against # the route in question. def self.compile(route) raise ArgumentError unless String === route[0] code, count = '', 0 while route[0] =~ @@section_regexp route[0] = route[0].dup name = $1 (name =~ /(\*+)(\w+)/) ? (flag = true; name = $2) : (flag = false) count += 1 if flag route[0].sub!(@@section_regexp, "([^,?]+)") else route[0].sub!(@@section_regexp, "([^\/,?]+)") end code << " sections[:#{name}] = $#{count}\n" end @@compiled_regexen << Regexp.new(route[0]) index = @@compiled_regexen.size - 1 condition = " when @@compiled_regexen[#{index}] " statement = "#{condition}\n#{code}" statement << " return #{route[1].inspect}.update(sections)\n" statement end class Resource # TODO : come up with naming convention and generate helper # methods for route generation. Route generation # framework needed but needs to be simple and light def initialize(resource, procs=[], opts={}) @resource, @procs, @opts = resource, procs, opts @procs << proc { ::Merb::RouteMatcher.generate_resources_routes(@resource, @opts) } end def resources(res, opts={}) (opts[:prefix]||='') << "/#{@resource}/:#{@resource.to_s.singularize}_id" opts[:prefix] = @opts[:prefix] + opts[:prefix] if block_given? yield self.class.new(res, @procs, opts) else @procs << proc { ::Merb::RouteMatcher.generate_resources_routes(res, opts) } end end def resource(res, opts={}) (opts[:prefix]||='') << "/#{@resource}/:#{@resource.to_s.singularize}_id" opts[:prefix] = @opts[:prefix] + opts[:prefix] if block_given? yield self.class.new(res, @procs, opts) else @procs << proc { ::Merb::RouteMatcher.generate_singleton_routes(res, opts) } end end end # add a resource to be compiled for rest style dispatch def self.resources(res, opts={}) opts[:prefix] ||= "" if block_given? procs = [] yield Resource.new(res, procs, opts) procs.reverse.each &:call else generate_resources_routes(res,opts) end end # add a resource to be compiled for rest style dispatch def self.resource(res, opts={}) opts[:prefix] ||= "" if block_given? procs = [] yield Resource.new(res, procs, opts) procs.reverse.each &:call else generate_singleton_routes(res,opts) end end def self.generate_resources_routes(res,opt) with_options :controller => res.to_s, :rest => true do |r| r.add "#{opt[:prefix]}/#{res}/:id[;/]+edit", :allowed => {:get => 'edit'} r.add "#{opt[:prefix]}/#{res}/new[;/]+:action", :allowed => {:get => 'new', :post => 'new', :put => 'new', :delete => 'new'} r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'} if mem = opt[:member] mem.keys.sort_by{|x| "#{x}"}.each {|action| allowed = mem[action].injecting({}) {|h, verb| h[verb] = "#{action}"} r.add "#{opt[:prefix]}/#{res}/:id[;/]+#{action}", :allowed => allowed } end if coll = opt[:collection] coll.keys.sort_by{|x| "#{x}"}.each {|action| allowed = coll[action].injecting({}) {|h, verb| h[verb] = "#{action}"} r.add "#{opt[:prefix]}/#{res}[;/]+#{action}", :allowed => allowed } end r.add "#{opt[:prefix]}/#{res}/:id\\.:format", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'} r.add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'index', :post => 'create'} r.add "#{opt[:prefix]}/#{res}/:id", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'} r.add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'index', :post => 'create'} end end def self.generate_singleton_routes(res,opt) with_options :controller => res.to_s, :rest => true do |r| r.add "#{opt[:prefix]}/#{res}[;/]+edit", :allowed => {:get => 'edit'} r.add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'show'} r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'} r.add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'show', :post => 'create', :put => 'update', :delete => 'destroy'} end end def self.default_routes add "/:controller/:action/:id\\.:format" add "/:controller/:action/:id" add "/:controller/:action\\.:format" add "/:controller/:action" add "/:controller\\.:format", :action => 'index' add "/:controller", :action => 'index' end end end