Как использовать Chronic для анализа дат в текстовом поле даты и времени

Я пытаюсь получить текстовое поле, которое мои пользователи могут вводить во что-то, что может быть проанализировано драгоценным камнем Chronic. Вот мой файл модели:

require 'chronic'

class Event < ActiveRecord::Base
  belongs_to :user

  validates_presence_of :e_time
  before_validation :parse_date

  def parse_date
    self.e_time = Chronic.parse(self.e_time_before_type_cast) if self.e_time_before_type_cast
  end
end

Я думаю, что он вызывается, потому что, если я что-то неправильно напишу в parse_date, он жалуется, что этого не существует. Я также пробовал before_save :parse_date, но это тоже не работает.

Как я мог заставить это работать?

Спасибо


person Reti    schedule 21.07.2010    source источник


Ответы (3)


Такая ситуация выглядит хорошим кандидатом на использование виртуальных атрибутов в вашей Event модели для представления даты и времени на естественном языке для цели представления, в то время как реальный атрибут поддерживается в базе данных. Общая техника описана в этом скринкасте.

Итак, вы можете иметь в своей модели:

class Event < ActiveRecord::Base
  validates_presence_of :e_time

  def chronic_e_time
    self.e_time // Or whatever way you want to represent this
  end

  def chronic_e_time=(s)
    self.e_time = Chronic.parse(s) if s
  end
end

И на ваш взгляд:

<% form_for @event do |f| %>

  <% f.text_field :chronic_e_time %>

<% end %>

Если синтаксический анализ завершится неудачно, то e_time останется nil, и ваша проверка остановит сохранение записи.

person bjg    schedule 21.07.2010
comment
Одно изменение, которое мне пришлось сделать: изменить self.e_time = Chronic.parse(s) if s на self.e_time = Chronic.parse(s.to_date) if s Тот, который вы дали, не будет анализировать настоящие даты и время UTC, поэтому, когда я редактировал форму, он никогда не анализировал что-то вроде 2010-07-22 12:00:00 -0700 как правильную дату (она вернет ноль) из-за -700 для часового пояса. - person Reti; 30.07.2010

Основываясь на том, что сделал @bjg, вот рабочее решение, которое вы можете добавить в config/initializers/active_record_extend.rb

module ActiveRecord
  class Base
    # Defines natural language getters/setters for date/time fields.
    #
    #   chronic_attr :published_at
    #
    # ...will get you c_published_at & c_published_at=

    def self.chronic_attr(*arguments)
      arguments.each do |arg|

        define_method "c_#{arg}=".to_sym do |dt|
          self[arg] = Chronic::parse(dt)
        end

        define_method "c_#{arg}".to_sym do 
          if self[arg]
            self[arg].to_s(:picker)
          else
            ''
          end
        end
      end
    end
  end
end
person Subimage    schedule 10.09.2013

Я знаю, что обезьяньи исправления в наши дни устарели, но я думаю, что это самый прямой способ интегрировать Ruby, Rails и Chronic. Я поместил эту суть в свой инициализатор:

# https://gist.github.com/eric1234/3739149
#
# Mass monkey-patching! Provides integration between Chronic, Ruby and
# Rails. So now these all work:
#
#     Date.parse "next summer"
#     DateTime.parse "in 3 hours"
#     Time.parse "3 months ago saturday at 5:00 pm"
#
# In addition we override String#to_date, String#to_datetime, String#to_time.
# These methods are used by older version of ActiveRecord when parsing time.
# For newer versions of ActiveRecord, Date::_parse is overridden to also
# use Chronic. This means you can assign a simple string to a ActiveRecord
# attribute:
#
#     my_obj.starts_at = "thursday last week"
#
# Also since the String method are redefined you can easily create dates
# from strings. For example if you want tomorrow at 2pm you can just do:
#
#     'tomorrow at 2pm'.to_time
#
# This is more readable than the following IMHO:
#
#     1.day.from_now.change hour: 14

module Chronic::Extensions
  module String
    def to_date
      parsed = Chronic::Extensions.safe_parse self
      return parsed.to_date if parsed
      super
    end

    def to_datetime
      parsed = Chronic::Extensions.safe_parse self
      return parsed.to_datetime if parsed
      super
    end

    def to_time
      parsed = Chronic::Extensions.safe_parse self
      return parsed.to_time if parsed
      super
    end
  end
  ::String.prepend String

  module DateTime
    def parse datetime, *args
      parsed = Chronic::Extensions.safe_parse datetime
      return parsed.to_datetime if parsed
      super
    end
  end
  ::DateTime.singleton_class.prepend DateTime

  module Date
    def _parse date, *args
      parsed = Chronic::Extensions.safe_parse(date).try :to_datetime
      if parsed
        %i(year mon mday hour min sec sec_fraction offset).inject({}) do |result, fld|
          value = case fld
            when :offset then (parsed.offset * 86400).to_i
            else parsed.public_send fld
          end
          result[fld] = value if value && value != 0
          result
        end
      else
        super
      end
    end

    def parse date, *args
      parsed = Chronic::Extensions.safe_parse date
      return parsed.to_date if parsed
      super
    end
  end
  ::Date.singleton_class.prepend Date

  module Time
    def parse time, now=self.now
      parsed = Chronic::Extensions.safe_parse time, now: now
      return parsed if parsed
      super
    end

    def zone
      super.tap do |cur|
        Chronic.time_class = cur
      end
    end

    def zone= timezone
      super.tap do
        Chronic.time_class = zone
      end
    end
  end
  ::Time.singleton_class.prepend Time

  def self.safe_parse value, options={}
    without_recursion { Chronic.parse value, options }
  end

  # There are cases where Chronic actually uses the Ruby date/time libraries.
  # This leads to infinate recursion as our monkey-patch will intercept the
  # built-in libraries to hand off to Chronic which in turn hands back to the
  # built-in libraries.
  #
  # To avoid this we have this function which acts as a guard to prevent the
  # recursion. If we have already proxied off to Chronic we won't proxy again.
  def self.without_recursion &blk
    unless in_recursion
      self.in_recursion = true
      ret = blk.call
      self.in_recursion = false
    end
    ret
  end
  mattr_accessor :in_recursion
end
person Eric Anderson    schedule 17.09.2012