David Tuite
David Tuite

Reputation: 22643

How to send form errors back to where they came from?

I have a page which shows a book and it's many reviews. On this page there is also a form to write a new review and post it to the create action of the reviews controller. When you post the form, the id of the corresponding book gets sent also so that the relationship can be established correctly.

At the reviews controller, we attempt to save the review. Here is what my controller looks like:

def create
    @review  = current_user.reviews.build(params[:review])
    @book = Book.find_by_ean params[:book]
    @review.book = @book

    if @review.save
      redirect_to book_path(@book)
    else
      # In here I want to go back to book_path(@book), sending @review with it so that I can have access to @review.errors
    end
  end

Of course, when the review fails to save (review content is mandatory for example) I would like to go back to the book show page and display the form, along with errors to the user. Now as far as I can work out, there are 2 possibilities here:

  1. render "books/show", :review => @review --- This does send back the review with accompanying errors (I think, not 100% on this) but the URL stays as "/reviews" which causes a ton of it's own problems. For example partials which I keep in the "/books" directory can't be found.
  2. redirect_to book_path(@book) --- This does get me back to the right URL but it doesn't send the @review with it so I can't show the error messages.

What's the best way to solve this problem?

Upvotes: 1

Views: 713

Answers (2)

DanneManne
DanneManne

Reputation: 21180

I usually solve this by sending the psot data to a member action in the original controller (in this case books) instead of a nested controller. For example:

# routes.rb
resources :books do
  member do
    post 'create_review'
  end
end

And then in your view

# books/show.html.erb
<%= form_for @new_review, :url => create_review_book_path(@book) do |f| %>
...
<% end %>

And finally in your books controller

# books_controller.rb
def create_review
  @book = Book.find(params[:id])
  @new_review = @book.reviews.build(params[:review])
  if @new_review.save
    @new_review = Review.new
  end
  render :action => :show
end

What happens is that the form has been manually directed to post it's data to our new member route in the books controller. If the Review is successfully saved, then we assign a new review object to the variable in preparation for the next review. If it is not succesfull, then the @new_review variable will contain the errors which can be accessed in the form.

Upvotes: 2

Danny Hiemstra
Danny Hiemstra

Reputation: 1188

Well, you can't really pass an object through the get params (with a redirect) unless you serialize it and you don't want that.

I see two solutions for this problem. 1) Save the review in the books controller 2) Make the form submit via ajax and update the form in the review controller using "render :update"

I don't know which one is the best, this depents on the project specs like can you use ajax?

Upvotes: 0

Related Questions