Resourceful routing in Rails is certainly useful. As is customary in Rails, you get a lot of stuff achieved with a very small amount of declarations. And at first glance it looks like REST! Yay! Except... it will lead one slightly astray. I've come across a few ways in which "resourceful" routing in Rails doesn't really follow REST principles, in ways that aren't merely theoretic but can affect intermediary behavior.
Resourceful routing defines paths or routes for objects that the server developer would like to have manipulated with CRUD operations. The developer can declare an 'invitation' to be a resource, and the routes to index all invitations, create a new invitation, download an invitation, update or delete the invitation, are automatically created. But, problem the first:
- Resourceful routing uses POST to create a new resource. PUT is defined as creating a new resource in HTTP, and intermediaries can use that information-- but only if PUT is used. If POST is used, an intermediary can't tell that a new resources was created.
All routes share common error handling. Route not found? Return 404! But Rails puts the method in as part of the route definition. Thus, problem the second:
- Rails returns 404 Not Found if the client uses an unsupported method on a resource that exists. So for example if I apply resourceful routing such that a resource can be downloaded with GET and updated with PUT, but don't define a POST variant route for that URL, then when the client tries to POST to that URL the server returns 404 because the route (with correct method) was not found. In theory an intermediary could mark the resource as missing and delete its cached representation. Instead, there's a perfectly good error to use in HTTP when a method is not supported on a URL, and that is 405 Method Not Allowed.
- URL parameters are mixed with body parameters. This may not cause problems for a POST, where the response typically isn't cachable anyway. But it's a bad choice in for GET requests, where the URL containing parameters affects caching, while other parameters are unseen by intermediaries.
- Query parameters are treated the same as path elements. Routes are defined as paths that can have parameters in them. So if the routes file defines a route for "/v1/store/buy/:product/with_coins", the :product path element could be any string, and Rails will pass that string into the application just as if it were a URL query parameter. However, caches are supposed to work differently if a URL has query parameters than if it does not, so treating them as the same is misleading the developer.
4 comments:
Show me, where PUT is defined as creating something...
What I said about HTTP PUT is generally accepted, though I could be more precise.
To be precise, then:
"The PUT method requests that the enclosed entity be stored under the supplied Request-URI."
RFC 2616 also says "If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response."
If a PUT request is sent to a URI and a 201 response is returned, a cache can understand that the resource at that URI was created and has the PUT request content, roughly. If a POST request is used, the intermediary does not know what happened. So Rails' use of POST vs PUT has a potential effect on intermediaries getting less information.
Rails also does not use the 201 response by default (though you can make it do so like this, but part of my argument is that the defaults aren't RESTful.)
Just a note: maybe I'm totally wrong but I found that older versions of Rails (for example 2.3.10) do not return a 404 status when using an unsupported method on an existent resource, at least for 'member' actions.
For example in this case:
map.resources :contents, :member => {:some_action => :post}
if you try to access to /contents/1/some_action via GET, a 405 Method Not Allowed status is returned.
Have this changed in newer versions of Rails? Maybe we should ask the members of the Rails core their reasons to do it ;)
If I'm not mistaken browsers are unable to submit method type PUT or DELETE. Rails is having to compensate for this feature defect by using POST to create.
Post a Comment