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 2005-12-07 02:27
on 2005-12-07 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 2005-12-07 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 2005-12-07 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 2005-12-07 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 2005-12-07 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 2005-12-07 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 2005-12-07 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 2005-12-07 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 2005-12-07 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
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.