Skip to content

Instantly share code, notes, and snippets.

@eltiare
Created October 27, 2008 17:29
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/20141 to your computer and use it in GitHub Desktop.
Save eltiare/20141 to your computer and use it in GitHub Desktop.
#I'm trying to make a dynamic router in Merb based on information in the
#database. Basically, I want the user to able to build their own URL
#"tree" with the home page being at the root. If what the user wants is
#a single page in a location, getting this information is easy: all one
#has to do is store the path in the database and retrieve it. However,
#if one wants to be adding different types of functionality (such as a
#blog) to a certain part of the tree, it gets a lot trickier because you
#have the base path of the blog and then all the other paths that make
#the blog function such as:
#
#Base path: /blog (stored in db)
#
#blog/posts blog/posts/new blog/posts/5 blog/posts/5/comments
#blog/posts/5/comments/new
#
#You can easily see how nasty it can get to have to define all these
#routes by hand for each module or slice. For a shopping cart, you could
#easily have tens to over 100 routes to define depending on how complex
#you want to make things. I'd prefer to use Merb's router instead of
#defining routes' regexp's individually as it is powerful and
#significantly easier to use. Here are the steps that I've come up with
#so far to help me reach this end:
#
#1. Create a database that will hold the path information for single
#pages and for the base of the modules (such as '/blog' from before).
#
#2. Populate database with paths such as '/' and '/blog' - with
#corresponding such as 'Routers::Blog' or 'Routers::PhotoGallary' in the
#class_name (see code link above).
#
#3. Check the database to see if the path exists for a _single_ page or
#the base of a module/slice. In this case, all the router has to worry
#about is returning the controller and action and other information
#associated with this page. This part is easy.
#
#4. If no single page is found, go through all the rows in the database,
#looking for a match of the beginning of the path. For instance, the
#request path of /blog/posts/3 would match the record in the database
#/blog: request.path.match(%r"^#{row.path}"
#
#5. From there, the the matched portion of the path would be removed from
#the path, and the remaining string passed to the appropriate model for
#further processing, so that it could figure out which controller it
#needs to send the information to as well as other informational
#processing tasks depending on the module.
#
#6: Routes are defined in each model that handles a different
#module/slice. This is why I want to have different routes that are
#separate from the main application's routes (ie, a subclassed route).
#The routes would be matched from the modified path from the previoius
#step.
Merb::Router.prepare do
match(/\/.*/).defer_to { |request, params| Router::Base.get_hash(request, params) }
end
module Router
class Base
class FakeRequest
def initialize(request); @request = request; end
def path=(path); @path = path; end
def path; @path || @request.path; end
def method_missing(method, *args); @request.send(method, *args); end
end
include DataMapper::Resource
property :id, Serial
property :class_name, Discriminator, :index => true
property :path, String, :unique_index => true, :length => 255
property :subdomain, String, :index => true, :length => 150
property :child_id, String, :length => 255
class << self
def default_storage_name; 'route'; end
def default_hash; {:controller => 'pages', :action => 'show'}; end
def router; @router ||= Class.new Eltiare::Router; end
def get_hash(request, params)
# First, check to see if there is something that matches the path exactly
page = first(:path => request.path)
if page && page.class_name == self.name
params[:page] = request.path
return params.merge(default_hash)
elsif page
params[:id] = page.id
return params.merge(page.class.default_hash)
else # Get the route of child object
all(:class_name.not => self.name).each do |router|
path_regexp = %r"^#{router.path}"
if request.path.match(path_regexp) && request.subdomains.join('.') == router.subdomain.to_s
req = FakeRequest.new(request)
req.path = req.path.sub(path_regexp, '')
params[:id] = router.child_id # This will get overwritten later if specified
return router.get_hash(req, params)
end
end
end
end # get_hash
end # class << self
def get_hash(request, params)
@route, @route_params = self.class.router.route_for(request)
params.merge! @route_params if @route_params.is_a?(Hash)
params
end
end # Base
end # Router
module Router
class Blog < Base
# if the blog module is installed at '/blog' and at '/eltiare/blog',
# then the router below will match '/blog/things' _and_ '/eltiare/blog/things'
router.prepare do |r|
r.match('/things').to(:controller => 'things', :action => 'index')
end
class << self
def default_hash; {:controller => 'stuff', :action => 'show'}; end
end
end
end # Router
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment