Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 363
Dave Avatar asked Dec 26 '16 16:12

Dave


5 Answers

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.

like image 126
Max Avatar answered Nov 14 '22 23:11

Max


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
like image 22
Doug Avatar answered Nov 15 '22 00:11

Doug


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'
like image 35
Mat Avatar answered Nov 15 '22 00:11

Mat


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
....
like image 40
Dhurba Baral Avatar answered Nov 14 '22 23:11

Dhurba Baral


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!

like image 27
Greg Navis Avatar answered Nov 14 '22 23:11

Greg Navis