← Back to Upcase

Rails Fundamentals - Validations

(Upcase ) #1

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.

(Chris Legg) #2

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?

1 Like
(Andy Waite) #3

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.

(Chris Legg) #4

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

(Cyle Hunter) #5

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
(Andy Waite) #6

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
(Chris Legg) #7

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!

1 Like
(Brian Davis) #8

Set the flash with the errors object See: http://guides.rubyonrails.org/layouts_and_rendering.html

(Brian Davis) #9

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
(Alan Matthews) #10

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
(Andy Waite) #11

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!).