Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Validating time in rails

My app has the user input two times, and I calculate the amount of time between them as a convenience for the user. So the user enters 1:00 AM as the start time and 2:00 AM as the stop time, it will output "1 hour". I have a helper method that does this, using a gem for the heavy lifting. The times are stored as a Time data type in the database in order for that to work.

That all works fine, and now I want to validate that the time the user enters is valid - but also NOT require it. So if the user enters nothing for those times, the record still saves - but if the user enters, say "asdfasdf", I want the validations to get triggered and say that it is an invalid time, rather than just silently not storing the time.

I created my own custom time validation like so:

class TimeValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    Time.parse(value.to_s)
  rescue ArgumentError
    record.errors[attribute] << (options[:message] || "is not a valid time")
  end
end

Then in my model, I'm doing:

validates :start_time, time: true
validates :stop_time, time: true

This does what I want as far as making sure the time can be correctly parsed, at it works pretty well. The user could enter 14:00, 3:00 PM, etc...and Rails can parse it. If they enter "asdfasdf", or any value that cannot be parsed as a valid time, the user is notified via a form validation error and the record is not saved.

But as that code stands, it will also trigger the validation error if the value is left blank.

So again, I want it to pass validations with a nil value, but fail if there is some value that cannot be parsed.

I tried:

validates :start_time, time: true, allow_blank: true

but then "asdfasdf" will pass. I also tried

Time.parse(value.to_s) unless value.nil?

but again, that breaks the whole thing and allows invalid dates to pass.

Any ideas?

like image 388
Greg Blass Avatar asked Oct 19 '25 16:10

Greg Blass


1 Answers

I have to admit first that I am not a Rails/Ruby expert.

I had the very same Problem today and I think i figured it out. The Problem is that Rails performs a type cast. If you enter an invalid parameter (e.g. starttime="asdf"), a dummy Time object will be created. When you validate it, it seems as everything went okay although it clearly didn't. You can access the "real" parameter value with :starttime_before_type_cast.

I adjusted Yule's code accordingly:

class TimeValidator < ActiveModel::EachValidator

  def validate_each(record, attribute, value)
    return true unless value
    begin
      Time.parse(value.to_s)
    rescue ArgumentError
      y = attribute.to_s
      y.slice! "_before_type_cast"
      record.errors[y] << (options[:message] || "invalid time format entered")
    end
  end

end

The validation in the model should look something like this:

validates :starttime, presence: true
validates :starttime_before_type_cast, time: true

The only snag with this approach is that the field in the form will contain the time 00:00 and not the malformed value after validation. I.e. The user won't see what he entered and what lead to the error message.

An article about Rails Type Casting

like image 143
iamhubi Avatar answered Oct 21 '25 05:10

iamhubi