AdamT AdamT - 21 days ago 8
Ruby Question

Show association errors in a form

I have a model: review.rb

class Review < ActiveRecord::Base
validates :title, :presence => true
belongs_to :product
end


model: product.rb

class Product < ActiveRecord::Base
validates :name, :presence => true

has_many :reviews, :dependent => :destroy
end


and this form: _form.html.haml

=form_for([@product, @product.reviews.build]) do |f|
.field
=f.label :title
%br
=f.text_field :title


with this reviews_controller:

def create
@product = Product.find(params[:product_id])
@review = @product.reviews.create(params[:review])
redirect_to product_path(@product)
end


When I add a review with no title the review doesn't get created because the title is required (which is good). I'm not sure how to get the errors to show on this association though.

I tried this:

=form_for([@product, @product.reviews.build]) do |f|
-if @product.reviews.errors.any?
.errors
%h2
=pluralize(@product.reviews.errors.count, "error")
%ul
=@product.reviews.errors.full_messages.each do |msg|
%li
=msg


but got this error:

undefined method `errors' for #<ActiveRecord::Relation:0x00000103e31df0>


I tried this:

-if @review.errors.any?
.errors
%h2
=pluralize(@review.errors.count, "error")
%ul
=@review.errors.full_messages.each do |msg|
%li
=msg


And got:

undefined method `errors' for nil:NilClass


In the controller when I:

raise @review.errors.inspect


I can see the errors:

#<ActiveModel::Errors:0x00000103d88c28 @base=#<Review id: nil, title: "", description: "", rating: nil, helpful: nil, product_id: 1, created_at: nil, updated_at: nil>, @messages={:title=>["can't be blank", "is too short (minimum is 5 characters)"]}>


What am I forgetting? How do I get the errors to show?

Thank you

Answer

When you make the call to redirect_to product_path(@product), you are invoking a different controller action, namely the Products#show action. The instance variables your views will have been reset. You probably aren't initializing a @review instance variable in this action which is why you are getting an exception.

What you want is something like this

# posts_controller.rb
def show
  @product = Product.find(params[:product_id])
  @review = @product.reviews.build
end

# show.html.erb
= form_for([@product, @review]) do |f|
  -if @review.errors.any?
    .errors
      %h2
        =pluralize(@review.errors.count, "error")
      %ul
        =@review.errors.full_messages.each do |msg|
          %li
            =msg

# reviews_controller.rb
def create
  @product = Product.find(params[:product_id])
  @review = @product.reviews.build(params[:review])
  if @review.save
    redirect_to product_path(@product)
  else
    render 'products/show'
  end
end

The key is that if the review is unsuccessfully created, you will re-render the page with the same instance variables intact.

Comments