The problem has to do with multiparameter assignment. Basically you want to store three values into one attribute (ie. written_at
). The date_select sends this as { 'written_at(1)' => '2009', 'written_at(2)' => '5', 'written_at(3)' => '27' }
to the controller. Active record packs these three values into a string and initializes a new Date object with it.
The problem starts with the fact that Date raises an exception when you try to instantiate it with an invalid date, Date.new(2009, 0, 1)
for instance. Rails catches that error and instantiates a Time object instead. The Time class with timezone support in Rails has all kinds of magic to make it not raise with invalid data. This makes your day turn to 1.
During this process active record looses the original value hash because it packed the written_at
stuff into an array and tried to create a Date or Time object out of it. This is why the form can't access it anymore using the written_at_before_time_cast
method.
The workaround would be to add six methods to your model: written_at_year
, written_at_year=
, and written_at_year_before_type_cast
(for year, month and day). A before_validation
filter can reconstruct the date and write it to written_at.
class Event < ActiveRecord::Base
before_validation :reconstruct_written_at
def written_at_year=(year)
@written_at_year_before_type_cast = year
end
def written_at_year
written_at_year_before_type_cast || written_at.year
end
def written_at_year_before_type_cast
@written_at_year_before_type_cast
end
private
def reconstruct_written_at
written_at = Date.new(written_at_year, written_at_month, written_at_day)
rescue ArgumentError
written_at = nil
end
end