RESTful routing question

I'm a noob to Rails, Ruby, and REST architecture, so please forgive me if this is a obvious question.

I've got a model ("Team") that aggregates some objects ("Resources"). In the Team view

/teams/show/3

I display the info for the team, along with the aggregated Resource objects. So far so good. I want a form to add a resource, so I put in a form element into the views/teams/show.rhtml file:

<% form_for :resource,       :url => {:controller => "resources" } ,       :html => { :multipart => true } do |form| %> ... fields

<% end %>

Again, so far so good: my resource_controller.rb script gets the form data in its create method.

    @resource = Resource.new(params[:resource])     if @resource.save       flash[:notice] = 'Resource was successfully created.'       redirect_to :back     else       flash[:notice] = 'Error creating resource'       logger.info "Save failed" # I see this in the logs       redirect_to :back # @resource is nil in the rhtml page!     end

But how do I get back to the /teams/show page? If I redirect, it seems all info for the model is lost: @resource is nil again. I'm blaming that on the redirect_to, but I can't figure out how to make the render method use the teams controller. I've event tried parsing the originating path out of request.env['HTTP_REFERER'], but besides being butt-ugly code I can't even render that path:

render :template => 'teams/show/3'

tries to render "3.rhtml" while

render:template => 'teams/show'

loses the ID so I get an error. Can I do cross-controller UI?

Thanks for any help, Rod

Most of the time you’ll want to just render the error where you are instead of redirecting. At least, nearly every tutorial or piece of Rails source I’ve looked at does that. Does that make sense or should I give an example code?

RSL

Very important thing to understand: redirect_to *ends the current request* and sends a 302 to the client (the browser). All the state you set up in your controller is gone.

If you need something to survive the redirect, put it in the session, or better yet, in the flash... which is just a self-managing hash stored in the session.

b

Rod wrote:

Yeah, that's what most every tutorial shows... and it's one of my pet peeves. My cardinal rule is this: POST shall NEVER return content.... it should always return a 302.

This is roughly analogous to the original intent (and the approach taken by REST); although it should really return a 201 with the redirect url in the Location header. But then again, we use POST because html can't do PUT. Sheesh, it's almost as if html and http were not written by the same guy [1]!

b

[1] Tim Berners-Lee - Wikipedia

Russell Norris wrote:

Hi, Russell. That's exactly what I'd like to do - show the original form with the errors highlighted. But since the form was posted to a different controller (from team_controller, to resource_controller), Rails doesn't seem to know how to send it back to the original page.

The posting form is in views/teams/show.rhtml, it posts to resource_controller via a post to /resources, and upon failure Rails tries to render views/resources/index.rhtml. I could have a distinct page from a GET /resources that had a form, and I'm sure that would work, but I'd rather my UI design wasn't constrained by my model.

Rod

You’ve really got me thinking on this one. I need to do some more reading about it but it makes sense what you’re talking about.

RSL

Hey Ben,

I'm in no way an expert but that's a new one for me: where exactly
does it say in the HTTP spec that POST can't return content?

This particular question was framed in terms of rendering an HTML
page that includes an error description and gives the opportunity to
make corrections - if you don't return content (to that effect) when
responding to the POST then I can only assume you'd have to assign a
unique URI to allow the user to see the results of, and optionally
correct issues with, that particular POST.

So, how do *you* present errors if your cardinal rule is never
returning content for a POST?

Regards, Trevor

Oh, I don't think "redirect on POST" is actually specified in the HTTP spec... might be though... I should take a look at some point.

What I was saying is that I am a redirect on post zealot. The prevailing rails attitude (in tutorials and books at least) is "use a redirect after you POST.... but only if it succeeds" (since they all render the form again with errors *in the same request*).

That doesn't make any sense to me. It's embracing a pattern and only using it halfway.

The primary reason why the redirect on post pattern interests me has nothing to do with specs. Rendering html during a POST request has unpleasant side effects: the POST url is in the browser address bar and the user is liable to get the mysterious (to most users) error message: "this request had POST data, decide what to do and don't screw it up!"

It does seem, however, that at least the way rails is doing REST follows in the "no response body" approach. Of course, that analogy only goes so far because somehow Berners-Lee managed to write the HTTP protocol with 7 verbs and then only get around to supporting 2 in HTML.

Anyway, I do redirect-on-errors simply by putting the model into the flash, redirecting back to the form action (edit or new), which now checks for the model in the flash before doing their normal load or create. This would get more awkward if you have a form with multiple models behind it, but I don't see that much.

Hope I've explained myself better....

b

Trevor Squires wrote:

Thanks for all the input. I think I'm going to punt and do an AJAX submission. :slight_smile:

On the data-from-a-POST issue, seems to me that's more of a REST issue than one inherent in HTTP. But even in REST, it seems like a POST should be able to return status information. To me that's fundamentally different than returning model information. Not all clients are browsers, and a redirect seems like a very browser-centric way to handle bad data.

Well, I disagree slightly... not all clients are browsers, but clients that ask for html back should get a redirect. Other clients should specify something different in what they can accept, and your app should use respond_to to provide the appropriate responses.

So an API client expecting XML back would not get a redirect upon success, but the xml of the new element or perhaps just a blank 200 response, whatever you think makes sense.

Jeff

I hadn't thought of that, and it makes sense. But one thing niggles me - if a POST is not supposed to return any data, not even status information (I know you didn't say that, but it is a point under discussion), then what's it doing examining the Accept header? And what's the client doing specifying a return format when there's not supposed to be any returned info?

Seems like a bit of an impedance mismatch. So far the best position to me is that a POST cannot return model information but can return status information pertaining to the action taken on the server. Others obviously have strong opinions in other directions, though, so I'd love it if they'd elaborate.

Rod

Well, I disagree slightly... not all clients are browsers, but clients that ask for html back should get a redirect. Other clients should specify something different in what they can accept, and your app should use respond_to to provide the appropriate responses.

So an API client expecting XML back would not get a redirect upon success, but the xml of the new element or perhaps just a blank 200 response, whatever you think makes sense.

Jeff

I hadn't thought of that, and it makes sense. But one thing niggles me - if a POST is not supposed to return any data, not even status information (I know you didn't say that, but it is a point under discussion), then what's it doing examining the Accept header? And what's the client doing specifying a return format when there's not supposed to be any returned info?

Because the HTTP1.1 spec says POST can return content. If the result of your post is URI addressable, return 201 and the Location. If the result is not URI addressable (the re-rendered form requesting corrections is a prime example of this) then you can return 200 and an entity (which is what we're calling 'content' here).

Ben is a self-confessed redirect-on-post zealot. His strategy of sticking the non-validating model into flash probably offends me (because of potential timing issues - however insignificant) just as much as it offends Ben to return the form and error messages in the POST response. IMHO he's wasting a round-trip and breaking one of *my* arbitrary rules: 'no fully-fleshed-models in the session'.

I'm certain he's a smart guy and has come to this strategy based on real experience, but his strategy is *not* mandated by the HTTP spec.

Regards, Trevor

Hey hey hey now.... you you calling "smart"? :slight_smile:

Yeah, I did find examples in O'Reilly's Definitive HTTP Guide of POST's returning plain text... I doubt there's any restriction there. But really, you interpreted what I had said as invoking specs... I was not. I was assuming that the dictates of RESTful rails reflected the original intentions of HTTP (i.e. that we return the location of the newly created resource after a POST).

But really, I was/am far more concerned with the reality of web browsers: if you return html content from a POST, you break the history. It's that simple. The designers of rails understand that; that's why they had generated controllers redirect after a successful save.

But going straight back to the form on errors still has the potential for screwing up users. I'd rather design for user comfort than follow my notions of architectural purity.

Or perhaps you're not talking purity.... what do you mean by "potential timing issues - however insignificant"? That this data is not pulled fresh from the db? Doesn't seem like redisplaying the form directly solves that.

Actually, I've never understood the aversion to AR objects in the session at all. I mean, I'm all for light sessions for performance sake (though many many many apps really don't have to worry about that), but why does putting something more complex that a string or an integer in the session give people the willies.

Maybe I'm just spoiled by my time in javaland where we'd put all sorts of stuff in the session. I actually wrote a Flash (though I did not use that ridiculous name) for Spring MVC and for WebWork just to carry my domain objects through the error redirect.

Then I get fully into rails and find resistance to the idea... hmph.

b

Trevor Squires wrote:

Hey hey hey now.... you you calling "smart"? :slight_smile:

Yeah, I did find examples in O'Reilly's Definitive HTTP Guide of
POST's returning plain text... I doubt there's any restriction there. But really, you interpreted what I had said as invoking specs... I was
not. I was assuming that the dictates of RESTful rails reflected the
original intentions of HTTP (i.e. that we return the location of the newly created resource after a POST).

absolutely - there is no argument from me that *when* you can give
the URI of a newly created resource, that's what you do - always.

And by the way, I never interpreted what you said as invoking specs -
my most recent reply was in response to a question (paraphrasing)
like: "if you're not supposed to return content, then why the Accept
headers?" - and my reply was "the spec says you can return an entity
(content) when the result is not URI accessible - that's why".

But really, I was/am far more concerned with the reality of web browsers: if you return html content from a POST, you break the
history. It's that simple. The designers of rails understand that; that's why they had generated controllers redirect after a successful save.

Okay, no arguments there either. But I don't get *too* hung up on a
slightly goofy history in the event of having to re-render a form
with errors - after all, I also do AJAX which is generally not
terribly back-button friendly - it's just tradeoffs.

But going straight back to the form on errors still has the potential for screwing up users. I'd rather design for user comfort than
follow my notions of architectural purity.

Or perhaps you're not talking purity.... what do you mean by
"potential timing issues - however insignificant"? That this data is not pulled fresh from the db? Doesn't seem like redisplaying the form directly solves that.

The timing issue (remember I said "however insignificant") is this:
flash is used in the next http request for the current session - no
guarantees about whether or not another one of the user's windows (or
tabs) issuing a completely unrelated request to the app will steal
your flash. Yeah, it's an incredibly small chance. But that chance,
along with the fact that what you are talking about is actually more
work and that it relies on shoving serialized objects into the
session, makes the "always redirect even on errors" strategy
unattractive.

Actually, I've never understood the aversion to AR objects in the session at all. I mean, I'm all for light sessions for performance
sake (though many many many apps really don't have to worry about that),
but why does putting something more complex that a string or an integer in the session give people the willies.

Experience has taught me that my life is simpler if I don't store
fully-hydrated objects in the session :slight_smile:

Maybe I'm just spoiled by my time in javaland where we'd put all sorts of stuff in the session. I actually wrote a Flash (though I did not
use that ridiculous name) for Spring MVC and for WebWork just to carry my domain objects through the error redirect.

Then I get fully into rails and find resistance to the idea... hmph.

heh - well, you only seem to be getting resistance from one loudmouth
(me) so don't take it too harsh :slight_smile:

Trev

Good points... nice conversation... thanks Trevor.

I used to want to argue my ideas to death, but I've learned that nothing is ever totally cut and dry.... there are always pros and cons to everything.

b

Trevor Squires wrote: