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.
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.
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.
I've found that I rarely need additional actions outside of the canonical six, though I do firmly believe we need a 'delete' action analogous to 'new' and 'edit'. Sticking to those actions like DHH mentions, allows me to stop worrying about where something belongs and get things done. Considering that most apps are simply fancy CRUD guis over a db ... it works very well.
Routing is decoupled from those actions. You are not forced to use 'example.com/categories/1/cards/5', you can have (in this case) the #show action mapped to 'example/foo/some_card' if you want.
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)
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.
I've been thinking about this very thing for awhile, too. I wrote some code to demonstrate how I think resource/routes/controllers should work: https://gist.github.com/d64847016687e82d104b
Started on an implementation based on Rails 3 ActionPack, but never got very far. To make the case statements work, you have to override #=== on Symbol.
Sounds like an interesting idea, but raises a few questions. What are the responsibilities of a resource? What actions does it know how to perform? You mentioned responding to the HTTP verbs, so clearly it knows about HTTP. Does it know how to interact with a persistence layer? If so, I think it's seriously overburdened. If not, and this perhaps is what your Entity is responsible for, then you can have an Account resource and an Account entity. Or, in other words, an Account controller, and an Account model, essentially changing little but class and method names. Another thing that bothers me is that (assuming Resource + Entity) a resource is supposed to "be" that abstract thing you're viewing / manipulating in a RESTful architecture. But this nomenclature demotes it to a proxy to that concept. I suppose one could criticize Rails similarly, except that Rails intentionally keeps the notion of a resource abstract.
> What are the responsibilities of a resource? What actions does it know how to perform? You mentioned responding to the HTTP verbs, so clearly it knows about HTTP.
Yep, it's entirely a HTTP-level concept. It is aiming to model exactly the concept of a resource which is referred to in the relevant RFCs
> Does it know how to interact with a persistence layer? If so, I think it's seriously overburdened.
Nope, and I agree. (Although it is available as mixins, so you could mix it into your model classes if you wish. However I'm tending towards discouraging this)
> If not, and this perhaps is what your Entity is responsible for
Nope - at present an entity is, as per the RESTful jargon, quite a simple object which represents an instance of some media type. You can declare a lazy entity with a block though, to avoid generating the response bytes for an entity which was refused by content negotiation.
> Another thing that bothers me is that (assuming Resource + Entity) a resource is supposed to "be" that abstract thing you're viewing / manipulating in a RESTful architecture. But this nomenclature demotes it to a proxy to that concept. I suppose one could criticize Rails similarly, except that Rails intentionally keeps the notion of a resource abstract.
Think I sort of get what you mean here, but that it's not really as much of a concern when you think about it.
Really my goal first and foremost is to formalise the interfaces which one ought to expose in order for an object to be served up as a resource by a restful HTTP server.
So in that sense, the framework is to be considered more a plugin API for RESTful HTTP servers, along the lines of Restlet for Java
There is inevitably still some boilerplate in wrapping persistence layer objects in such interfaces, but the plan is to develop standard wrappers for ActiveModel classes and instances to help with that.
And of course, not every resource is backed by an ORM.
I've found the process of trying to pin down the right interface for a Resource quite interesting though, in that it's lead me to read the HTTP RFCs a lot deeper than I otherwise would have.
One other big thing to point out is that this approach has the biggest pay-off when you're doing API-focused development (eg writing RESTful web services for thick client applications to use, which was where the framework originated from).
While you could do more traditional web apps this way too, I haven't really tried to optimise for that use case at present. It would need more rails-style glue code and help for HTML templating etc to get there, although that's certainly doable.
I've also been waiting to see if the new more flexible Rails makes it possible to switch in the resource-based routing and controller approach that I want. Sadly I suspect there wouldn't be much of Rails left by that point (we're not using ActiveRecord either), but bears investigation.
I was working on a framework like this, too -- I spoke about it at RubyConf in 2008, and the last version of the code (from 2009) is at http://github.com/bscofield/athena
I put it on hold a while back, though I'm thinking about bringing it back on top of Rails 3's more flexible substructure.
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 ;)
I think it is advocating something concrete: Drop the separate routing layer. Then it gives two concrete examples of how to handle urls: Sinatra-style and HTTP-style methods in the controller. I think the biggest problem in the article is its use of an ugly url in the examples.
I actually like the HTTP-style method approach. You mention pushback to prevent people from creating mega-controllers, and I think this takes it even a step further. It really ties the controller to the resource. As mentioned in the article, this is already how the methods are called in the tests.
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.
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.
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.
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.
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.
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.
I don't understand how a monolithic route mapping layer & a separate resource layer is more reusable than a modular resource + route layer. Care to explain?
I find the Sinatra approach leads to less code and less complexity, especially for larger apps. This is because it is easy to extract middleware using Sinatra, since I break up separate resources anyways, instead of the single Application object.
This applies even with Rails routes. Our routing scheme does not match that of Rails (we want seo friendly routes, that does not always fit the /collection-name/:collection_id paradigm).
I think simple routing systems, like Sinatra, are the way to go here.
From a simple library that does not get in the way, it's not too difficult to come up with a convention that matches your needs. The overall application then becomes simpler because I don't have to fight a heavy-weight routing system that misses the point.
The main argument is flexibility and acknowledging what the role of the controller actually is in a resource-oriented application
Idea 2 is the way to go because it's resource oriented and you get useful stuff like 405's for free which you don't with the sinatra approach.
Please don't remove routing. Having routes is important - it makes the code more descriptive and easier to follow. All a route needs to do is map one URI pattern to one controller, i.e:
"get(:member) do |id|" or "get "/api/v1/report/:id" do |id|" have the virtue of being pretty damn explicit.
As far as ensuring API consistency (a fine goal), it wouldn't be altogether difficult to have a rake task that prints the full set of routes to STDOUT for app's using such a framework. Load all of the controllers, make a route table, then emit said route table. This provides a holistic view of Sinatra-like routes similar to "rake routes".
Just the same, I can see how for apps with many types of resources, remembering the precise URL incantation would PROBABLY be more difficult than remembering Rails generated path/URL helpers.
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.
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?
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 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).
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).
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.
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.
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.
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.
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.
My sibling poster is dead on that 'normals' don't care about URLs. Often, I don't either. In my app, I have a bunch of "real" web pages, and then a bunch of "accidental" URLs that only exist to handle ajax requests. These aren't real pages, and what they return is of no value to the user unless they're on the page that generated the ajax call.
I don't want to go through the process of creating a new URL, figure out what its parameters will be, and then map those HTTP parameters to arguments to a server-side function. I want to magically link a client side action to a call on the server that may perform a side effect or return HTML, or both. The URLs are an implementation detail that I'd rather not think about.
If you care about REST (and arguably you should) then URLs are not an implementation detail. Especially if you are creating an API that others may eventually use to get at your data, URLs are everything. I'll say it again: the application is the URLs. Because ultimately the application is its resources and simple manipulations of them, which the URLs are representing.
Caring about what URLs look like is at best irrelevant, but normally extremely counterproductive to actual RESTful implementation. If you're building URL strings to make requests (save for get-based forms) anywhere in any of your clients, you're doing it wrong. Wanking about pretty URLs just encourages you to go in the wrong direction.
In true REST, URLs are opaque identifiers. They might as well be UUIDs. The only way you're supposed to get one to request is in a response from the server.
Hypertext. Is. The. Engine. Of. Application. State.
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."
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.