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

Ruby on-RailsFormsViewControllerRoutes

Ruby on-Rails Problem Overview


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?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

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.

Solution 2 - Ruby on-Rails

I just started with the Rails-Tutorial and had the same problem. The solution is just simple: If you want the same URL after submitting a form (with errors), just combine the new and create action in one action.

Here is the part of my code, which makes this possible (hope it helps someone^^)

routes.rb (Adding the post-route for new-action):

...
	resources :books
	post "books/new"
...

Controller:

...
def create
	@book = Book.new(book_params)
	
	if @book.save
		# save was successful
		print "Book saved!"
		
		else
		# If we have errors render the form again	
		render 'new'
	end
end

def new	
	if book_params
		# If data submitted already by the form we call the create method
		create
		return
	end
	
	@book = Book.new

	render 'new' # call it explicit
end

private

def book_params
	if params[:book].nil?  || params[:book].empty?
		return false
	else
		return params.require(:book).permit(:title, :isbn, :price)
	end
end

new.html.erb:

<%= form_for @book, :url => {:action => :new} do |f| %>
  <%= f.label :title %>
  <%= f.text_field :title %>

  <%= f.label :isbn %>
  <%= f.text_field :isbn %>

  <%= f.label :price %>
  <%= f.password_field :price %>

  <%= f.submit "Save book" %>
<% end %>

Solution 3 - Ruby on-Rails

Just had the very same question, so maybe this might help somebody someday. You basically have to make 3 adjustments in order for this thing to work, although my solution is still not ideal.

  1. In the create action:

    if @book.save redirect_to(@book) else flash[:book] = @book redirect_to new_book_path end

  2. In the new action:

    @book = flash[:book] ? Book.new(flash[:book]): Book.new

  3. Wherever you parse the flash hash, be sure to filter out flash[:book].

--> correct URL is displayed, Form data is preserved. Still, I somehow don't like putting the user object into the flash hash, I don't think that's it's purpose. Does anyboy know a better place to put it in?

Solution 4 - Ruby on-Rails

It doesn't land you at /books/new since you are creating resource by posting to /books/. When your create fails it is just rendering the new action, not redirecting you to the new action. As @MrYoshiji says above you can try redirecting it to the new action, but this is really inefficient as you would be creating another HTTP request and round trip to the server, only to change the url. At that point if it matters you could probably use javascript change it.

Solution 5 - Ruby on-Rails

It can be fixed by using same url but different methods for new and create action.

In the routes file following code can be used.

resources :books do
  get :common_path_string, on: :collection, action: :new
  post :common_path_string, on: :collection, action: :create
end

Now you new page will render at url

books/common_path_string

In case any errors comes after validation, still the url will be same.

Also in the form instead using

books_path

use

url: common_path_string_books_path, method: :post

Choose common_path_string of your liking.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionrcdView Question on Stackoverflow
Solution 1 - Ruby on-RailsJim StewartView Answer on Stackoverflow
Solution 2 - Ruby on-RailsofhouseView Answer on Stackoverflow
Solution 3 - Ruby on-RailspanepeterView Answer on Stackoverflow
Solution 4 - Ruby on-RailsDoonView Answer on Stackoverflow
Solution 5 - Ruby on-RailsBotView Answer on Stackoverflow