lib/merb/merb_router.rb in merb-0.3.1 vs lib/merb/merb_router.rb in merb-0.3.3

- old
+ new

@@ -1,187 +1,275 @@ 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 + class Router - attr_accessor :sections - @@section_regexp = /(?::([a-z*_]+))/.freeze + 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 + class << self + + def prepare(&block) + @@matcher = RouteMatcher.new + @@generator = RouteGenerator.new + + yield self + + @@matcher.compile_router + end + + def add(*route) + @@matcher.add(*route) + end + + def matcher + @@matcher + end + + def generator + @@generator + end + + def match(path) + @@matcher.route_request(path) + end + + def generate(method, options = {}) + @@generator.generate(method, options) + end + + def 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 + + def 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 default_routes + @@matcher.default_routes + end + + def compiled_statement + @@matcher.compiled_statement + end + + def compiled_regexen + @@matcher.compiled_regexen + end + + def generate_resources_routes(res, opts) + [@@matcher,@@generator].each { |r| r.generate_resources_routes(res, opts) } + end + + def generate_singleton_routes(res, opts) + [@@matcher,@@generator].each { |r| r.generate_singleton_routes(res, opts) } + end - # the final compiled lambda that gets used - # as the body of the route_request method. - def self.compiled_statement - @@compiled_statement - end + end # class << self - def self.compiled_regexen - @@compiled_regexen + class RouteMatcher + + def initialize + @routes = Array.new + @compiled_statement = String.new + @compiled_regexen = Array.new + end + + # the final compiled lambda that gets used + # as the body of the route_request method. + def compiled_statement + @compiled_statement + end + + def compiled_regexen + @compiled_regexen + end + + # add a route to be compiled + def 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 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 compile(route) + raise ArgumentError unless String === route[0] + code, count = '', 0 + while route[0] =~ Router::SECTION_REGEXP + route[0] = route[0].dup + name = $1 + (name =~ /(\*+)(\w+)/) ? (flag = true; name = $2) : (flag = false) + count += 1 + if flag + route[0].sub!(Router::SECTION_REGEXP, "([^,?]+)") + else + route[0].sub!(Router::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 + + def 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 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 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 - - 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, "([^\/,?]+)") + + class RouteGenerator + + attr_accessor :paths + + def initialize + @paths = {} + end + + def add(name, path) + name = name.intern unless Symbol === name + @paths[name] = path + end + + def generate(name, options = {}) + path = @paths[name] + while path =~ Router::SECTION_REGEXP + path.sub!(Router::SECTION_REGEXP, options[$~[1].intern].to_s) end - code << " sections[:#{name}] = $#{count}\n" + if f = options[:format] + "#{path}.#{f}" + else + path + end 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 + + def generate_singleton_routes(res,opt) + add "edit_#{res}", "#{opt[:prefix]}/#{res}/edit" + add "new_#{res}","#{opt[:prefix]}/#{res}/new" + add res, "#{opt[:prefix]}/#{res}" + end + + def generate_resources_routes(res,opt) + res_singular = res.to_s.singularize + add res, "#{opt[:prefix]}/#{res}" + add res_singular, "#{opt[:prefix]}/#{res}/:id" + add "new_#{res_singular}", "#{opt[:prefix]}/#{res}/new" + add "custom_new_#{res_singular}", "#{opt[:prefix]}/#{res}/new/:action" + add "edit_#{res_singular}", "#{opt[:prefix]}/#{res}/:id/edit" + if mem = opt[:member] + mem.keys.sort_by{|x| "#{x}"}.each {|action| + add "#{action}_#{res_singular}", "#{opt[:prefix]}/#{res}/:id/#{action}" + } + end + if coll = opt[:collection] + coll.keys.sort_by{|x| "#{x}"}.each {|action| + add "#{action}_#{res_singular}", "#{opt[:prefix]}/#{res}/#{action}" + } + end + end 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) } + @procs << proc do + [::Merb::Router.matcher, ::Merb::Router.generator].each {|r| r.generate_resources_routes(@resource, @opts) } + end 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) } + @procs << proc do + [::Merb::Router.matcher, ::Merb::Router.generator].each {|r| r.generate_resources_routes(res, opts) } + end 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) } + @procs << proc do + [::Merb::Router.matcher, ::Merb::Router.generator].each { |r| r.generate_singleton_routes(res, opts) } + end 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