1

I'm basically attempting to implement the solution from Railscast #32, modernized for Rails 3.0.7

http://railscasts.com/episodes/32-time-in-text-field

class Task < ActiveRecord::Base

attr_accessible :description, :complete, :deadline

validate :deadline_string_no_errors


def deadline_string
  self.deadline.to_s
end

def deadline_string=(deadline_str)
  unless deadline_str.blank?
    begin
      self.deadline = Chronic.parse(deadline_str)
    rescue
      self.deadline = Time.parse(deadline_str)
    rescue
      @deadline_invalid = true
    end
  end
end

def deadline_string_no_errors
  errors.add(:deadline_string, "Is Invalid") if @deadline_invalid
end

I want to set a validation error when a string, such as 'foobar' is put into #deadline_string=, either by console or (intended) form entry.

What I'm particularly concerned about is a more strict formatting of time, Chronic.parse('2011-05-25 08:19:00 UTC') Which throws a TypeError. In that case I want to fallback to Time.parse, which can understand this formatting.

This alternative form also does not work with deadline_string="foobar" as the initial call.

# snip
def deadline_string=(deadline_str)
  unless deadline_str.blank?
    self.deadline = ( parse_time(deadline_str, Chronic) || parse_time(deadline_str, Time) )
  end
end

private

def parse_time(string, parser)
  begin
    parser.parse(string)
  rescue
    @deadline_invalid = true
  end
end

def deadline_string_no_errors
  #snip
end

No matter what I try it doesn't ever seem to get to the second rescue. There is also some strangeness around the deadline attribute, which is specified in the schema as a datetime.

The solution in the end was something like this, but it could probably stand to some refactoring. I'm anxious about setting self.deadline in the method call, but not sure why I should be.

Class Task < ActiveRecord::Base
  attr_accessible: description, :complete, :deadline

  validate :deadline_string_no_errors

  def deadline_string
    self.deadline.to_s
  end

  def deadline_string=(deadline_str)
    unless deadline_str.blank?
      self.deadline = ( parse_time(deadline_str, Chronic) || parse_time(deadline_str, Time) )
      @deadline_invalid = true if self.deadline.nil?
    end
  end

  private

  def parse_time(string, parser)
    begin
      parser.parse(string)
    rescue
    end
  end

  def deadline_string_no_errors
    errors.add(:deadline_string, "Is Invalid") if @deadline_invalid
  end

Refactoring is welcome, and will be marked as an answer.

Carl Thuringer
  • 146
  • 1
  • 7

1 Answers1

1

A few of things:

$ Chronic.parse 'foo'

yields nil and not an exception.

$ Time.parse 'foo'

yields a Time object, not an exception. (Edit: In Ruby 1.8.7)

When you use a rescue, you need to specify the type of exception that you're rescuing

rescue SyntaxError, NameError => boom
monocle
  • 5,866
  • 2
  • 26
  • 22
  • Trying out `Time.parse 'foo'` it seems that it returns an ArgumentError, but you're correct about `Chronic.parse('foo')` returning nil. What I'm particularly concerned about is a more strict formatting of time, `Chronic.parse('2011-05-25 08:19:00 UTC')` Which throws a TypeError. – Carl Thuringer May 25 '11 at 13:49
  • Actually, you are not required to specify the type of exception that you are rescuing from. By not providing any exception you are rescuing from StandardError exception and all of its subclasses. – Pikachu Jun 06 '14 at 21:59