Yaya
Yaya

Reputation: 291

How to validate a date string in Rails

OK, so there's a bunch of answers on related topics, but for some reason it seems that no one is interested in the following very-real problem:

I have a Timecard model, with to_date and from_date fields. I'm using jquery.ui.datepicker and generally speaking - all is well. I'm also using the 'validates_timeliness' gem:

class Timecard < ActiveRecord::Base
  ...
  validates_date :to_date
  validates_date :from_date
  ...
end

However, if the user decides to manually edit the text field, entering something like '29-Feb-2013' (which is an invalid date), this date will be converted to '01-Mar-2013'. The following spec will fail:

describe "POST #create" do
  ...
  context "with invalid attributes (invalid date)" do
    it "re-renders the :new template" do
      timecard = fix_date_attribs_for(:timecard_feb)
       # Manually create an invalid date in the params hash - Feb only has 28 days
      timecard['to_date(3i)'] = 29
      post :create, timecard: timecard
      response.should render_template :new
    end
  end
  ...
end

The following puts in timecards_controller.rb:

def create
  @timecard = Timecard.new(timecard_params)
  puts ">>>> #{@timecard.valid?} - #{@timecard.to_yaml}"
  ...
end

yields the following output when running the above spec:

.>>>> true - --- !ruby/object:Timecard
attributes:
  id: 
  name: February 2013
  from_date: 2013-02-01
  to_date: 2013-03-01
  created_at: 
  updated_at: 
F

Failures:

  1) TimecardsController POST #create with invalid attributes (invalid date) re-renders the :new template
     Failure/Error: response.should render_template :new
       expecting <"new"> but rendering with <[]>
     # ./spec/controllers/timecards_controller_spec.rb:65:in `block (4 levels) in <top (required)>'

Finished in 0.28495 seconds
17 examples, 1 failure

How do I do input validation here? Using callbacks in the model seem too far off (since the problem occurs before - when the controller does @timecard = Timecard.new(timecard_params)). I could try to catch a Date.new(relevant_timecard_params) exception in the controller, but it doesn't have access to the errors hash so I wouldn't be able to tell the user it's an invalid date, not to mention that the controller doesn't sound like the right place to do input validation... Please help...

Update 1

With 'validate_timeliness' as @Grantovich suggested below (with the plugin enabled) and save and update fail with invalid dates such as '29-Feb-2013' even without writing validates_date :to_date in the model. When adding validates_date, save and update fail and the validated fields are set to nil even on perfectly valid input (by the validation logic, I suspect).

Also tried specifying the format: validates_date :to_date, format: 'dd-mmm-yyyy' but got the same results.

Upvotes: 0

Views: 1971

Answers (1)

DigitalCora
DigitalCora

Reputation: 2232

The behavior you're seeing comes from the standard Ruby time parser, which validates_timeliness uses by default. You can see this by manually running your example through Time.parse:

irb(main):001:0> Time.parse('29-Feb-2013')
=> 2013-03-01 00:00:00 -0500

This section of the validates_timeliness README says the optional timeliness parser is "more strict than the Ruby parser, which means it won’t accept day of the month if it’s not a valid number for the month". This seems like exactly the behavior you want, so I'd try putting the following in a validates_timeliness.rb initializer (or adding the config line if you already have one):

ValidatesTimeliness.setup do |config|
  config.use_plugin_parser = true
end

Upvotes: 2

Related Questions