Skip to content

Instantly share code, notes, and snippets.

@eltiare
Created January 12, 2010 03:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eltiare/274853 to your computer and use it in GitHub Desktop.
Save eltiare/274853 to your computer and use it in GitHub Desktop.
class MiniRouter
class << self
def routes; @routes ||= [] end
def named_routes; @named_routes ||= {} end
def context; @context ||= Context.new(self) end
def add_routes(&blk)
raise 'add_routes requires a block' unless block_given?
context.instance_eval(&blk)
end
def match(request)
routes.each { |route|
if (matches = route.reg_exp.match(request.path)) && request.method == route.method
params = route.defaults
route.matchers.each_with_index { |m,i| params[m] = matches[i+1].sub(%r'^\/', '') unless matches[i+1].nil? }
unless params[:controller].blank? || params[:controller_prefix].blank?
params[:controller] = "#{params[:controller_prefix]}#{params[:controller]}"
params.delete(:controller_prefix)
end
return route, params
end
}
nil
end
def url(name, obj_or_hash=nil)
obj_or_hash.symbolize_keys! if obj_or_hash.is_a?(Hash)
if named_routes[name] && named_routes[name].parts
m = /^\:/
is_hash = obj_or_hash.is_a?(Hash)
named_routes[name].parts.map { |p| p.match(m) ? ( is_hash ? obj_or_hash[p.sub(m, '').to_sym] : obj_or_hash.send(p.sub(m, '').to_sym) ) : p }.join('/')
else
raise 'Route not found!'
end#if
end#def
end#class
Attrs = [:reg_exp, :name, :matchers, :defaults, :method]
module Common
def to(opts, &blk)
raise "the method 'to' only takes a hash" unless opts.is_a?(Hash)
@defaults.merge!(opts)
instance_eval(&blk) if block_given?
self
end
end
class Router
attr_accessor(*(Attrs + [:parts]))
include Common
def initialize(behavior)
@behavior = behavior
Attrs.each { |k|
val = behavior.send(k)
send(:"#{k}=", case val; when NilClass, Symbol: val; else val.dup end )
}
end
def name(sym)
raise "name only takes a symbol" unless sym.is_a?(Symbol)
@name = sym
@behavior.router.named_routes[sym] ||= self
end
end
class Context
attr_accessor(*Attrs)
attr_reader :router
attr_reader :parents
include Common
def initialize(router_klass, c_or_r = nil, parent_push = nil)
raise 'parent_push takes only a one item hash' if parent_push && (!parent_push.is_a?(Hash) || parent_push.size != 1)
@router = router_klass
@matchers = []
@defaults = {}
@reg_exp = //
@method = :get
Attrs.each { |k| send(:"#{k}=", c_or_r.instance_variable_get(:"@#{k}")) } if c_or_r
@parents = c_or_r.is_a?(self.class) ? c_or_r.parents : OrderedList.new
if parent_push
raise 'You cannot add a resource below another with the same name' if @parents[parent_push.keys[0]]
@parents[parent_push.keys[0]] = parent_push.values[0]
end
end
def resources(name, opts = {}, &blk)
collection = opts.delete(:collection) || {}
member = opts.delete(:member) || {}
controller = opts.delete(:controller) || name
collection.merge!({
:index => :get,
:new => :get,
:create => {:index => :post}
})
member.merge!({
:show => :get,
:edit => :get,
:update => {:show => :put},
:delete => :get,
:destroy => {:show => :destroy}
})
path_prefix = ''
@parents.each { |k,v| path_prefix << "/#{v}/:#{k}_id" }
singular_name = name.to_s.singularize
collection.each { |k,v|
action = k
path_action, method = v.is_a?(Hash) ? [v.keys[0], v.values[0]] : [k, v]
path_action = path_action == :index ? '' : "/#{path_action}"
name_prefix = path_action.blank? ? '' : "#{action}_"
name_content = action == :new ? singular_name : name
map("#{path_prefix}/#{name}#{path_action}", :method => method).to(:controller => controller, :action => action).name(:"#{name_prefix}#{name_content}")
}
member.each { |k,v|
action = k
path_action, method = v.is_a?(Hash) ? [v.keys[0], v.values[0]] : [k, v]
path_action = path_action == :show ? '' : "/#{path_action}"
name_prefix = path_action.blank? ? '' : "#{action}_"
map("#{path_prefix}/#{name}/:id#{path_action}", :method => method).to(:controller => controller, :action => action).name(:"#{name_prefix}#{singular_name}")
}
# TODO: Eventually I want it to be smarter than just name (including the path_prefix for the router options, for instance)
self.class.new(@router, self, singular_name => name).instance_eval(&blk) if block_given?
end
def map(exp, opts = {}, &blk)
router = Router.new(self)
router.method = opts.delete(:method) || self.method
prepend_str = @reg_exp.inspect.gsub(/(^\/)|(\/$)/, '')
case exp
when String
if exp.match('/:')
parts = exp.split('/')
parts -= [""]
router.parts = parts.dup
parts.map! do |p|
if p.match(/^:/)
if p.match(/\?$/)
optional = true
p.sub!(/\?$/, '')
end#if
matcher = p.sub(/^\:/, '').to_sym
router.matchers << matcher unless router.matchers.include?(matcher)
optional ? '(/[^/]+)?' : '/([^/]+)'
else
"/#{p}"
end#if
end#do
append_str = "#{parts.join}"
else
router.parts = [exp]
append_str = exp
end#if
router.reg_exp = Regexp.new("^#{prepend_str.blank? ? append_str : prepend_str.sub(/\/$/, '') << append_str}$")
when Regexp
router.reg_exp = Regexp.new("^#{exp.inspect.gsub(/(^\/)|(\/$)/, '')}$")
else
raise 'map only takes a string or regexp'
end
if block_given?
c = self.class.new(@router, router)
c.instance_eval(&blk)
c
else
@router.routes << router
router
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment