In Rails when a resource create action fails and calls render :new, why must the URL change to the resource's index url?

rcd picture rcd · Jan 23, 2013 · Viewed 12.4k times · Source

I have a resource called Books. It's listed as a resource properly in my routes file.

I have a new action, which gives the new view the standard:

@book = Book.new

On the model, there are some attributes which are validated by presence, so if a save action fails, errors will be generated.

In my controller:

@book = Book.create
...  # some logic
if @book.save
  redirect_to(@book)
else
  render :new
end

This is pretty standard; and the rationale for using render:new is so that the object is passed back to the view and errors can be reported, form entries re-filled, etc.

This works, except every time I'm sent back to the form (via render :new), my errors show up, but my URL is the INDEX URL, which is

/books

Rather than

/books/new

Which is where I started out in the first place. I have seen several others posts about this problem, but no answers. At a minimum, one would assume it would land you at /books/create, which I also have a view file for (identical to new in this case).

I can do this:

# if the book isn't saved then
flash[:error] = "Errors!"
redirect_to new_book_path

But then the @book data is lost, along with the error messages, which is the entire point of having the form and the actions, etc.

Why is render :new landing me at /books, my index action, when normally that URL calls the INDEX method, which lists all the books?

Answer

Jim Stewart picture Jim Stewart · Jan 24, 2013

It actually is sending you to the create path. It's in the create action, the path for which is /books, using HTTP method POST. This looks the same as the index path /books, but the index path is using HTTP method GET. The rails routing code takes the method into account when determining which action to call. After validation fails, you're still in the create action, but you're rendering the new view. It's a bit confusing, but a line like render :new doesn't actually invoke the new action at all; it's still running the create action and it tells Rails to render the new view.