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)
on 07.12.2005 02:27
on 07.12.2005 14:19
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 07.12.2005 14:52
> 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
on 07.12.2005 15:57
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 07.12.2005 17:07
On Dec 7, 2005, at 6:50 AM, Rick Olson wrote: > Can you set the controller/action with this tip? If not, you'd be no > Yes, you can change the controller. That's the main advantage. Duane Johnson (canadaduane)
on 07.12.2005 17:15
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)
on 07.12.2005 18:45
* Duane Johnson (duane.johnson@gmail.com) [051207 11:20]: > 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 07.12.2005 19:38
On Dec 7, 2005, at 10:40 AM, Rick Bradley wrote: >> 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
on 07.12.2005 19:46
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: > moment) would be to allow for Procs to determine if a match > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails Trevor Squires http://somethinglearned.com
on 07.12.2005 19:50
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: >> and it failed miserably. It seems there is something about Procs > | got it running on > random email haiku | a Tivo? Not yet, but > | I'm working on it... > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails Trevor Squires http://somethinglearned.com