Tuesday, December 16, 2008

Date validation in Ruby using the Date object

Ruby has a date library to help you manage and manipulate dates. The date library provides Date and Date/Time objects that can be used to validate dates. In addition to straight Ruby, this helps work around a particularly nasty problem in version 1.x scaffolding date fields created by Rails.

The Date object

To use the date library, add this line near the top of your Ruby program:

require 'date'

The date library provide the Date object and the DateTime object, which adds time attributes to the Date object. To create a new Date object, pass the year, month, and day to the new Date constructor:

mydate = Date.new(2008, 7, 11)

To get the current date, use the Date.now method. You can access different attributes of a date object like this:

mydate.year
mydate.month
mydate.day

Converting a date object to a string returns a "YYYY-MM-DD" format. For example:

mydate.to_s
"2008-07-11"

A simple validate function

One way to test for a valid date is to try to create a Date object. If the object is created, the date is valid, and if not, the date is invalid. Here is a function that accepts year, month, and day, then returns true if the date is valid and false if the date is invalid.

  def test_date(yyyy, mm, dd)
begin
@mydate = Date.new(yyyy, mm, dd)
return true
rescue ArgumentError
return false
end
end

The creation of the date object is wrapped in a begin...rescue...end block so that the error can be trapped if the date is invalid. Ruby throws an ArgumentError if the date is invalid and in that case, the function returns false. This version uses an instance variable (@mydate) because I wrote it to be used in a Rails application.

The nasty date problem in 1.x Rails scaffolding

In version 1.x of Rails scaffolding, date fields in the edit view are generated with three drop down boxes, one for the year, one for the month, and one for the day. However, you are free to select invalid month/day combinations like February 30 and November 31. When these parameters are passed to the Rails update function in a scaffolding generated controller, it fails.

The crude solution I used to work around it was to edit the date prior to the attempt to save the record. In this case, the view sends the year to the controller in the dob(1i) field, the month in dob(2i), and the day in dob(3i). I convert them to integers, then call the function above to validate the date. If the date is invalid, I set a flash message and return to the edit screen.

    # date validation code
@allparms = params[:dependent]
@yyyy = @allparms["dob(1i)"].to_i
@mm = @allparms["dob(2i)"].to_i
@dd = @allparms["dob(3i)"].to_i

if test_date(@yyyy, @mm, @dd) == false
flash[:error] = "Invalid date " + @mm.to_s + "/" + @dd.to_s + "/" + @yyyy.to_s
redirect_to :action => 'edit', :id => params[:id]
return
end
It seems like this rather basic problem would have been worked out in the Rails scaffolding, but it wasn't. I haven't tested Rails 2.0 scaffolding to see if it has similar issues. Other people have come up with more comprehensive solutions, like the Rails date kit. Take a look at something like that if you want more than a quick and dirty solution.