Rerouting Rails Part II
I wrote about my initial experiments with reimplementing Rails’s routing yesterday; this post continues the story.
I’ve started running the tests from Rails’s Action Pack against my routing implementation. It forced me to change some names to match the existing API. So far, so good.
One obvious failure occurs when the controller is namespaced:
Rails allows you to give a route like
':controller/:action/:id'
but to have two segments in
the controller, such that the URL '/foo/bar/baz/1'
will match controller Foo::Bar
with action
baz
and id 1
—if and only if such a
controller exists.
Now, I could implement this fairly easily. For a start, I’d have to change my tree to branch on strings instead of route segments, and to start using left-anchored substring matches against the remaining request URL at each comparison. In fact, regardless of the namespace problem, that might well be a better method than the one I’m currently using, as it would preserve the original path more accurately when this is passed as a parameter to an action.
But there’s a problem: the router still needs to know the name
of every valid controller in the application. Another possibility
would be to make :controller
a special case, with an
extra branch for a possible namespace. Once again, though, the
router needs to know what those are.
I dislike the tight coupling of the current routing implementation, and I don’t think that the router should have to know which controllers are valid (and especially not by scanning through source directories as it does in Rails at present). The trade-off is that routes files would have to specify namespaced controllers more explicitly, with a
map.connect 'namespace/:controller/:action/:id' ...
line for each namespaced controller. I don’t really see this as a bad thing, considering how it helps in making the code cleaner and better decoupled, but I’d be interested to hear any points of view on the subject.
As I was rummaging through the routing tests, I found that there’s a benchmarking ‘test’ in there, although (a) it only runs when a specific command line option is passed to the test runner, and (b) doesn’t actually work after all that! I ported it over to benchmark my routing file, adding explicit routing for the namespaced controllers. (I also pulled it out of the test file where it didn’t really belong.) With the requisite quantities of salt, here are the numbers on my MacBook:
Recognition (RouteSet): 0.0537548184394836 ms/url 18602.9834911597 url/s
As I haven’t managed to get the same benchmark running against the Rails code, I can’t compare the speed, but it’s fast enough: 50 μs on a request is really not going to be any kind of bottleneck. And it’s all done in 169 lines of code, including blank lines.
Let me spell that out clearly: It’s possible to implement route recognition that is clean, fast, and doesn’t use any magic in a small number of lines of code.