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