#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