
Rethinking Rails 3 Controllers and Routes - PeepCode Blog - jfcouture
http://blog.peepcode.com/tutorials/2010/rethinking-rails-3-routes
======
dhh
What an awesome way to advocate for code change. Very pretty.

Unfortunately, I also think it's faulty. First, it doesn't actually advocate
anything concrete. There's some hand-waving to Sinatra and other Rails
features, but nothing concrete. If you're going to make such a pretty
proposal, it should come with a call to specific action that people can get
behind.

This is double true when it comes to API design. It's all fine and good to
general ideas and principles guiding you, but when the code hits the editor is
when all the constraints and trade-offs are revealed. I could fill a book with
all the premature ideas I had for API rewrites that turned out not work when
applied to the tough reality of real code.

But I'm wearing too far into hand-waving territory too, so let me address a
few of the points raised:

1) "Not specifying URLs directly leads to poor [URL] API design and other
ills": There are only so many (reasonable) ways to specify a URL if you want
to follow REST principles. You name your resource and you give it an
identifier.

/products/1-some-perma-id became a pattern because it was both simple,
repeatable across models, and extractable. Why spend time coming up with a
unique URL structure for every model that you want to expose when they follow
the same pattern of using the model name as the url identifier?

This is exactly what Rails does and always have done. Spot patterns currently
done by hand, extract said pattern into a convention, allow people to move on
from thinking about how to do X until they hit an exception. This, in my mind,
is exactly what leads to great API design and simple solutions. You do the
same as everyone else when the choice is less important than the consistency.

Now I welcome the praise for Sinatra. I think it's completely awesome. I'd use
it for smaller projects myself. But that's exactly where conventions don't
give you that much. If you're exposing, say, 20 urls to the public, you don't
gain a bunch from having a convention that follows a set pattern. In fact,
cutting out the middle man of an abstraction can make the code seem easier to
read and more immediate.

That trade off flips when you have 100, 500, or (as is the case of Highrise)
2000 routes exposed to the public. When 95% of those follow the same pattern,
there's big gain in the consistency of a convention. The last 5% are handled
by outlet valves that allow you to declare whatever exceptions you need.

2) "The Seven Action Names Don't Help": I think the default constraint of 7 is
the most important part of the positive effects you get from following REST in
Rails for internal organization. Again, this isn't theory, but extracted
experience from practice.

Rails used to require you to map everything by hand. Lots of people ended up
with mega controllers that had 25 actions because there was no easy pushback.
By the time the controller was too big it felt like too much of a hassle to
break it up just to add "one more action". We still have GlobalController in
Basecamp to remind us of what that was like.

Having the default conversion that turns /product/1 into show is also just
pretty code. The two alternatives lined up are not very pretty. I'll take "def
show" over "get(:member) do |id|" or "get "/api/v1/report/:id" do |id|" any
day when it comes to a large, consistent URL surface.

3) "Assets are resources": I completely agree here. This will be addressed in
Rails 3.1 when we get the asset pipeline going.

But as always, the proof is in the code. I'd love to see an even simpler
routes system, but none of the arguments presented in the article gives any
indication that the proposed ideas will lead to that. I've been both surprised
and wrong before, though, so please do investigate.

Again, though, kudos for the presentation. I wish more people passionate about
API design would take the time to do something as pretty. But with more
concrete code examples, please ;)

~~~
h3h
A necessary component of mass production is consistency. Like it or not, Rails
has reached the masses of web developers and enforces its constraints and
conventions to facilitate the production of new web applications. I empathize
greatly with DHH when he talks about large apps and the need for conventions.
The truth is that most web developers will never devote as much attention to
URLs as some people think they should. Full stop.

With that in mind, stable conventions are of paramount importance if Rails is
going to maintain its practicality when building large web applications. On
the spectrum of poor conventions to good ones, Rails has been much closer to
the idealistic end of pushing REST principles and the concept of resources
over haphazard URLs and actions. We owe a debt to Rails and its designers for
the abundance of REST in production today.

That said, I empathize infinitely with Adrian Holovaty. I love URLs and I want
them to always be beautiful everywhere. It's true that Rails gives less
obvious control over URLs than Django or Sinatra, but it does so for the
reasons mentioned above. There are probably small changes that could be made
to make Rails more URL-aware for those of us who would like to craft every URL
with loving adoration, but those changes absolutely must be compatible with
the consistency already embodied in the Rails routing system. I haven't
thought much about what those changes might look like, but I agree with DHH
that the post above feels very hand-wavy in the proposal department.

Just remember that we URLophiles are a minority. Rails must work well for the
majority and they will never care about URLs.

~~~
dhh
I'd be curious what hand-crafted URLs you feel aren't possible with the new
Rails 3 router. We've gone far to allow all kinds of hand-crafted urls. For
example, here's an example:

get 'something/fun/:id', :to => "controller#action"

You can map just about anything that falls outside of the conventions with a
variety of that.

~~~
eob
At that point, I suppose it is just a matter of taste: should the routes be
specified inside each controller class, or should they be specified in a
separate configuration file.

The over-engineer in me likes the way Rails 3 does it. That way I can pretend
that my code is reusable, as the URL is decoupled from the code that
eventually implements its handler.

But I suppose there is also something nice about a controller which self-
defines the way in which it can be accessed.

~~~
_pius
_The over-engineer in me likes the way Rails 3 does it. That way I can pretend
that my code is reusable, as the URL is decoupled from the code that
eventually implements its handler._

You don't have to pretend ... it actually _is_ reusable. I don't get what's
"over-engineer"ed about it.

~~~
dkubb
Whenever I've tried to reuse the same controller for two URLs, I usually end
up with some conditional logic within the controller to handle difference in
parameters available from the URL.

I think a better approach would've been for me to subclass one from the other,
or have a common parent class, and then handle the parameter differences
within each subclass. At that point you'd end up with a 1:1 mapping between
controller class and URL anyway.

~~~
mikek85
That 1:1 mapping is a good thing, but I don't think that's a reason to
completely do-away with routing altogether. Sticking the route in the
controller just makes the code harder to follow.

------
patio11
I think Rails developers seriously, seriously fetishize those six actions to
the detriment of several other concerns.

First, not everything your app will do is conveniently understandable in terms
of resources, just like not everything code does is conveniently
understandable in terms of operators. We have, thankfully, largely killed
operator overloading and replaced it with functions. Why regress on function
naming in our controllers? This is really a paypal_callback, not a PUT on the
Paypal "resource", which doesn't even exist and if it did would tie across
authentication, billing, and stats subsystems. (What does a PUT on Paypal even
mean, anyhow?)

I also get hives when I think about including Rails default routes -- which
are programmer-optimized, not user-optimized -- in publicly visible places,
where they will get picked up by search engines and seen by copy/pasting
users. example.com/categories/1/cards/5 is a part of your user interface...
and it sucks. example.com/bingo-cards/holidays/halloween is superior in just
about every conceivable way.

~~~
dhh
It seems you misunderstand how the Rails system works. But let me explain.

1) You can certainly have a paypal_callback method, if you choose. That's
talking about the carrier, though, not the action. Presumably that callback
means something specify. Like completing an order or authorizing a credit card
check. Modeling your domain deeper like that makes it easier to follow and
understand. But if you're either unable, unwilling, or uninterested in
expanding your domain like that, you can certainly also just map
paypal_callback to a controller with a action that corresponds to that.

2) example.com/bingo-cards can certainly work well if you know you entire
namespace in advance. This can be true for things like CMS'es, but it's rarely
true for user account based applications. For example, if your product has a
/help section that's supposed to do something specific and a user creates
another entity called "help" you're in trouble.

The /categories part establishes a namespace. So you can have both
/categories/wonderland and /tags/wonderland and they can peacefully co-exist.

The ID part doesn't have to be a number either, but again it comes back to
namespacing. If you don't use unique identifiers, there can only be one thing
named halloween and it has to refer to the same entity. When that's true, go
ahead, knock yourself out. Lots of Rails application is having a taste of two
worlds with ID including permas, like /users/5-sam -- that's nice for SEO and
still you can have two Sams.

Hope that clears up the confusion.

~~~
patio11
I think our difference of opinion here is largely a function that you run a
web application which sprouted a CMS and I run a CMS which sprouted a web
application.

------
mjw
I think they should get rid of controllers and instead model RESTful /
resource-oriented concepts like Resource and Entity directly!

A Resource is an object which responds to some subset of GET, PUT, POST and
DELETE. It has a unique URL which identifies it.

(OK it's a bit more complicated than that, but you get the gist).

At present Controllers in Rails are a flat grab-bag of procedural code loosely
associated with some group of resources, with meta-programming used to sweep
some of the mess under the carpet. They're not an elegant way to model RESTful
concepts.

(I actually have a framework in development for doing resource-oriented APIs
on top of Rack, however it's currently very pre-1.0)

~~~
mjw
One particular thing which bothers me about controllers is the way they lump
together obviously class- or collection-level methods (index, create, ...)
with instance-level methods (show, update, delete, ...).

IMO requests should be routed to an instance representing a particular
resource; the methods on that resource should correspond directly to the HTTP
request methods. The collection (or the class) is a separate resource.

~~~
dkubb
Agreed. I always thought that Rails controllers violated the Single
Responsibility Principle
(<http://en.wikipedia.org/wiki/Single_responsibility_principle>) by mixing
collection and member concerns into a single class.

------
grandalf
He's totally right. This is why I switched to Sinatra even for apps larger
than the typical sinatra app.

Most of the need for the rewrite of the router came from the overhead
associated with having all those (often unused) routes in memory.

My sinatra apps use very little memory and that is great for a lot of reasons.

I also realized that I don't like the rails convention of having a separate
file for everything. Avoiding that has saved me lots of RSI.

~~~
binspace
Conventions are powerful. Programmers should be allowed and able to come up
with their own conventions that match their domain.

I personally find sinatra routes more scalable than Rails routes because they
are modular (defined within the resources). Rails routes are one monolithic
file with a separate controller layer.

------
sashthebash
I encountered an even bigger problem/limitation while implementing a RESTful
API with Rails. It was that it's not easy to follow one of the most important
principles of REST (from Wikipedia):

"An important concept in REST is the existence of resources (sources of
specific information), each of which is referenced with a global identifier
(e.g., a URI in HTTP)."

I do not find the global identifier (URI) in Rails when I just do a "render
:xml => @model" or "render :xml => @array". Instead of a global identifier the
id of the object is in the representation of the resource.

What is missing are the links between all the resources. When a client fetches
a collection it must guess or reconstruct the URIs manually for the individual
members. This makes it impossible to change the route generation on the server
without breaking the client.

In REST Rails Models are Resources, but Rails Models don't know anything about
their global unique id (URL). I wish there would be a way to just say user.uri
and I would get something like "<http://myservice.com/users/1> or user.path
"/users/1". It would be nice if this would be considered when rewriting the
Rails router. Maybe the routes should be defined in the model (=resource)
itself?!

I tried to implement links between resources in the API I created. You can
find some information in documentation at
<http://wiki.tagcrumbs.com/developers/rest>. In my opinion an XML
representation of a truly RESTful service should look something like this:
<http://wiki.tagcrumbs.com/developers/resources/user#examples>. Comments are
highly appreciated.

------
edwincheese
This suggestion is good until you need to change the whole set of URLs. Of
course in a single web site deployment you seldom want to change URLs (and it
is often a bad idea), but I changed URL scheme sometimes when I need to deploy
my application in another place and integrating with other system. (e.g.
Changing from path routing to subdomain). I would made crazy if I need to
modify every controller to change the URL scheme.

I think the routing abstraction is necessary and useful in larger application.
Isn't it a very simple and clever syntax for the routing configuration is good
enough?

------
experimentor
I agree.

I was introduced to Ruby through Sinatra. Developed all my web apps using that
though I used to play around with Rails. I had an initial idea that Sinatra
was meant for small apps, and for anything serious you should go Rails.

However that idea seems invalid now; the way I'm now used to Sinatra, it is
kind of getting almost all the goodies of Rails without the Routing layer
(which I find hard to follow - as mentioned by the OP).

Sinatra+ActiveRecord+ActionSupport+ActionView = Rails - Routing -
ActionController

<http://gist.github.com/423571>

------
michaelfairley
Slightly off-topic: It annoys me when I have to scroll both up and down while
reading an article to read it sequentially (see Idea 1 and Idea 2 for an
example infringement).

~~~
mr_justin
You didn't enjoy the benefit of being able to compare the two ideas side-by-
side? They both neatly fit the screen in most resolutions if your browser
window is max height, no scrolling in that particular case.

Beautifully designed article and your specific example is a great attention to
detail on the author's behalf.

~~~
michaelfairley
Three things: 1) I (and other people) like to scroll the page as we read so
our eyes stay in a relatively fixed position and the text moves under it. 2)
Plenty of monitors can't fit those entire sections. 3) Those two sections
didn't benefit much (if at all) from side-by-side comparison.

------
stanleydrew
I really like the part about getting back to thinking in URLs. The application
_is_ the URLs!

~~~
generalk
Everyone always says this, but I don't see it.

I've seen non-technical users balk at long, confusing URLs, but I've never
seen one of them praise or even _remember_ a short, semantic URL. From the few
non-technical people (mostly family) who I work with on a regular basis, URLs
are just things to click on to get somewhere. Some of them are confusing.

Having semantic, discoverable URLs is awesome for people that think in terms
of URLs. It's kind of a non-issue for people that don't.

~~~
stanleydrew
URLs have never been for users. Users don't care how your application works.
They just want it to work. URLs are for developers. We have links to abstract
away URLs for users. But that doesn't mean developers shouldn't think in terms
of URLs. Users also don't care about database tables and data normalization,
but that doesn't mean developers shouldn't.

~~~
bphogan
A subset of users care about URLs when a hierarchy exists. I've seen it happen
during usability testing. /projects/25/tasks shows the tasks list... and I've
watched the user delete the end of the url to get to /projects.

Most users don't care, but there are advantages to a clean hierarchical URL
scheme, and it's one reason I like how Rails does routing currently.

------
korch
I have come to believe the only purpose of controllers is to impose a callback
structure on top of a deterministic set of resource-models. The more simple
and lean the better. CRUD just happens to be the most useful subset of
callback structures, so it's the default. If you're putting code in your
controller that isn't for one of the 7 resource actions, then it probably
ain't controller code. (To be glib, you should instead move that code to
lib/util.rb, the smartest next mistake).

On the flip side, if you remove all the non-REST code from your controller to
the extreme, like what you'd see when using the inherited_resources gem, the
only code left will be your callbacks. DHH said this presentation needed to
show some code to make the claim hit home. So the only code left to remove is
the callback code.

How could the controller callbacks be made better, and more invisibly
integrate with the routing muck? Rails 3 is getting the state machine gem
added to core, so no doubt about this time next year someone will have re-re-
re-written the routing code using the state machine api. Could the routing
muck be made to be the controller callbacks? I always liked the idea of how
the Seaside framework did it using continuations, even though in practice it
wasn't quite as good. Everyone knows that creating a zen-simple state machine
api using continuations is not fun by anyone's measure of the word "fun."

