Reputation: 641
I want to verify my user for their registration. I already have their date of birth stored in my database since they were sent over from an API client. I want the user to confirm their date of birth so I set up a Rails confirmation validation. I have in my params date_of_birth_confirmation
and that is what I'm using in my form.
<%= f.label :date_of_birth_confirmation, "DATE OF BIRTH (MM/DD/YYYY)" %>
<%= f.date_select :date_of_birth_confirmation, ...
I am able to submit and get my params but the confirmation validation isn't working. My params come through as
"user" => { "date_of_birth_confirmation(2i)"=>"10", "date_of_birth_confirmation(3i)"=>"5", "date_of_birth_confirmation(1i)"=>"1927" }
which I believe is expected with the date select. From what I understand if it was just the user's date_of_birth and not their dob_confirmation that Rails would convert that to a date object when it was saved in the database.
But when the comparison is made with the actual date_of_birth value it always fails even if they match. I assumed it was because of how the confirmation
validation works. Since date_of_birth_confirmation
is just a string that meant I needed to convert the dob_confirmation into a date so they could be compared so this ended up being my solution
def verification_params
params[:user][:date_of_birth_confirmation] = format_date_of_birth_confirmation
params.require(:user).permit(
:date_of_birth_confirmation
)
end
# Format date_of_birth_confirmation to be a date so the comparison can be made with date_of_birth
def format_date_of_birth_confirmation
year = params[:user].delete("date_of_birth_confirmation(1i)")
month = params[:user].delete("date_of_birth_confirmation(2i)")
day = params[:user].delete("date_of_birth_confirmation(3i)")
return nil unless year.present? && month.present? && day.present?
"#{year}-#{month}-#{day}".to_date
end
This appears to work when I test it. Which is good!
But I wanted to know if there a better solution out there? I'm not entirely sure if there is a more "railsy" way to go about this.
Upvotes: 0
Views: 833
Reputation: 641
I ended up changing the controller logic to instead put the logic of formatting the date_of_birth_confirmation in the model which is probably where it better fits.
# User.rb
before_validation :format_date_of_birth_confirmation
def format_date_of_birth_confirmation
return if date_of_birth_confirmation.blank?
return if date_of_birth_confirmation.is_a?(Date)
year = date_of_birth_confirmation[1]
month = date_of_birth_confirmation[2]
day = date_of_birth_confirmation[3]
return unless year && month && day
assign_attributes(date_of_birth_confirmation: "#{year}-#{month}-#{day}".to_date)
end
Upvotes: 0
Reputation: 4136
The short answer is no, there's not a more direct 'rails-y' way than what you are doing since the date_select always just returns the raw selected values as independent strings.
Upvotes: 1
Reputation: 165606
Use validations.
First, validate that date_of_birth
is a Date, not blank, and a reasonable one. I use validates_timeliness.
class User < ApplicationRecord
# Validate the actual date of birth.
validates :date_of_birth,
confirmation: true,
presence: true,
timeliness: {
type => :date,
# They're not from the future.
on_or_before: lambda { Date.current },
# They're not 120.
after: '1900-01-01'
}
end
Then make a Form model to validate and process the form input. By including ActiveModel::Model we get a class that can do many of the same things as a Record, but there's no table.
class DateForm
include ActiveModel::Model
attr_accessor :year, :month, :day
validates :year, :month, :day
# They must be positive integers
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1 }
def to_date
Date.new(year, month, day)
end
end
Then you can use DateForm in your controller to validate the form and pass the date along to the User.
def new
@birth_date = DateForm.new
@birth_date_confirmation = DateForm.new
@user = User.new
end
def create
@user = User.new(params[:user])
@birth_date = DateForm.new(
year = params[:birth_date]["date_of_birth(1i)"]
month = params[:birth_date]["date_of_birth(2i)"]
day = params[:birth_date]["date_of_birth(3i)"]
)
@birth_date_confirmation = DateForm.new(
year = params[:birth_date_confirmation]["date_of_birth_confirmation(1i)"]
month = params[:birth_date_confirmation]["date_of_birth_confirmation(2i)"]
day = params[:birth_date_confirmation]["date_of_birth_confirmation(3i)"]
)
render 'new' if !@birth_date.valid? || !@birth_date_confirmation.valid?
@user.date_of_birth = @birth_date.to_date
@user.date_of_birth_confirmation = @birth_date_confirmation.to_date
if @user.save
redirect_to @user
else
render 'new'
end
end
And use @birth_date and @birth_date_confirmation in your view as their own forms.
form_for(@birth_date, as: :birth_date do |f|
...
end
form_for(@birth_date_confirmation, as: :birth_date_confirmation) do |f|
...
end
This also gives you a model you can easily unit test making your controller tests simpler.
Upvotes: 0