Errors within form not displaying in view - ruby-on-rails

I am trying to display errors in my view when a form is not submitted correctly. I have a validation set in my model for location to be present and in my form I am using the errors method to try and display the errors in my view. Below is my code. The validation is working, because I get a rails error when location is nil, it's just not displaying the msg as html.
model
class Destination < ActiveRecord::Base
validates :location, presence: true
end
form new.html.erb
<%= form_for #destination do |f| %>
<% if #destination.errors.any? %>
<% #destination.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
<% end %>
<%= f.label :location %>
<%= f.text_field :location %><br>
<%= f.submit %>
<% end %>
controller
def create
#destination = Destination.new(destination_params)
if #destination.save!
redirect_to destinations_path
else
render new_path
end
end
private
def destination_params
params.require(:destination).permit(:location, :description)
end
end

#destination.save! will raise an error if not successful.
#destination.save will return true or false.

#destination.save! will throw error in case of unsuccessful saving. To get to the render new_path line you have to do just #destination.save.

#destination.save! will throw an error. You have to do something like;
if #destination.save # returns true if successfully saved else false
redirect_to destinations_path
else
flash[:errors] = #destination.error_messages # Display errors in view
render new_path
end
HTH.

Related

Validation errors disappear after render 'new'

I have a nested form which captures information for two models, Games and Teams.
My Models:
class Game < ApplicationRecord
has_many :teams
accepts_nested_attributes_for :teams
validates_associated :teams
validates :start_time, presence: true
end
class Team < ApplicationRecord
belongs_to :game
validates :name, presence: true, length: { maximum: 50 }
end
Before saving the records, the validations must be passed. If the save fails, the form should be re-rendered and the validation error messages displayed, as per the controller below. However, the error messages never get displayed.
My GamesController:
class GamesController < ApplicationController
def new
#game = Game.new
#team = #game.teams.build
end
def create
#game = Game.new(game_params)
unless #game.save
render 'new'
return
end
# Some other code that shouldn't run if the save fails, hence the 'return' above
end
end
My form (new.html.erb):
<%= render 'shared/error_messages' %>
<%= form_with model: #game do |f| %>
<%= f.fields_for :teams do |f_teams| %>
<%= f_teams.label :name %>
<%= f_teams.text_field :name, class: 'form-control'%>
<%= f.label :start_time, "Game day" %>
<%= f.date_field :start_time, id: "game_day", class: 'form-control' %>
<%= f.submit "Book now!", class: "btn btn-primary" %>
<% end %>
<% end %>
and finally, the error message partial:
<% if #game.errors.any? %>
<div id="error_explanation">
<div class="alert alertdanger">
The form contains <%= pluralize(#game.errors.count, "error") %>.
</div>
<ul>
<% #game.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
When I deliberately trip up the validations (e.g. by not including the game day) the error message partial doesn't run, presumably because the #game.errors.any? is false.
If I use byebug or if I go throug the rails console, I get the validation errors, e.g. start_time can't be blank.
What am I missing here?
EDIT
Chris's solution below worked for me. However, I wanted my controller to run JS if validations were met and save succeeded. So I went back and removed the suggested local: true and allowed the remote submission to happen. What I did to fix the issue is render html if save didn't succeed:
unless #game.save
respond_to do |format|
format.html { render 'new' }
end
return
end
This didn't work out of the box because turbolinks interferes. I therefore ended up adding gem 'turbolinks_render' to my Gemfile and voila everthing works great now.
Huge shoutout to Joel (https://joelc.io/ajax-ruby-on-rails-forms) for the walkthrough.
In your new.html.erb file, try
<%= form_with(model: #game, local: true) do |f| %>
Some good info for using the form_with tag here:
https://medium.com/#tinchorb/form-with-building-html-forms-in-rails-5-1-f30bd60ef52d

Finding the render route if validation fails in Rails

I am trying to create a basic survey tool as my first Rails project.
At the moment I am working on the validation for submitting a new answer to a survey question. The following is my answer model.
class Answer < ApplicationRecord
belongs_to :question
belongs_to :participant
validates :text, presence: true,
length: { minimum: 5, maximum: 100 }
end
I have set up an if statement that takes you to the next question if passes validation. My problem is I'm not sure what to render inside the else for this statement.
For similar validation in other controllers I have written the render statement to be the pages URL. For example: View all questions + add new question are rendered on the studies/id page. So if question validation fails then render will be 'studies/show'.
The URL to add a new answer looks like this.
http://localhost:3000/studies/20/questions/47/answers/new
For more context here is some of my code:
*Answers Controller*
class AnswersController < ApplicationController
def new
#study = Study.find(params[:study_id])
#question = #study.questions.find(params[:question_id])
#participant = find_participant
#answer = #question.answers.build(participant: #participant)
end
def create
#study = Study.find(params[:study_id])
#question = #study.questions.find(params[:question_id])
#answer = #question.answers.build(answer_params)
if #answer.save
next_question = #question.next_question
redirect_to next_question_path(next_question, #answer) if next_question.present?
else
#I want to render the current page the participant is on to display errors here.
end
end
***some private functions here***
end
* New Answer View *
<div class="wrap">
<h1 class="med-header"><%= #question.question %></h1>
<%= form_with model: #answer, url: study_question_answers_path(#study, #question), local: true do |form| %>
<%= form.hidden_field :participant_id %>
<% #question.answers.each do |answer| %>
<% if answer.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(answer.errors.count, "error") %> prohibited
this answer from being saved:
</h2>
<ul>
<% answer.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% end %>
<%= form.text_area :text %><br>
<%= form.submit %>
<% end %>
</div>
What do I render in the else?
create generates the object and saves. new only generates the object.
If #answer.save fails in this situation,
we need to show the form back to the user. format.html { render :new }
If use JSON you need show errors format.json { render json: #anwser.errors, status: :unprocessable_entity }
Reference : How CURD Work
The cheap way of doing this:
if #answer.save
next_question = #question.next_question
redirect_to next_question_path(next_question, #answer) if next_question.present?
else
flash[:notice] = #answer.errors.full_messages.first
render :new
end
render :new will render the new view as you can infer.
Anecdotally, your next_question_path(next_question, #answer) if next_question.present? is an unlocked gate if there's no next_question :)

Rails, user input persist when showing activerecord error message

Currently, I have text fields and some validation on the model. However, when I show the error message, all the data that was inserted by the user will be gone. I want to show the error message + the data to persist. Here is the code:
<% if #student.errors.any? %>
<div id="validation_error" class="alert alert-danger" role="alert">
<ul>
<% #student.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form_for(:user_student, :url => {:controller => 'profile', :action => 'information'}) do |f| %>
<%= f.text_field :first_name, :value => #student.first_name, class: "form-control" , :placeholder => "First Name" %>
<%= f.text_field :last_name, :value => #student.last_name, class: "form-control" , :placeholder => "Last Name" %>
<% end %>
class Student < ActiveRecord::Base
validates :first_name, :presence => true
validates :last_name, :presence => true
end
UPDATED
Controller:
def information
#student = Student.create(check_new_student_params)
if #student.save
#redirect to other page
end
end
def check_new_student_params
params.require(:user_student).permit(:first_name, :last_name)
end
My expected behaviour: when the user give first name but not last name, it will show the error message and the first name persist on the text field. Thanks
if you have a route to a snew
you should do this
def information
#student = Student.new(check_new_student_params)
if #student.save
#redirect to other page
else
render 'new'
end
end
you're not telling rails to bring back the form after errors. and of course, you should have a new action in your controller.
Here is where your problem is. Your trying to create the object rather than assigning and trying to save. Upon save failing, you should render the page again
def information
#student = Student.new(check_new_student_params)
if #student.save
#redirect to other page
else
render original page again
end
end
def check_new_student_params
params.require(:user_student).permit(:first_name, :last_name)
end

Custom Validation error messages not appearing in view

I have some custom validations in my model for when the user submits a URL using simple_form, but I can't seem to get the error message associated with each custom validation to show in the view (yet the validations seem to work)?
The only error I see is the one defined in the create method. Any guidance would be appreciated....
Model
class Product < ActiveRecord::Base
validates :url, presence: true
validate :check_source, :must_contain_product_id
def check_source
valid_urls = ["foo", "bar"]
errors.add(:url, "Must be from foo or bar") unless valid_urls.any? {|mes| self.url.include? mes}
end
def must_contain_product_id
errors.add(:url, "Must be product page") unless self.url.include? "productID"
end
end
Controller
def create
#product = Product.new
if #product.save
flash[:success] = "Product added to your list"
redirect_to root_path
else
flash[:message] = "Sorry we can't add this product"
redirect_to root_path
end
end
View (using Simple_form)
# Various messaging I've tried
<% if flash[:success].present? %>
<div data-alert class="alert-box success">
<%= flash[:success] %>
</div>
<% end %>
<% if flash[:error].present? %>
<div data-alert class="alert-box">
<%= flash[:error] %>
</div>
<% end %>
<% if flash[:message].present? %>
<div data-alert class="alert-box">
<%= flash[:message] %>
</div>
<% end %>
<% if #product.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#product.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% #product.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
# The actual form...
<%= simple_form_for Product.new do |form| %>
<%= form.input :url, maxlength: false %>
<%= form.button :submit, "Get Product", data: { disable_with: "Retrieving product ..." } %>
<% end %>
I had the same issue solved using:
errors.add(:base, "message here")
However this won't be assigned to a specific form input (e.g url) which was not necessary in my case
You're redirecting to the root_path in the create action regardless of whether or not the save is successful. Your view for the Product form contains divs for the flashes, but unless the view for the root_path renders flash divs as well you won't see them.
I think you neet to redirect to products, or to edit. Otherwise you are unable to see the error, because you are not on the page where it should be displayed.
Try
def create
#product = Product.new
if #product.save
flash[:success] = "Product added to your list"
redirect_to root_path
else
flash[:message] = "Sorry we can't add this product"
render 'edit'
end
end
errors.add(:base, "message")
This is the correct syntax to add message in custom validations.
See your code you are redirecting to root_path if you redirecting means values and variable will not come to your partial.
so you have to change your code like,
def create
#product = Product.new
if #product.save
flash[:success] = "Product added to your list"
redirect_to root_path
else
flash[:message] = "Sorry we can't add this product"
render :action => "new"
end
end
after that in model Instead of
errors.add(:url, "Must be from foo or bar")
Please try like this:
errors[:base] << ("Must be from foo or bar")

Nested form fields_for text_area is not displaying

I have three-tier model:
User has_many Asks has_many Outcomes
On the home page, I would like the user to be able to add an Outcome to their Ask when they mark it complete. I'm trying to use a nested form to display the Outcome description in the Ask form which also updates the done flag and done date.
Like other users/questions here on SO, I cannot get a nested form to display on the screen. I've followed instructions from the other questions, but still the nested field is not displaying. Am wondering if someone can spot the issue in the code below?
Ask Model
class Ask < ActiveRecord::Base
attr_accessible :category, :description, :done, :followed_up,
:helper, :public, :date_done, :date_followed_up, :user_id, :outcomes_attributes
belongs_to :user, counter_cache: true
has_many :outcomes
accepts_nested_attributes_for :outcomes
end
Ask Controller
class AsksController < ApplicationController
def new
#ask = current_user.asks.build(params[:ask])
#ask.outcomes.build
end
def create
#ask = current_user.asks.build(params[:ask])
if #ask.save!
respond_to do |format|
format.html { redirect_to edit_ask_path(#ask) }
format.js
end
else
flash[:error] = "Something is wrong. The Ask was not saved..."
end
end
def edit
#ask = current_user.asks.find(params[:id])
end
def update
#ask = current_user.asks.find(params[:id])
#ask.outcomes.build
#ask.update_attributes(params[:ask])
respond_to do |format|
format.html { redirect_to edit_ask_path(#ask) }
format.js
end
end
end
Home Page Controller (this form is on the home page)
class StaticPagesController < ApplicationController
def home
if signed_in?
#ask = current_user.asks.build(params[:ask])
#ask.outcomes.build
end
end
Form Partial rendered on the home page
<% if current_user.asks.any? %>
<ul id="ask-list-items">
<% current_user.asks.where(done: false).each do |a| %>
<%= form_for(a) do |f| %>
<li><%= a.description %></li>
<%= f.hidden_field :date_done, value: Date.today %>
<%= f.hidden_field :done, :value=>true %>
<%= f.submit "Mark as done", class: "btn btn-small hidden done_btn", id: "a-#{a.id}-done" %>
<%= f.fields_for :outcomes do |builder| %> # << These fields are not showing up
<%= builder.text_area :description, placeholder: "Describe the outcome...", id: "ask-message" %>
<% end %>
<%= f.submit "Save outcome", class: "btn btn-primary" %>
<% end %>
<% end %>
</ul>
<% end %>
When using symbol in form_for and fields_for Rails tries to use an instance variable with he same name, e.g. #outcomes for :outcomes. So try (for existing outcomes):
<% #outcomes = a.outcomes %>
before the line with f.fields_for :outcomes....
And for new outcomes:
<% #outcomes = a.outcomes.build %>
(the last with contribution to the owner of the question)

Resources