Created
October 27, 2008 17:29
-
-
Save eltiare/20141 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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