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 [email protected]? && @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.
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
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
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
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 %>
Keep in mind to add routing to config/routes.rb
:
Rails.application.routes.draw do
resources :profiles
end
That's it!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With