60

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?

notapatch
  • 4,560
  • 5
  • 30
  • 33
rcd
  • 1,300
  • 1
  • 14
  • 27
  • 2
    it doesn't land you at /books/new since you are creating resource by posting to /books/ When it fails it is just rendering the new action, not redirecting you to the new action – Doon Jan 23 '13 at 22:20
  • 1
    Try `redirect_to new_book_path(book: params[:book])` – MrYoshiji Jan 23 '13 at 22:24
  • @Doon is right (he should make it an answer so I can upvote it :)). Create actions in "Rails view" of REST do not have a visualization, hence the normal redirect. What is wrong with the wrong url here? – Jim Deville Jan 23 '13 at 22:25
  • @MrYoshiji close, but no cigar... this gives me a URL with /books/new/book%skldjalkdja (garbled stuff relating to the book ID itself, I believe). How about just /book/new? – rcd Jan 24 '13 at 06:48

5 Answers5

30

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.

Jim Stewart
  • 15,555
  • 4
  • 60
  • 83
  • 4
    Is there not a way to render :new, so that the errors are passed along to the form, while landing at /books/new in the URL? It seems as though that would look better and make more sense, given that /books/new is where I started out, and where the user expects to be. – rcd Jan 24 '13 at 06:39
  • 2
    To do that, you would have to redirect (see @MrYoshiji's comment above). The browser does a request to `create_book_path` when you submit the form. Unless you subsequently redirect to another page, the browser still thinks it's at the create URL. There's some work involved to make it really RESTful, though; if you simply redirect to `new_book_path` using GET, you'll have to encode the form parameters in the URL (not always ideal, and sometimes not possible with more complex/larger form data). The RESTful way would be to add a POST route for `/books/new` to populate the model/form data. – Jim Stewart Jan 24 '13 at 15:20
  • @Doon's suggestion to use JavaScript to update the displayed URL in the browser is a more efficient solution. I don't know offhand how to change the displayed URL. It's possible browsers won't honor it. – Jim Stewart Jan 24 '13 at 15:22
7

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 %>
Grant Birchmeier
  • 15,933
  • 11
  • 58
  • 83
ofhouse
  • 1,446
  • 21
  • 27
  • Very nice solution, thanks for sharing, I have a problem though, I use the same "_form" template for both `new` and `update` actions, so I'm trying with: `- action = params[:action] == "new" ? :new : @book` and then: `= form_for @book, :url => {:action => action}` but it doesn't seem to generate the path right. – Don Giulio Apr 30 '14 at 14:01
  • ok, I figured that I had to use `url_for @book` to generate the URL, thanks – Don Giulio Apr 30 '14 at 14:04
3

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?

pedroooo
  • 475
  • 1
  • 3
  • 13
panepeter
  • 1,924
  • 21
  • 26
  • 1
    I'm getting a CookieOverflow with this method as it's bigger than the 4k allowed for cookies – MCB Sep 23 '14 at 17:34
  • How would you pass the error messages with this though? – Gcap Jan 09 '17 at 23:26
  • @Gcap Fortunately, `flash` is a hash, so you can add additional items like `flash[:error]` or `flash[:alert]` after you've set `flash[:book]`. The [Action Controller guide](http://guides.rubyonrails.org/action_controller_overview.html#the-flash) has more detail on `flash`. – jabbett Feb 13 '17 at 19:01
2

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.

Arslan Ali
  • 16,294
  • 7
  • 51
  • 65
Doon
  • 18,294
  • 3
  • 35
  • 42
1

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.

Bot
  • 971
  • 2
  • 12
  • 31