When working with date formats and date validations in Rails, I have particular set of behaviours (spelling: a shout out to Peter Cooper) I want to achieve. The code I’ve achieves this behaviour, but it seems to me it should be easier than this.
I include code so that 1) if you have been struggling to achieve these same behaviours, this code provides a solution, and 2) if you’re an old hand at Rails and I am writing more code than I need to, I would *really* appreciate any recommendations you can provide. Also, if any of this code is obnoxiously counter to “idiomatic Ruby/Rails”, meaning it works, but it’s “just not the Ruby/Rails way”, please let me know that as well.
Here is the behaviour I want in validating and formatting dates in Rails:
1) Validate that something has been entered.
2) I want to use the “chronic” gem because I love its ability to parse input such as “today” and “next Tuesday” and return the correct ruby Time object.
3) When editing an record (already has a valid date) and the user enters an invalid date value, or enters blank, I want the application to keep displaying the invalid value so they can see what they keyed and determine why they’re getting an error message. The default Rails behaviour is to redisplay the original, valid value.
4) Finally, I want the dates to be fetched from the database and displayed to the screen using the North American format of “mm/dd/YYYY”.
I created a micro application with one model, “Person” (table = “people”) and only three attributes: first_name, last_name and date_of_birth. In order to achieve all of the behaviour I listed above, I had to create a “virtual attribute” called “date_of_birth_display” which works with the view and use this field for shuttling date values back and forth between the view and the database as well as formatting purposes.
Here’s the important part of the view code:
<table cellspacing=”0″>
<%= form_for(@person) do |f| %>
#… Other fields omitted for brevity…
<td><%= f.label :date_of_birth, “Date Of Birth” %></td>
<td><%= f.text_field :date_of_birth_display %></td>
</tr>
<% end %>
</table>
And now the model code:
require ‘chronic’
class Person < ActiveRecord::Base
validate :date_of_birth_display_is_parseable?
def date_of_birth_display
date_of_birth.strftime(“%m/%d/%Y”) unless date_of_birth.blank?
end
def date_of_birth_display=(val)
@date_of_birth_display = val
end
def date_of_birth_display_is_parseable?
self[:date_of_birth_display] = @date_of_birth_display
if Chronic.parse(@date_of_birth_display)
return self[:date_of_birth] = Chronic.parse(@date_of_birth_display)
end
if @date_of_birth_display.blank?
return !errors.add(:date_of_birth, “cannot be blank.”)
else
return !errors.add(:date_of_birth, “not a valid date.”)
end
end
end