views:

579

answers:

2

I believe that datetime_select is black magic. What I'm really trying to figure out is the whole 1i,2i,3i,4i... multiple parameters stuff. Specifically how is it handled in the back end (activerecord, something else?). What's with the 'i' after the order-number? Is it a type-specifier? If so what are other types that are available? I've read the source of date_helper.rb and it's quite opaque.

Here's my motivation:

I've got a :datetime column in my model and I want to input in the view via two text_fields: one for date and one for time. They need to be validated, merged together, then stored into the datetime column. Ultimately I'll be using a javascript calendar to input dates into the date field.

So has anybody done this? I tried using virtual attributes (incredibly undocumented besides the rudimentary railscast) and the issue was that when a new activerecord object is created and has nil attributes, the virtual attribute fails (undefined method strftime for nil class, which makes sense).

Anybody have any suggestions or best practices? Thanks!

A: 

Here's what I ended up coming up with. If anyone has any comments, let me know. I'd love to get feedback.

  validate :datetime_format_and_existence_is_valid  
  before_save :merge_and_set_datetime   

  # virtual attributes for date and time allow strings
  # representing date and time respectively to be sent
  # back to the model where they are merged and parsed
  # into a datetime object in activerecord
  def date
    if (self.datetime) then self.datetime.strftime "%Y-%m-%d"
    else @date ||= (Time.now + 2.days).strftime "%Y-%m-%d" #default
    end
  end
  def date=(date_string)
    @date = date_string.strip
  end
  def time
    if(self.datetime) then self.datetime.strftime "%l:%M %p"
    else @time ||= "7:00 PM" #default
    end
  end
  def time=(time_string)
    @time = time_string.strip
  end

  # if parsing of the merged date and time strings is
  # unsuccessful, add an error to the queue and fail
  # validation with a message
  def datetime_format_and_existence_is_valid    
    errors.add(:date, 'must be in YYYY-MM-DD format') unless
      (@date =~ /\d{4}-\d\d-\d\d/) # check the date's format
    errors.add(:time, 'must be in HH:MM format') unless # check the time's format
      (@time =~ /^((0?[1-9]|1[012])(:[0-5]\d){0,2}(\ [AaPp][Mm]))$|^(([01]\d|2[0-3])(:[0-5]\d){0,2})$/)
    # build the complete date + time string and parse
    @datetime_str = @date + " " + @time
    errors.add(:datetime, "doesn't exist") if 
      ((DateTime.parse(@datetime_str) rescue ArgumentError) == ArgumentError)
  end

  # callback method takes constituent strings for date and 
  # time, joins them and parses them into a datetime, then
  # writes this datetime to the object
  private
  def merge_and_set_datetime
    self.datetime = DateTime.parse(@datetime_str) if errors.empty?
  end
Dean Stamler
Check out validates_timeliness for date validations, much easier. Also look at Date::DATE_FORMATS AND Time::DATE_FORMATS, for a better way of creating more formats you can re-use. I for instance add a format like Date::DATE_FORMATS[:in_words] = "%b %e, %Y" and you can then say Date.today.to_s(:in_words) in an initializer.
brad