This is posted on my blog: http://inquirylabs.com/blog/?p=25 == Suppose your Ruby on Rails application has several different kinds of resources that are all identified by GUIDs. For example, several images, some blog entries, and a few user-generated pages. Now suppose that you want to access these resources from the URL without having to identify the resource type in the URL. Let’s say this is an image: http://localhost:3000/d1e89a06-7524-a97e-3f31-f60e59b3d888 And let’s say this is a blog entry: http://localhost:3000/81f5fb29-0797-7ab3-9890-5377fa416df4 Normally, using Rails’ routing system, you wouldn’t be able to distinguish between these two URLs: you would probably force the user to prepend the resource type, e.g.: http://localhost:3000/blog/81f5fb29-0797-7ab3-9890-5377fa416df4 Or, you would create a single controller to take the place of the routing system by rendering components once the resource type is determined. Luckily, the routing system is pretty flexible. You can, for example, use Regular Expressions to match things on the URL and route to different controllers depending on the result. Now, what if we subclass Regexp and create our own “regular expression”? The result is a simple class that performs one function: it distinguishes resource types for the routing system. class ResourceMatcher < Regexp def initialize(*args) super(''^$'') end def inspect \"#{self.class}.new\" end end class IsBlog < ResourceMatcher def =~(identifier) !Blog.find_by_guid(identifier).nil? end end class IsImage < ResourceMatcher def =~(identifier) !Image.find_by_guid(identifier).nil? end end Now, in our routes: ActionController::Routing::Routes.draw do |map| map.connect '':id'', :id => IsBlog.new, :controller => ''blog'', :action => ''show'' map.connect '':id'', :id => IsImage.new, :controller => ''image'', :action => ''show'' end The reason this works is because (IsBlog.new).is_a?(Regexp) returns true. If you go into the internals of the routing system, you''ll see that the ResourceMatcher class is written in just such a way that it tricks the routing system in to thinking it''s a regular old Regexp. Once the trickery is in place, we can use the =~ matching method to return true or false however we please. Duane Johnson (canadaduane) _______________________________________________ Rails mailing list Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails
OMG! WTF!? LOL! You just solved the single biggest problem and annoyance I''ve had with Rails to date. Thank you for that, thank you so very much. With this, I''ll be able to implement type agnostic a.k.a. "cruft-free" URLs in our CMS. An URL such as domain.com/section/pagename could be anything; an article, a news page, a forum, or whatever. Up until now I''ve had to resort to horrible hacks to handle them which wouldn''t scale at all and was really messy to maintain. This solves everything. (Btw, this should be in one or several wiki pages, on the topic of type agnostic, cruft-free URLs.) Happy happy joy joy. Regards, and with sincere gratitude, Tomas Jogin On 12/7/05, Duane Johnson <duane.johnson-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> This is posted on my blog: > http://inquirylabs.com/blog/?p=25 > > ==> > Suppose your Ruby on Rails application has several different kinds of > resources that are all identified by GUIDs. For example, several images, > some blog entries, and a few user-generated pages. Now suppose that you want > to access these resources from the URL without having to identify the > resource type in the URL. > > > > Let''s say this is an image: > http://localhost:3000/d1e89a06-7524-a97e-3f31-f60e59b3d888 > > > > And let''s say this is a blog entry: > http://localhost:3000/81f5fb29-0797-7ab3-9890-5377fa416df4 > > > > Normally, using Rails'' routing system, you wouldn''t be able to distinguish > between these two URLs: you would probably force the user to prepend the > resource type, e.g.: > http://localhost:3000/blog/81f5fb29-0797-7ab3-9890-5377fa416df4 > > > > Or, you would create a single controller to take the place of the routing > system by rendering components once the resource type is determined. > > > > Luckily, the routing system is pretty flexible. You can, for example, use > Regular Expressions to match things on the URL and route to different > controllers depending on the result. Now, what if we subclass Regexp and > create our own "regular expression"? The result is a simple class that > performs one function: it distinguishes resource types for the routing > system. > class ResourceMatcher < Regexp > def initialize(*args) > super(''^$'') > end > > def inspect > \"#{self.class}.new\" > end > end > > class IsBlog < ResourceMatcher > def =~(identifier) > !Blog.find_by_guid(identifier).nil? > end > end > > class IsImage < ResourceMatcher > def =~(identifier) > !Image.find_by_guid(identifier).nil? > end > end > > > > Now, in our routes: > ActionController::Routing::Routes.draw do |map| > map.connect '':id'', > :id => IsBlog.new, > :controller => ''blog'', > :action => ''show'' > > map.connect '':id'', > :id => IsImage.new, > :controller => ''image'', > :action => ''show'' > end > > The reason this works is because (IsBlog.new).is_a?(Regexp) returns true. > If you go into the internals of the routing system, you''ll see that the > ResourceMatcher class is written in just such a way that it tricks the > routing system in to thinking it''s a regular old Regexp. Once the trickery > is in place, we can use the =~ matching method to return true or false > however we please. > > Duane Johnson > (canadaduane) > > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails > > >
> With this, I''ll be able to implement type agnostic a.k.a. "cruft-free" > URLs in our CMS. An URL such as domain.com/section/pagename could be > anything; an article, a news page, a forum, or whatever. Up until now > I''ve had to resort to horrible hacks to handle them which wouldn''t > scale at all and was really messy to maintain. This solves everything. > (Btw, this should be in one or several wiki pages, on the topic of > type agnostic, cruft-free URLs.)Can you set the controller/action with this tip? If not, you''d be no better off doing this vs: map.connect ''*path'', :controller => ''dispatcher'', :action => ''dispatch'' If you can''t change the controller, you''re going to the same spot anyways. Or, you''ll end up having to add routing statements for every content type of every section. With the catch-all statement, you''ll have a single spot to find the section from the path and figure out what content type to render. -- rick http://techno-weenie.net
Very nice, Duane! This raises the question, for me: why is the routing code looking for Regexp''s explicitly? Perhaps we could make it more duck-type friendly, so you don''t have to subclass Regexp to make this work. - Jamis On Dec 6, 2005, at 6:25 PM, Duane Johnson wrote:> This is posted on my blog: http://inquirylabs.com/blog/?p=25 > > ==> > Suppose your Ruby on Rails application has several different kinds > of resources that are all identified by GUIDs. For example, several > images, some blog entries, and a few user-generated pages. Now > suppose that you want to access these resources from the URL > without having to identify the resource type in the URL. > > Let’s say this is an image: > > http://localhost:3000/d1e89a06-7524-a97e-3f31-f60e59b3d888 > > And let’s say this is a blog entry: > > http://localhost:3000/81f5fb29-0797-7ab3-9890-5377fa416df4 > > Normally, using Rails’ routing system, you wouldn’t be able to > distinguish between these two URLs: you would probably force the > user to prepend the resource type, e.g.: > > http://localhost:3000/blog/81f5fb29-0797-7ab3-9890-5377fa416df4 > > Or, you would create a single controller to take the place of the > routing system by rendering components once the resource type is > determined. > > > Luckily, the routing system is pretty flexible. You can, for > example, use Regular Expressions to match things on the URL and > route to different controllers depending on the result. Now, what > if we subclass Regexp and create our own “regular expression”? The > result is a simple class that performs one function: it > distinguishes resource types for the routing system. > > class ResourceMatcher < Regexp > def initialize(*args) > super(''^$'') > end > > def inspect > \"#{self.class}.new\" > end > end > > class IsBlog < ResourceMatcher > def =~(identifier) > !Blog.find_by_guid(identifier).nil? > end > end > > class IsImage < ResourceMatcher > def =~(identifier) > !Image.find_by_guid(identifier).nil? > end > end > > Now, in our routes: > > ActionController::Routing::Routes.draw do |map| > map.connect '':id'', > :id => IsBlog.new, > :controller => ''blog'', > :action => ''show'' > > map.connect '':id'', > :id => IsImage.new, > :controller => ''image'', > :action => ''show'' > end > > The reason this works is because (IsBlog.new).is_a?(Regexp) returns > true. If you go into the internals of the routing system, you''ll > see that the ResourceMatcher class is written in just such a way > that it tricks the routing system in to thinking it''s a regular old > Regexp. Once the trickery is in place, we can use the =~ matching > method to return true or false however we please. > > Duane Johnson > (canadaduane) > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails
On Dec 7, 2005, at 6:50 AM, Rick Olson wrote:>> With this, I''ll be able to implement type agnostic a.k.a. "cruft- >> free" >> URLs in our CMS. An URL such as domain.com/section/pagename could be >> anything; an article, a news page, a forum, or whatever. Up until now >> I''ve had to resort to horrible hacks to handle them which wouldn''t >> scale at all and was really messy to maintain. This solves >> everything. >> (Btw, this should be in one or several wiki pages, on the topic of >> type agnostic, cruft-free URLs.) > > Can you set the controller/action with this tip? If not, you''d be no > better off doing this vs: > > map.connect ''*path'', :controller => ''dispatcher'', :action => > ''dispatch'' > > If you can''t change the controller, you''re going to the same spot > anyways. Or, you''ll end up having to add routing statements for every > content type of every section. With the catch-all statement, you''ll > have a single spot to find the section from the path and figure out > what content type to render. >Yes, you can change the controller. That''s the main advantage. Duane Johnson (canadaduane)
On Dec 7, 2005, at 7:55 AM, Jamis Buck wrote:> Very nice, Duane! This raises the question, for me: why is the > routing code looking for Regexp''s explicitly? Perhaps we could make > it more duck-type friendly, so you don''t have to subclass Regexp to > make this work. >That would be super. There are actually a couple of ways we could do this. The best way (but a little beyond my capability at the moment) would be to allow for Procs to determine if a match occurs. I tried this first (using what I thought would be the intuitive way... hehe) and it failed miserably. It seems there is something about Procs that the GenerationGenerator can''t handle (perhaps its trying to serialize them?) In any case, either approach would work for me. Duane Johnson (canadaduane)
* Duane Johnson (duane.johnson-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org) [051207 11:20]:> >Very nice, Duane! This raises the question, for me: why is the > >routing code looking for Regexp''s explicitly? Perhaps we could make > >it more duck-type friendly, so you don''t have to subclass Regexp to > >make this work. > > > That would be super. There are actually a couple of ways we could do > this. The best way (but a little beyond my capability at the moment) > would be to allow for Procs to determine if a match occurs. I tried > this first (using what I thought would be the intuitive way... hehe) > and it failed miserably. It seems there is something about Procs > that the GenerationGenerator can''t handle (perhaps its trying to > serialize them?) In any case, either approach would work for me.Not having looked at that particular section of the Rails guts at all, I''m idly wondering why === wasn''t used instead of checking for a Regexp? Rick -- http://www.rickbradley.com MUPRN: 227 | got it running on random email haiku | a Tivo? Not yet, but | I''m working on it...
On Dec 7, 2005, at 10:40 AM, Rick Bradley wrote:> * Duane Johnson (duane.johnson-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org) [051207 11:20]: >>> Very nice, Duane! This raises the question, for me: why is the >>> routing code looking for Regexp''s explicitly? Perhaps we could make >>> it more duck-type friendly, so you don''t have to subclass Regexp to >>> make this work. >>> >> That would be super. There are actually a couple of ways we could do >> this. The best way (but a little beyond my capability at the moment) >> would be to allow for Procs to determine if a match occurs. I tried >> this first (using what I thought would be the intuitive way... hehe) >> and it failed miserably. It seems there is something about Procs >> that the GenerationGenerator can''t handle (perhaps its trying to >> serialize them?) In any case, either approach would work for me. > > Not having looked at that particular section of the Rails guts at all, > I''m idly wondering why === wasn''t used instead of checking for a > Regexp? >Here''s the code in question (from routing.rb): def test_condition(expression, condition) case condition when String then "(#{expression} == #{condition.inspect})" when Regexp then condition = Regexp.new("^#{condition.source}$") unless /^ \^.*\$$/ =~ condition.source "(#{condition.inspect} =~ #{expression})" when Array then conds = condition.collect do |condition| cond = test_condition(expression, condition) (cond[0, 1] == ''('' && cond[-1, 1] == '')'') ? cond : "(# {cond})" end "(#{conds.join('' || '')})" when true then expression when nil then "! #{expression}" else raise ArgumentError, "Valid criteria are strings, regular expressions, true, or nil" end end The reason I chose to subclass Regexp instead of String is because for Regexp, the =~ operator is a method of the condition--for the string, the == is a method of the expression. This code looks fairly clean, and may be a little bit difficult to duck type without adding too much... but maybe Jamis has some ideas :) Duane
I''ve not looked at the routing code in depth so I could be talking out of my (...) But the first thing that springs to mind (duck-wise) is the === comparison operator. Regexp instances already implement it and the documentation for Duane''s technique would be pretty simple: "implement def===(arg)" Regards, Trevor On 7-Dec-05, at 8:14 AM, Duane Johnson wrote:> > On Dec 7, 2005, at 7:55 AM, Jamis Buck wrote: > >> Very nice, Duane! This raises the question, for me: why is the >> routing code looking for Regexp''s explicitly? Perhaps we could >> make it more duck-type friendly, so you don''t have to subclass >> Regexp to make this work. >> > That would be super. There are actually a couple of ways we could > do this. The best way (but a little beyond my capability at the > moment) would be to allow for Procs to determine if a match > occurs. I tried this first (using what I thought would be the > intuitive way... hehe) and it failed miserably. It seems there is > something about Procs that the GenerationGenerator can''t handle > (perhaps its trying to serialize them?) In any case, either > approach would work for me. > > Duane Johnson > (canadaduane) > > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/railsTrevor Squires http://somethinglearned.com
Good Grief Rick, either I''m in your head or your in my head but... get out of my head! :-P Trevor On 7-Dec-05, at 9:40 AM, Rick Bradley wrote:> * Duane Johnson (duane.johnson-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org) [051207 11:20]: >>> Very nice, Duane! This raises the question, for me: why is the >>> routing code looking for Regexp''s explicitly? Perhaps we could make >>> it more duck-type friendly, so you don''t have to subclass Regexp to >>> make this work. >>> >> That would be super. There are actually a couple of ways we could do >> this. The best way (but a little beyond my capability at the moment) >> would be to allow for Procs to determine if a match occurs. I tried >> this first (using what I thought would be the intuitive way... hehe) >> and it failed miserably. It seems there is something about Procs >> that the GenerationGenerator can''t handle (perhaps its trying to >> serialize them?) In any case, either approach would work for me. > > Not having looked at that particular section of the Rails guts at all, > I''m idly wondering why === wasn''t used instead of checking for a > Regexp? > > Rick > -- > http://www.rickbradley.com MUPRN: 227 > | got it running on > random email haiku | a Tivo? Not yet, but > | I''m working on it... > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/railsTrevor Squires http://somethinglearned.com