Christian Metts
2005-Feb-26  23:58 UTC
Pushing Routes to their limits (path based URLs for CMS).
Like a few others are doing, I''m working on a site that has a CMS
component that uses paths (that may contain one or more ''/''s)
as
database keys and in URLs. This allows for some very pretty URLs.  I
want it to be able to use the controller/action/ url prefix normally
like:  /page/edit/path/to/page
But also be able to work directly off the root so:  /path/to/page 
Would show the same thing as:  /page/show/path/to/page.
It took some doing but I was able to get it to work just like this
under Routes. Here''s what I had to do.
Because there is no way to capture multiple parts of a path into one
parameter in Routes I had to tweak the mod_rewite rules a bit.
Instead of:
    RewriteRule ^(.*)$ /dispatch.fcgi?$1 [QSA,L]
I now use this:
    # because the page controller is the only one that needs path support I can 
    # restrict this to just urls that start with ''page'', this
puts the
path part I need
    # in a parameter and passes it off to Routes to deal with the rest.
    RewriteRule ^page/[-_a-zA-Z0-9]+/([-_a-zA-Z0-9/]+)$   
/dispatch.fcgi?path=$1 [QSA] [L]
    # This puts the entire path in a parameter and sends it along with
every request
    RewriteRule ^(.+)$    /dispatch.fcgi?path=$1 [QSA] [L]
So now that I have my path we move on to the routes.rb. After all my
other routes I have these:
  map.connect '':path'', :controller =>
''page'', :action => ''show''
  map.connect '':a/:b/:c/:d/:e/:f'', :controller =>
''page'', :action => ''show'',
  	      :a => nil, :b => nil, :c => nil, :d => nil, :e => nil,
:f => nil
  map.connect ''page/:action/:path'', :controller =>
''page''
  map.connect "page/:action/:a/:b/:c/:d/:e/:f", :controller =>
''page'',
  	      :a => nil, :b => nil, :c => nil, :d => nil, :e => nil,
:f => nil
 
The fist version  of each rule matches when doing URL generation. 
The second one matches incoming requests and lets them through. It
also loads up a bunch of unnecessary parameters  but that not really a
problem. It doesn''t assign a :path bit but that was taken care of at
the mod_rewrite step. It''s hacky and crufty and it''s not that
bad.
Which gets me 90% there. But because of the way Route.generate()
escapes URL components a url that should be /about/mission is written
as /about%2Fmission. On top of being ugly. It doesn''t work.
I ended up redefining ActionController::Routing::Route.generate() in
the bottom of my routes.rb to not apply CGI.escape to :path url
components. (By default it escapes all url components that aren''t
:controller). This is even more of a hack, and it''s a ~40 line method
so chances are good I''ll have to update this with any Rails update.
----
To anyone who needs to do this right now: This is a bit messy, but
it''s easier to work with than just using mod_rewrite as before.
Because url_for works properly now.
To Ulysses, DHH and any other Rails devs: Is there a way we can make
this not be so difficult?
 It''s a bit of a fringe case and not worth adding alot of complexity
or  options. So my vote would simply be for declaring :path a special
URL component . When recognizing it could eat up the entire rest of
the path. And when generating it wouldn''t escape the slashes.
-- Xian
Robert Williams
2005-Feb-27  00:28 UTC
Re: Pushing Routes to their limits (path based URLs for CMS).
I was messing around and found that you can do the following:
ActionController::Routing::Routes.draw do |map|
  map.connect '''', :controller => ''page'',
:action => ''read'', :a => ''home''
  map.connect '':controller/:action/:id'' #''
  begin 
    map.recognise!
  rescue # if all other mappings fail
    path = '':a/:b/:c/:d/:e/:f'' #''
    map.connect path, :controller => ''page'', :action =>
''read'', :a =>
''home'', :b => nil, :c => nil, :d => nil, :e =>
nil, :f => nil
  end
end
Any help?
On Sat, 26 Feb 2005 17:58:16 -0600, Christian Metts
<mintxian.list-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
wrote:> Like a few others are doing, I''m working on a site that has a CMS
> component that uses paths (that may contain one or more
''/''s) as
> database keys and in URLs. This allows for some very pretty URLs.  I
> want it to be able to use the controller/action/ url prefix normally
> like:  /page/edit/path/to/page
> 
> But also be able to work directly off the root so:  /path/to/page
> Would show the same thing as:  /page/show/path/to/page.
> 
> It took some doing but I was able to get it to work just like this
> under Routes. Here''s what I had to do.
> 
> Because there is no way to capture multiple parts of a path into one
> parameter in Routes I had to tweak the mod_rewite rules a bit.
> 
> Instead of:
> 
>     RewriteRule ^(.*)$ /dispatch.fcgi?$1 [QSA,L]
> 
> I now use this:
> 
>     # because the page controller is the only one that needs path support I
can
>     # restrict this to just urls that start with ''page'',
this puts the
> path part I need
>     # in a parameter and passes it off to Routes to deal with the rest.
>     RewriteRule ^page/[-_a-zA-Z0-9]+/([-_a-zA-Z0-9/]+)$
> /dispatch.fcgi?path=$1 [QSA] [L]
>     # This puts the entire path in a parameter and sends it along with
> every request
>     RewriteRule ^(.+)$    /dispatch.fcgi?path=$1 [QSA] [L]
> 
> So now that I have my path we move on to the routes.rb. After all my
> other routes I have these:
> 
>   map.connect '':path'', :controller =>
''page'', :action => ''show''
>   map.connect '':a/:b/:c/:d/:e/:f'', :controller =>
''page'', :action => ''show'',
>               :a => nil, :b => nil, :c => nil, :d => nil, :e
=> nil, :f => nil
> 
>   map.connect ''page/:action/:path'', :controller =>
''page''
>   map.connect "page/:action/:a/:b/:c/:d/:e/:f", :controller =>
''page'',
>               :a => nil, :b => nil, :c => nil, :d => nil, :e
=> nil, :f => nil
> 
> The fist version  of each rule matches when doing URL generation.
> 
> The second one matches incoming requests and lets them through. It
> also loads up a bunch of unnecessary parameters  but that not really a
> problem. It doesn''t assign a :path bit but that was taken care of
at
> the mod_rewrite step. It''s hacky and crufty and it''s not
that bad.
> 
> Which gets me 90% there. But because of the way Route.generate()
> escapes URL components a url that should be /about/mission is written
> as /about%2Fmission. On top of being ugly. It doesn''t work.
> 
> I ended up redefining ActionController::Routing::Route.generate() in
> the bottom of my routes.rb to not apply CGI.escape to :path url
> components. (By default it escapes all url components that aren''t
> :controller). This is even more of a hack, and it''s a ~40 line
method
> so chances are good I''ll have to update this with any Rails
update.
> 
> ----
> 
> To anyone who needs to do this right now: This is a bit messy, but
> it''s easier to work with than just using mod_rewrite as before.
> Because url_for works properly now.
> 
> To Ulysses, DHH and any other Rails devs: Is there a way we can make
> this not be so difficult?
> 
>  It''s a bit of a fringe case and not worth adding alot of
complexity
> or  options. So my vote would simply be for declaring :path a special
> URL component . When recognizing it could eat up the entire rest of
> the path. And when generating it wouldn''t escape the slashes.
> 
> -- Xian
> _______________________________________________
> Rails mailing list
> Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org
> http://lists.rubyonrails.org/mailman/listinfo/rails
>