There have been many questions about this, but none of them seem to help. And yes, I have watched this rails cast.
I have an Author who has many Books, like so:
Author:
class Author < ActiveRecord::Base
attr_accessible :name
has_many :books, dependent: :destroy
accepts_nested_attributes_for :books, allow_destroy: true
validates :name, presence: true
validates :name, length: { minimum: 3 }
end
Book:
class Book < ActiveRecord::Base
attr_accessible :name, :year
belongs_to :author
validates :name, :year, presence: true
validates :year, numericality: { only_integer: true, less_than_or_equal_to: Time.now.year }
end
I created the following form to add a book to an author in authors#show:
<%= form_for([@author, @book], html: { class: "well" }) do |f| %>
<% if @book.errors.any? %>
<div class="alert alert-block">
<ul>
<% @author.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
#labels and buttons...
<% end %>
...with the following authors_controller method:
def show
@author = Author.find(params[:id])
@book = @author.books.build
end
...and the following books_controller method:
def create
@author = Author.find(params[:author_id])
if @author.books.create(params[:book])
redirect_to author_path(@author)
else
render action: :show
end
end
I cannot seem to figure out why the form does not display any error messages. I followed the example from railscasts where they say there should be an instance variable of books in the form instead of @author.books.build, so I put the latter in the controller and @book in the form - still to no avail.
Thanks for any help!
Let's step through it.
You submit the create, and that enters your create action
def create
@author = Author.find(params[:author_id])
if @author.books.create(params[:book])
redirect_to author_path(@author)
else
render action: :show
end
end
(Side note, what if @author is not found. You are not handling that case.)
Now, the Author is found, but @author.books.create fails (returns false), so you render the show action.
This uses the show template, but does not call the show action code. (Side note, maybe the new page would be a better choice, so the user can try to create again.)
At this point @author is instantiated with the Author you found, but not @book. So @book, if called will be nil.
Your show template does
if @book.errors.any?
which will not be true, so the rest of the template inside the if will be skipped. That's why there are no errors.
You don't need a form_for to display error messages. If you switch to using the new template, then there will be a form to try again.
So let's switch to rendering new.
Class BooksController < ApplicationController
def new
@author = Author.find(params[:author_id])
@book = @author.books.build
end
def create
@author = Author.find(params[:author_id])
@book = @author.books.build(params[:book])
if @author.save
redirect_to author_path(@author)
else
render action: :new
end
end
Your new template will be
<% if @author.errors.any? %>
<div class="alert alert-block">
<ul>
<% @author.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% if @book.errors.any? %>
<div class="alert alert-block">
<ul>
<% @book.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form_for([@author, @book], html: { class: "well" }) do |f| %>
#labels and buttons...
<% end %>
In Books controller /books_controller.rb
def new
@author = Author.find_by_id(params[:author_id])
@book = @author.books.build
end
def create
@author = Author.find_by_id(params[:author_id])
if @author
@book = @author.books.build(params[:book])
if @book.save
flash[:notice] = "Book saved successfully"
redirect_to author_path(@author)
else
render :new
end
else
flash[:notice] = "Sorry no author found"
redirect_to author_path
end
end
If author is not present redirect to authors index page with error message dont render the new form as you'll not be able to build the books form as author is nil.
And in your books new form you can have the error listed for books
/books/new.html.erb
<% if @book.errors.any? %>
<div class="alert alert-block">
<ul>
<% @books.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With