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

On Mar 25, 1:42 pm, “Russell N.” [email protected] wrote:

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

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

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

Rod wrote:

@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

I had the same issue and ended this way, saving a whole @comment object
in flash:

respond_to do |format|
if @comment.save
flash[:notice] = ‘Comment was successfully created.’
format.html { redirect_to post_path(@comment.post_id) }
format.xml { head :created, :location =>
comment_path(@comment.post_id, @comment) }
else
flash[:comment] = @comment
format.html { redirect_to post_path(@comment.post_id) }
format.xml { render :xml => @comment.errors.to_xml }
end
end

Then I added “@comment = flash[:comment]” to “show” method in my
posts_controller.rb:

GET /posts/1

GET /posts/1.xml

def show
@post = Post.find(params[:id])
@comment = flash[:comment]
respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @post.to_xml }
end
end

My Post is like your Team, my Comment is like your Resource.

Hope that helps.

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

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

On Mar 26, 1:11 pm, Ben M. [email protected] wrote:

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).

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.

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

On Mar 26, 11:03 pm, “Jeff” [email protected] wrote:

On Mar 26, 10:26 pm, “Rod” [email protected] wrote:

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

On Mar 26, 10:26 pm, “Rod” [email protected] wrote:

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

On 27-Mar-07, at 6:58 AM, Rod wrote:

should use respond_to to provide the appropriate responses.
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

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

On 27-Mar-07, at 11:51 AM, Ben M. wrote:

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