Search code examples
ruby-on-railsrailstutorial.orgruby-on-rails-7

Rails - errors partial not rendering


I'm very new to Rails and full-stack development in general. I've just finished Michael Hartl's Ruby on Rails Tutorial 6th edition and am walking through it again to build my first project (i.e. an app that's different from the sample app built in the book, but draws on a lot of the same lessons). Catch is that new project is using Rails 7.

It was going well until I ran into issues around Chapter 7; my app will not render a partial with error messages for bad submissions to the new user form. The code in the partial executes (as verified with debugger, and later with a puts statement to output on console), but the HTML doesn't output (i.e. it cannot be found when inspecting the page in Chrome). There is a CSS issue related to newer version of bootstrap, but I even tried downgrading to the bootstrap version from the book (3.4.1) with no luck. (the offending CSS segment is commented out below)

I've banged my head on this for a few hours. Hoping it's just something dumb I'm missing. If it's a broader issue with Bootstrap vs Importmaps or something I'd also appreciate references on good places to learn these. I am extremely grateful for any ideas!

Edit This definitely isn't an issue with passing local variables into the partial; see code snippet and comment added at the end of this post.

app/views/users/new.html.erb:

<% provide(:title, 'Create New User') %>
<h1>Create New User</h1>

<div class="row">
    <div class="col-md-6 col-md-offset-3">

        <%= form_with(model: @user, local: true) do |f| %>
            <%= render 'shared/error_messages' %>
            
            <%= f.label :email %>* <i>required</i>
            <%= f.email_field :email %>      

            <%= f.label :first_name %>
            <%= f.text_field :first_name %>
            
            <%= f.label :last_name %>
            <%= f.text_field :last_name %>
            
            <%= f.label :phone_number %>
            <%= f.text_field :phone_number %>
            
            <% # f.label :admin, "Company Admin" %> 
            <% # f.radio_button :admin, "True" %> 
            <% # f.label :admin, "Regular User" %>
            <% # f.radio_button :admin, "False", :checked => true %> 
            
            
            <%= f.submit "Create New User", class: "btn btn-primary" %>
        <% end %>
    </div>
</div>

app/views/shared/_error_messages.html.erb:

<% if @user.errors.any? %>
  <% puts "the error partial was called" %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(@user.errors.count, "error") %>
    </div>
    <ul>
      <% @user.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>

Example server output when submitting bad data that should generate an error (fail email validation):

 Started POST "/users" for 24.85.170.222 at 2022-02-14 01:01:56 +0000
Cannot render console from 24.85.170.222! Allowed networks: 127.0.0.0/127.255.255.255, ::1
Processing by UsersController#create as TURBO_STREAM
  Parameters: {"authenticity_token"=>"[FILTERED]", "user"=>{"email"=>"no123@good-email", "first_name"=>"Jeff", "last_name"=>"Lebowski", "phone_number"=>""}, "commit"=>"Create New User"}
   (0.1ms)  SELECT sqlite_version(*)
  ↳ app/controllers/users_controller.rb:13:in `create'
  TRANSACTION (0.1ms)  begin transaction
  ↳ app/controllers/users_controller.rb:13:in `create'
  User Exists? (0.2ms)  SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "no123@good-email"], ["LIMIT", 1]]
  ↳ app/controllers/users_controller.rb:13:in `create'
  TRANSACTION (0.1ms)  rollback transaction
  ↳ app/controllers/users_controller.rb:13:in `create'
  Rendering layout layouts/application.html.erb
  Rendering users/new.html.erb within layouts/application
the error partial was called
  Rendered shared/_error_messages.html.erb (Duration: 1.8ms | Allocations: 701)
  Rendered users/new.html.erb within layouts/application (Duration: 6.2ms | Allocations: 2949)
  Rendered layouts/_rails_default.html.erb (Duration: 6.7ms | Allocations: 6277)
  Rendered layouts/_shim.html.erb (Duration: 0.5ms | Allocations: 153)
  Rendered layouts/_header.html.erb (Duration: 0.8ms | Allocations: 318)
  Rendered layouts/_footer.html.erb (Duration: 0.8ms | Allocations: 258)
  Rendered layout layouts/application.html.erb (Duration: 18.8ms | Allocations: 11238)
Completed 200 OK in 35ms (Views: 23.0ms | ActiveRecord: 0.8ms | Allocations: 16412)

Gemfile:

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "3.0.3"

gem "rails", "~> 7.0.1"   
gem "sprockets-rails"
gem "puma", "~> 5.0"
gem "importmap-rails"
gem "turbo-rails"           # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "stimulus-rails"        # Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "jbuilder"              # Build JSON APIs with ease [https://github.com/rails/jbuilder]


gem "bootsnap", require: false
gem "sassc-rails"           # Use Sass to process CSS 

gem 'image_processing'#,           '1.9.3'
gem 'mini_magick'#,                '4.9.5'
gem 'active_storage_validations'#, '0.8.9'
gem 'bcrypt'#,                     '3.1.13'
gem 'faker'#,                      '2.11.0'
gem 'will_paginate'#,              '3.3.0'
gem 'bootstrap-will_paginate'#,    '1.0.0'
gem 'bootstrap-sass'#,             '3.4.1'

group :development, :test do
  gem "sqlite3", "~> 1.4" 
  gem "debug", platforms: %i[ mri mingw x64_mingw ] 
end

group :development do
  gem "web-console"     
  gem "rack-mini-profiler"   
  gem "spring"              
end

group :test do
  # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
  gem "capybara"
  gem "selenium-webdriver"
  gem "webdrivers"
  
  gem 'rails-controller-testing'#, '1.0.5'
  gem 'minitest'#,                 '5.11.3'
  gem 'minitest-reporters'#,       '1.3.8'
  gem 'guard'#,                    '2.16.2'
  gem 'guard-minitest'#,           '2.4.6'
end

group :production do
  gem "pg"
  gem 'aws-sdk-s3', require: false
end

app/assets/stylesheets/custom.scss:

@import "bootstrap-sprockets";
@import "bootstrap";

/* mixins, variables, etc. */

$gray-medium-light: #eaeaea;

/* universal */

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
  h1 {
    margin-bottom: 10px;
  }
}

@mixin box_sizing {
  -moz-box-sizing:    border-box;
  -webkit-box-sizing: border-box;
  box-sizing:         border-box;
}


/* typography */

h1, h2, h3, h4, h5, h6 {
  line-height: 1;
}

h1 {
  font-size: 3em;
  letter-spacing: -2px;
  margin-bottom: 30px;
  text-align: center;
}

h2 {
  font-size: 1.2em;
  letter-spacing: -1px;
  margin-bottom: 30px;
  text-align: center;
  font-weight: normal;
  color: $gray-light;

}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}

/* header */

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: white;
  /* text-transform: uppercase; */
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  &:hover {
    color: white;
    text-decoration: none;
  }
}

/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid #eaeaea;
  color: $gray-light;
  a {
    color: $gray;
    &:hover {
      color: $gray-darker;
    }
  }
  small {
    float: left;
  }
  
}


footer ul {
  float: right;
  list-style: none;
}

footer ul li {
  float: left;
  margin-left: 15px;
}

/* Forms */

input, textarea, select, .uneditable-input {
  border: 1px solid #bbb;
  width: 100%;
  margin-bottom: 15px;
  @include box_sizing
}

input {
  height: auto !important;
}

#error_explanation {
  color: red;
  ul {
    color: red;
    margin: 0 0 30px 0;
  }
}

/*
.field_with_errors {
  @extend .has-error;    /*this is breaking 
  /*potential solution? https://jasoncharnes.com/bootstrap-4-rails-fields-with-errors/
  .form_control {
    
    color: $state-danger-text;
  }
}
*/ 

/* Miscellaneous */

.debug_dump {
  clear:       both;
  float:       left;
  width:       100%;
  margin-top:  45px;
  @include box_sizing;
}

app/controllers/users_controller.rb:

class UsersController < ApplicationController
  def new
    @user = User.new
  end
  
  def show
    @user = User.find(params[:id])
  end
  
  def create
    @user = User.new(user_params)   #strong params required by Rails
    #debugger
    if @user.save
      #handle succesful save
    else
      render 'new'
    end
  end
  
  private
  
    def user_params
      params.require(:user).permit(:email, :first_name, :last_name, :phone_number, :admin)
    end
end

Edit Ok, I have confirmed by inserting test pieces into the code that this is something to do with HTML rendering. It is definitely not an issue with the instance variable passing into the partial (although I've now learned why that's poor practice!). Example below outputs to the command line exactly as expected, however the plaintext "False" doesn't change to "True" when erroneous data is submitted in the form. It almost seems like this is something with Ajax or similar where the form is submitted and the ruby/rails runs, but the html isn't refreshed? Furthermore - I've now written tests for the errors to appear and the tests pass! This is messing with my head.

app/views/shared/_error_messages.html.erb:

<% puts @user.errors.any? %>
<% if @user.errors.any? %>
  True
  <% puts @user.errors.count %>
  
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(@user.errors.count, "error") %>
    </div>
    <ul>
      <% @user.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% else %>
  <% puts @user.email %>
  False 
<% end %>

Solution

  • Solved! This was an issue with Turbo / Hotwire in Rails 7 (instead of Turbolinks). In the users_controller, I had to change the render call to include status: :unprocessable_entity. The behavior works perfectly now.

      def create
        @user = User.new(user_params)   #strong params required by Rails
        #debugger
        if @user.save
          #handle succesful save
        else
          render 'new', status: :unprocessable_entity
        end
      end
    

    Thanks to all who commented. Even if using instance variables in a partial was not the issue, I did read up and learn why that's undesireable practice, so worthwhile.

    I figured out the answer from this stackoverflow comment: Rendering a view after form submission not working correctly in Rails 7