Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The correct way to handle invalid form submissions in Rails

I'm new to rails and am not sure I agree with the way I've things done in some of the tutorials I've gone through. The issue has to do with how to handle invalid form submissions. The standard way of doing things seem to be:

class ThingsController < ApplicationController


  # POST /things
  def create

    @thing = Thing.new(params[:thing])

    if @thing.save
      flash[:notice] = 'Thing created'
      redirect_to(@thing)
    else
      render :action => :new
    end

  end

When @thing.save fails, the user is presented with the same form, prefilled-out with the values he just entered, along with a flash of what went wrong. So far so good, except that now the URL has changed from /things/new to things/, which one would expect to be rendering the index view instead.

Also, if the user refreshes the page, he is now looking at the index view. If he clicks back, he is prompted to resubmit the form, which I've always tried to avoid. If I redirect_to(new_thing_path), the user's previous submission is lost, as are the error messages.

I realize that RESTfully, this method may be "correct", since creation of a thing object should be the result of POSTing to /things, but user-interface-wise, I don't particularly care for it.

I could "manually" save the invalid @thing object in the user's session, to be displayed after I redirect him back to new_thing_path, but that feels like a hack. It seems like there should be a "rails way" of doing just that.

Ideas?

like image 242
dearlbry Avatar asked Feb 18 '11 22:02

dearlbry


1 Answers

As you've found, by default when you specify resources :things, the POST path for creating a new thing is at /things. Here's the output for rake routes:

    things GET    /things(.:format)          {:action=>"index", :controller=>"things"}
           POST   /things(.:format)          {:action=>"create", :controller=>"things"}
 new_thing GET    /things/new(.:format)      {:action=>"new", :controller=>"things"}
edit_thing GET    /things/:id/edit(.:format) {:action=>"edit", :controller=>"things"}
     thing GET    /things/:id(.:format)      {:action=>"show", :controller=>"things"}
           PUT    /things/:id(.:format)      {:action=>"update", :controller=>"things"}
           DELETE /things/:id(.:format)      {:action=>"destroy", :controller=>"things"}

It sounds like you want something more like this:

create_things POST   /things/new(.:format)      {:action=>"create", :controller=>"things"}
       things GET    /things(.:format)          {:action=>"index", :controller=>"things"}
    new_thing GET    /things/new(.:format)      {:action=>"new", :controller=>"things"}
   edit_thing GET    /things/:id/edit(.:format) {:action=>"edit", :controller=>"things"}
        thing GET    /things/:id(.:format)      {:action=>"show", :controller=>"things"}
              PUT    /things/:id(.:format)      {:action=>"update", :controller=>"things"}
              DELETE /things/:id(.:format)      {:action=>"destroy", :controller=>"things"}

Although not recommended, you can get this result with the following route:

resources :things, :except => [ :create ] do
  post "create" => "things#create", :as => :create, :path => 'new', :on => :collection
end

You would also need to modify your forms to make them POST to the correct path.

All that being said, the description of the URLs you have in your question don't sound right. You list the following: After submitting a new thing (submitting a form at /things/new),

  1. The URL changes from /things/new to /things
  2. Clicking back prompts to resubmit the form
  3. Refreshing shows things#index

This is not the functionality I experience in my own Rails 3 applications. Instead, I find that: After submitting a new thing (submitting a form at /things/new),

  1. The URL changes from /things/new to /things (this is the same)
  2. Clicking back takes the user back to the non-submitted form (no request for a re-post)
  3. Refreshing prompts to resubmit the form (as expected in my opinion)
like image 187
Michelle Tilley Avatar answered Sep 22 '22 23:09

Michelle Tilley