This topic is for the Validations exercise in the Rails Fundamentals trail. Post any questions, corrections, or pointers you have to share with other Upcase subscribers.
Trying to do the Validations exercise on the Ruby on Rails trail and I canāt workout how to pass the error object back to the welcome controller from the GuestbookEntries controller when the form input is invalid - Iām assuming Iām missing something blindingly obvious but canāt work it out. Any suggestions?
https://exercises.upcase.com/exercises/validations
I remember doing this exercise and thinking that the design was a bit odd.
Hereās a hint: One controller can render views which are for another controller just by calling render
with a path. For example render 'widgets/show'
will render app/views/widgets/show
, even if called from some controller other than WidgetsController
.
Hmmmā¦I tried that (doing a render āpages/welcomeā) but was then getting issues as the @guestbook_entries wasnāt defined as I define that in the welcome controller which isnāt getting called
Calling render isnāt the same as a redirect. For instance, given the following code you might expect calls to FooController.create
to execute the code in PagesController.welcome
thereby setting the @guestbook_entries
variable. But this isnāt actually the case, render simply tells Rails what view content it needs to serve to the browser. If the resulting view template makes use of the @guestbook_entries
variable then youāll need to define it once again in the controller action that makes the call to render
.
class PagesController < ApplicationController
def welcome
@guestbook_entries = ...
end
end
class FooController < ApplicationController
def create
@guestbook_entries = ...
render 'widgets/show'
end
end
You also have the option of using redirect_to
instead of render
, but youāll need to relay the error messages into the flash hash:
def create
...
redirect_to(welcome_index_path, flash: { error: @foo.errors })
end
Unless itās been changed recently, the controller spec supplied with the exercise needs the template to be rendered:
describe GuestbookEntriesController do
describe "#create" do
context "invalid entry" do
it "renders the welcome view" do
post :create, guestbook_entry: { body: "" }
expect(response).to render_template "pages/welcome"
end
end
end
end
Thanks a lot for your help - I did just have to use the render on the welcome/page and then re-declare the guestbook entries within the create to get it to pass. Iām not overly keen on my solution so will go through and look and see how other people have done it to hopefully find a neater solution.
Once again, thank you!
Set the flash with the errors object See: Layouts and Rendering in Rails ā Ruby on Rails Guides
I had to rewrite one of the tests. Render test like this are new to me. But I had to set the flash within the test, since apparently the assign()
is mocking the controller, and the flash hadnāt been set there. Are we expected to not use the flash, and call @guestbookEntry.errors.messages directly in the view?
it "displays the error messages of an invalid form submission" do
assign(:guestbook_entries, [])
g = GuestbookEntry.new body: ""
g.valid?
flash[:notice] = g.errors.messages
assign(:guestbook_entry, g)
render
expect(rendered).to have_content("Body can't be blank")
end
I am calling the error messages in the view but I am not seeing them displayed.
My view:
<h1>Welcome to My Guestbook</h1>
<br>
<%= image_tag("under_construction.gif") %>
<div id="guestbook-entries">
<p>Guestbook Entries:</p>
<ul>
<% @guestbook_entries.each do |guestbook_entry| %>
<%= guestbook_entry.created_at.strftime("%b %d %Y:") %>
<%= guestbook_entry.body %>
<% end %>
</ul>
</div>
<%= form_for @guestbook_entry do |f| %>
<% if @guestbook_entry.errors.any? %>
<h2><%= pluralize(@guestbook_entry.errors.count, "error") %> prohibited this entry from being saved:</h2>
<ul>
<% @guestbook_entry.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
<% end %>
<%= f.label :body, "Guestbook Entry:" %>
<%= f.text_area :body %>
<%= f.submit "Submit" %>
<% end %>
My guestbook_entries_controller:
class GuestbookEntriesController < ApplicationController
def create
guestbook_entry = GuestbookEntry.create(guestbook_entry_params)
if guestbook_entry.valid?
redirect_to root_path, notice: "Thank you for your entry."
else
@guestbook_entry = GuestbookEntry.new
@guestbook_entries = GuestbookEntry.all
render template: "pages/welcome"
end
end
private
def guestbook_entry_params
params.require(:guestbook_entry).permit(:body)
end
end
My model:
class GuestbookEntry < ActiveRecord::Base
validates :body, presence: true
validates :body, length: { maximum: 255 }
end
When you call guestbook_entry.valid?
, that will set errors on the guestbook_entry
. However, this is a local variable (no @
prefix)., so it never gets shared with the view.
(I think itās better if I donāt spell out the solution, hopefully this will get you on the right track!).