Validation messages after redirect - ruby-on-rails

We have a form to submit ratings for a certain restaurant in a in our views/restaurants/show.html.erb. If there is a validation error we get redirected back to views/restaurants/show.html.erb but the validation messages do not appear. We figured out that this happens because we lose the messages using redirect_to(#restaurant) in our RatingController create action. But how can we get back without redirection?
Thanks!

Here is how I solved this. (Note that in what follows I'm obviously just including just the most relevant lines.)
In the model there may be multiple validations and even methods that potentially report on multiple errors.
class Order < ActiveRecord::Base
validates :name, :phone, :email, :presence => true
def some_method(arg)
errors.add(:base, "An error message.")
errors.add(:base, "Another error message.")
end
end
As well, the controller action may set flash messages. Finally, the user may have entered data into the input fields, and we want it to persist through the redirect_to too.
class OrdersController < ApplicationController
def create
#order = Order.new(params[:order])
respond_to do |format|
if #order.save
session.delete(:order) # Since it has just been saved.
else
session[:order] = params[:order] # Persisting the order data.
flash[:notice] = "Woohoo notice!" # You may have a few flash messages
flash[:alert] = "Woohoo alert!" # as long as they are unique,
flash[:foobar] = "Woohoo foobar!" # since flash works like a hash.
flash[:error] = #order.errors.to_a # <-- note this line
format.html { redirect_to some_path }
end
end
end
end
Depending on your setup, you may or may not need to save the model data, such as order, to the session. I did this for the purpose of passing the data back to the original controller, and thereby being able to setup the order there again.
In any case, to display the actual error and flash messages, I did the following (in views/shared/_flash_messages.html.erb, but you could do it in application.html.erb or wherever else makes sense for your app). And this is thanks to that line flash[:error] = #order.errors.to_a
<div id="flash_messages">
<% flash.each do |key, value|
# examples of value:
# Woohoo notice!
# ["The server is on fire."]
# ["An error message.", "Another error message."]
# ["Name can't be blank", "Phone can't be blank", "Email can't be blank"]
if value.class == String # regular flash notices, alerts, etc. will be strings
value = [value]
end
value.each do |value| %>
<%= content_tag(:p, value, :class => "flash #{key}") unless value.empty? %>
<% end %>
<% end %>
</div><!-- flash_messages -->
To be clear, regular flash messages such as notices, alerts, etc. will be strings, however errors will be arrays since the above call was errors.to_a

You can pass your error on flash message
flash[:error] = #restaurant.errors
After you need display it in your redirect

Here is how I did it still doing the redirect:
Just before you redirect on validation errors in your controller store errors to flash like suggested by #shingara:
if #restaurant_rating.save
redirect_to #restaurant, :notice => "Successfully added rating to restaurant."
else
flash[:error] = #restaurant_rating.errors
redirect_to #restaurant, :alert => "There were errors to add rating to restaurant. "
end
Then in your form for rating you assign errors back for the rating object just before rendering the form:
- flash[:error].messages.each {|error| #restaurant_rating.errors.add(error[0], error[1][0]) } if flash[:error]
= simple_form_for #restaurant_rating do |f|
....

You can use render instead of redirect_to
render :action => "show"
or set flash[:error], flash[:notice] again, because they automatically reseted

After your clarification in the comment, you need to setup your
/app/views/layouts/application.html.erb
with this line
<%- flash.each do |name, msg| -%><%= content_tag :div, msg, :id => "flash_#{name}" %><%- end -%>

Related

Name attribute is coming up Nil?

I am trying to create a destination, but it keeps telling me in my browser that 'name' is nil when it redirects redirects to my 'show' view.
Error I receive
undefined method `name' for nil:NilClass
Here are my controller actions for new, create, and show:
def show
#destination = Destination.find_by(id: params[:id])
end
def new
#destination = Destination.new
end
def create
#destination = Destination.create(dest_params)
redirect_to user_destination_path(current_user, #destination.id )
end
private
def dest_params
params.require(:destination).permit(:name,:user_id)
end
My new form where I enter the name of the destination:
<h2>Add a destination</h2>
<div>
<%= form_for #destination do |f|%>
<%= f.label :name %>
<%= f.text_field :name %><br>
<%= f.submit %>
<% end %>
</div>
here is my read/show view:
<h3>Added destination</h3>
<div>
<p><%= #destination.name %></p>
</div>
Before all this I was getting missing required keys [:id] errors, but I seemed to fix that but for some reason I suspect that might have something to do with the issue I am having now. Let me know if you are able to spot the issue
Updated Error
No route matches {:action=>"show", :controller=>"destinations", :id=>nil, :user_id=>"1"}, missing required keys: [:id]
The main problem here is a total lack of error handling. You're not checking at all if the user provided valid input or if the record was even saved in your create method.
def create
#destination = Destination.create(dest_params)
redirect_to user_destination_path(current_user, #destination.id )
end
If the record is not saved for example due to a failed validation #destination.id is nil.
In your show method you're using find_by instead of find which just lets the error slide instead of raising a ActiveRecord::RecordNotFound error.
Your controller should actually look like:
class DestinationsController
def show
# will raise if the record is not found and return a 404 not found response
# instead of just exploding
#destination = Destination.find(params[:id])
end
def new
#destination = Destination.new
end
def create
# never just assume that the record is created unless you want
# to get kicked in the pants.
#destination = Destination.new(dest_params)
if #destination.save
# this route really does not need to be nested.
# see https://guides.rubyonrails.org/routing.html#shallow-nesting
redirect_to user_destination_path(current_user, #destination)
else
# re-render the form with errors
render :new
end
end
private
def dest_params
params.require(:destination).permit(:name,:user_id)
end
end

How do I display a validation error properly if my date format is not correct in Rails?

I’m using Rails 4.2.7. I would like to throw a validation error if a user doesn’t enter their date of birth field in the proper format, so I have
def update
#user = current_user
begin
#user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
rescue ArgumentError => ex
end
if #user.update_attributes(user_params)
and I have this in my view
<%= f.text_field :dob, :value => (f.object.dob.strftime('%m/%d/%Y') if f.object.dob), :size => "20", :class => 'textField', placeholder: 'MM/DD/YYYY' %>
<% if #user.errors[:dob] %><%= #user.errors[:dob] %><% end %>
However, even if someone enters a date like “01-01/1985”, the above doesn’t return a validation error to the view. What do I need to do to get the validation error to be returned properly?
Edit: Per one of the answers given, I tried
#user = current_user
begin
#user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
rescue ArgumentError => ex
puts "Setting error."
#user.errors.add(:dob, 'The birth date is not in the right format.')
end
if #user.update_attributes(user_params)
last_page_visited = session[:last_page_visited]
if !last_page_visited.nil?
session.delete(:last_page_visited)
else
flash[:success] = "Profile updated"
end
redirect_to !last_page_visited.nil? ? last_page_visited : url_for(:controller => 'races', :action => 'index') and return
else
render 'edit'
end
And even though I can see the "rescue" branch called, I'm not directed to my "render 'edit'" block.
Triggering an exception doesn't add anything to the errors list. If you just want to tweak this code slightly, you should be able to call errors.add inside the rescue block. Something like #user.errors.add(:dob, 'some message here').
Keep in mind that this will only validate the date of birth when using this controller method. If you want to validate the date of birth whenever the user is saved, you'll want to explicitly add the validation to the model. You can write your own custom validation class or method, and there are also some gems that add date validation.
Calling update_attributes clears out the errors that you set in the rescue. You should check for errors, and if none, then continue on, something like this:
#user = current_user
begin
#user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
rescue ArgumentError => ex
puts "Setting error."
#user.errors.add(:dob, 'The birth date is not in the right format.')
end
if !#user.errors.any? && #user.update_attributes(user_params)
last_page_visited = session[:last_page_visited]
if !last_page_visited.nil?
session.delete(:last_page_visited)
else
flash[:success] = "Profile updated"
end
redirect_to !last_page_visited.nil? ? last_page_visited : url_for(:controller => 'races', :action => 'index') and return
end
render 'edit'
Since you redirect_to ... and return you can close out the conditional and, if you make it this far, simply render the edit page.
You may also want to add a simple validation to your user model:
validates :dob, presence: true
This will always fail if the dob can't be set for some other, unforseen, reason.
To get the user entered string to populate the field on re-load, you could add an accessor to the user model for :dob_string
attr_accessor :dob_string
def dob_string
dob.to_s
#dob_string || dob.strftime('%m/%d/%Y')
end
def dob_string=(dob_s)
#dob_string = dob_s
date = Date.strptime(dob_s, '%m/%d/%Y')
self.dob = date
rescue ArgumentError
puts "DOB format error"
errors.add(:dob, 'The birth date is not in the correct format')
end
Then change the form to set the :dob_string
<%= form_for #user do |f| %>
<%= f.text_field :dob_string, :value => f.object.dob_string , :size => "20", :class => 'textField', placeholder: 'MM/DD/YYYY' %>
<% if #user.errors[:dob] %><%= #user.errors[:dob] %><% end %>
<%= f.submit %>
<% end %>
And update the controller to set the dob_string:
def update
#user = User.first
begin
##user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
#user.dob_string = user_params[:dob_string]
end
if ! #user.errors.any? && #user.update_attributes(user_params)
redirect_to url_for(:controller => 'users', :action => 'show') and return
end
render 'edit'
end
def user_params
params.require(:user).permit(:name, :dob_string)
end
I would add a validation rule in the model. Like:
validates_format_of :my_date, with: /\A\d{2}\/\d{2}\/\d{4}\z/, message: 'Invalid format'
Try adding validation rule in model.
validate :validate_date
def validate_date
begin
self.dob = Date.parse(self.dob)
rescue
errors.add(:dob, 'Date does not exists. Please insert valid date')
end
end
and in your controller update your code
...
#user.update_attributes(user_params)
if #user.save
....
I think this is a case where Active Model shines. I like to use it to implement form objects without extra dependencies. I don't know the exact details of your situation but below I pasted a small demo that you should be able to adapt to your case.
The biggest benefit is that you don't pollute your controllers or models with methods to support profile updates. They can be extracted into a separate model which simplifies things.
Step 1: Store dob in users
Your users table should have a column dob of type date. For example:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name, null: false
t.date :dob, null: false
end
end
end
Don't put anything fancy in your model:
class User < ActiveRecord::Base
end
Step 2: Add Profile
Put the following in app/models/profile.rb. See comments for explanations.:
class Profile
# This is an ActiveModel model.
include ActiveModel::Model
# Define accessors for fields you want to use in your HTML form.
attr_accessor :dob_string
# Use the validatiors API to define the validators you want.
validates :dob_string, presence: true
validate :dob_format
# We store the format in a constant to keep the code DRY.
DOB_FORMAT = '%m/%d/%Y'
# We store the user this form pertains to and initialize the DOB string
# to the one based on the DOB of the user.
def initialize(user)
# We *require* the user to be persisted to the database.
fail unless user.persisted?
#user = user
#dob_string = user.dob.strftime(DOB_FORMAT)
end
# This method triggers validations and updates the user if validations are
# good.
def update(params)
# First, update the model fields based on the params.
#dob_string = params[:dob_string]
# Second, trigger validations and quit if they fail.
return nil if invalid?
# Third, update the model if validations are good.
#user.update!(dob: dob)
end
# #id and #persisted? are required to make form_for submit the form to
# #update instead of #create.
def id
#user.id
end
def persisted?
true
end
private
# Parse dob_string and store the result in #dob.
def dob
#dob ||= Date.strptime(dob_string, DOB_FORMAT)
end
# This is our custom validator that calls the method above to parse dob_string
# provided via the params to #update.
def dob_format
dob
rescue ArgumentError
errors[:dob] << "is not a valid date of the form mm/dd/yyyy"
end
end
Step 3: Use the form in the controller
Use Profile in ProfilesController:
class ProfilesController < ApplicationController
def edit
# Ensure #profile is set.
profile
end
def update
# Update the profile with data sent via params[:profile].
unless profile.update(params[:profile])
# If the update isn't successful display the edit form again.
render 'edit'
return
end
# If the update is successful redirect anywhere you want (I chose the
# profile form for demonstration purposes).
redirect_to edit_profile_path(profile)
end
private
def profile
#profile ||= Profile.new(user)
end
def user
#user ||= User.find(params[:id])
end
end
Step 4: Render the form with form_for
In app/views/profiles/edit.html.erb use form_for to display the form:
<%= form_for(#form) do |f| %>
<%= f.label :dob_string, 'Date of birth:' %>
<%= f.text_field :dob_string %>
<%= f.submit 'Update' %>
<% end %>
Step 5: Add routing
Keep in mind to add routing to config/routes.rb:
Rails.application.routes.draw do
resources :profiles
end
That's it!

Manually Send Email Rails

I've been trying to implement the code from this question: Send an email manually of a specific page in rails app
The only difference is that I need to fetch the email address from my model. This is usually no problem when sending emails from models.
UserMailer.report(self).deliver
But I want to click on a button in the show view of my record.
I need to manually send out emails using the details of the record in the email.
Maybe there is a better approach than using an extra controller for this?
# app/mailers/user_mailer.rb
class UserMailer < ActionMailer
def report(thing)
#thing = thing
mail :to => thing.email, :from => 'you#example.com',
:subject => 'that report you want'
end
end
# app/views/user_mailer/report.html.erb
<h1>Report</h1>
<p>Here is your <% #thing.customer_id %></p>
# app/controllers/reports_controller.rb
def create
UserMailer.report(#thing).deliver
flash[:notice] = 'report sent!'
redirect_to root_path # or wherever
end
# in a view
<% form_tag(reports_path(#thing), :method => :post) do %>
<% submit_tag 'send report email' %>
<% end %>
I'm returning null with the code above:
ArgumentError (wrong number of arguments (0 for 1)):
app/controllers/reports_controller.rb:3:in `create'
Create is a post request in rails, you cannot pass parameter like this you need to fetch from params. I'm seeing you are giving it a parameter which is wrong.
Secondly you are doing #thing = thing and then you are sending thing (without #) to report method of UserMailer which is also wrong, it would be be nil in report method. you should do UserMailer.report(#thing).deliver after #thing is an object which has email

Ruby on Rails, find if a certain value exists in a column

I'm building a website with user authentication. And I just noticed that if I create a user with an existing email, it just doesn't work which is normal, but I'd like to give feedback to the user. So for that I need to determine if any user has already that email.
I've tried some things like:
if User.email.include? params[:user][:email]
flash.now[:error] = "A user with this password already exists"
render :action => :new, :layout => 'signin-layout.html.erb'
Those are the columns for User:
2.1.0 :014 > User.column_names
=> ["id", "name", "email", "created_at", "updated_at", "password_digest", "remember_token", "admin", "team_id", "teamLeader"]
And the result I get is a big fat error:
undefined method `email' for #<Class:0x00000102b9a908>
So if anybody sees what I'm doing wrong, or knows another way to do it, that would be great.
Cheers
Try this:
if User.exists?(:email => params[:user][:email])
flash.now[:error] = "A user with this password already exists"
render :action => :new, :layout => 'signin-layout.html.erb'
...
else
# more code here..
end
Also, you can add validations when you're creating the object:
class User
validates_uniqueness_of :email
More on different validations here: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
I believe this way of doing the validation is wrong, you should validate the uniqueness of the email in the User model itself like below
validates :email, uniqueness: true #User model
This way the validation would be on the the User model. The problem with the condition you are using is that it is accessing an instance method specific to objects as a class method. So User.email means that there is a method called email that has the same logic for all the instances of the user class or more formally a class method which you don't have here. The email is an attribute specific to each user an instance attribute/variable (Each user has a different email).
You can see/show the validation errors present on the model using #user.errors.full_messages where #user is the instance you are trying to register/save.
This is how I would normally do it if this action is for registering users i.e. creating new users.
class User < ActiveRecord::Base
#attribute accessors and accessible
validates :email, uniqueness: true
end
class UsersController < ApplicationController
def create
#user = User.new params[:user]
if #user.save
#code for redirect or rendering the page you want
else
render 'new'
end
end
end
#new.html.erb
<%= form_for #user do |f| %>
<% if #user.errors.any? %>
<div>
<ul>
<% #job.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
#form fields
<% end %>
This way you display all the error messages to the user at the top of the registration form.

Rails: Easy way to add more than one flash[:notice] at a time

I thought every time you do a flash[:notice]="Message" it would add it to the array which would then get displayed during the view but the following just keeps the last flash:
flash[:notice] = "Message 1"
flash[:notice] = "Message 2"
Now I realize it's just a simple hash with a key (I think :)) but is there a better way to do multiple flashes than the following:
flash[:notice] = "Message 1<br />"
flash[:notice] << "Message 2"
I usually add such methods to my ApplicationHelper:
def flash_message(type, text)
flash[type] ||= []
flash[type] << text
end
And
def render_flash
rendered = []
flash.each do |type, messages|
messages.each do |m|
rendered << render(:partial => 'partials/flash', :locals => {:type => type, :message => m}) unless m.blank?
end
end
rendered.join('<br/>')
end
And after it is very easy to use them:
You can write something like:
flash_message :notice, 'text1'
flash_message :notice, 'text2'
flash_message :error, 'text3'
in your controller.
And just add this line to your layout:
<%= render_flash %>
The flash message can really be anything you want, so you could do something like this:
flash[:notice] = ["Message 1"]
flash[:notice] << "Message 2"
And then in your views, output it as
<%= flash[:notice].join("<br>") %>
Or whatever you prefer.
Whether that technique is easier than other solutions is determined by your own preferences.
I think the idea built into the framework is that every message you stick into flash is over-writeable. You give each message a unique key so you can change or overwrite it.
If you need another message, don't call it ":notice." Call each something unique. Then to render the flash messages, loop through whatever is in the hash.
If this doesn't work for you, consider whether you actually need to simplify your UI.
Although I agree with Jonathan in that the UI might need some simplification, there are instances where you might want to display multiple messages for the same key.
As a result, I have created a gem that (should) make it easy to deal with multiple flash messages in the same key and their rendering in the view.
GitHub:
flash-dance
On Rails 4.1 this will not work. I have a different implementation, so I think your code should be changed this way:
def render_flash
rendered = []
flash.keys do |type|
flash[type].each do |m|
rendered << render(:partial => 'partials/flash', :locals => {:type => type, :message => m}) unless m.blank?
end
end
rendered.join('<br/>')
end
A method on how to use both 'traditional' one-liner messages and array-style for messages with line breaks.
With Rails 4.1 and the JSON serializer, you can use one-liner flash messages
flash[:notice] = 'message'
and multi-line flash messages
flash[:notice] = ['message line 1', 'message line 2']
like so (in application.html.haml or the appropriate template file)
- if notice
#css_class
= safe_join(Array(notice), '<br/>'.html_safe)
If you want to add flash messages in your application you just need to flow these steps.
Add a _flash_messages.html.erb file and add this code in it.
<% flash.each do |type, message| %>
<div class="alert <%= bootstrap_class_for(type) %> fade in">
<button class="close" data-dismiss="alert">×</button>
<%= message %>
</div>
<% end %>
And now call it in application.html.erb like add this line in application.html.erb
<%= render partial: "shared/flash_messages", flash: flash %>
now add this code in application.helper.rb
module ApplicationHelper
def bootstrap_class_for flash_type
case flash_type
when :success
"alert-success"
when :error
"alert-error"
when :alert
"alert-block"
when :notice
"alert-info"
else
flash_type.to_s
end
end
end
This will work for you hopefully.
Bootstap 4, slim
.container.mt-3.full-max-width-down-md
- flash.each do |key, value|
div{class="alert alert-#{key} alert-dismissible fade show text-center" role="alert"}
button type="button" class="close" data-dismiss="alert" aria-label="Close"
i class="fa fa-times" aria-hidden="true"
- if value.is_a? Array
= safe_join(value, "<br />".html_safe)
- else
= value
Example
flash[:notice] = "Hey"
flash[:notice] = ["Hey", "Hey"]
redirect_to my_path, flash: { notice: "Hey" }
redirect_to my_path, flash: { notice: ["Hey", "Hey"] }
Easy way to do multiple flash messages at a time without too much rigamarole, using this answer as inspiration:
flash[:notice] = "#{flash[:notice]} Message 1<br />"
flash[:notice] = "#{flash[:notice]} Message 2"
Or
flash[:notice] = flash[:notice].to_s + 'Message 1<br />'
flash[:notice] = flash[:notice].to_s + 'Message 2'
Quote from the above-referenced answer:
This works because NilClass defines a #to_s method that returns a zero-length String, and because instance variables are automatically initialized to nil.
Depending on circumstances, though not in the context of this posted question, you might have to deal with a trailing break tag or comma or whatever "delimeter" you're using. Which you could deal with a number of ways like chomp

Resources