2010-07-21 68 views
2

我试图让我的用户可以输入一些可由慢性宝石解析的文本字段。这里是我的模型文件:如何使用Chronic在日期时间text_field中解析日期

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,但那也行不通。

我怎么能得到这个工作?

感谢

回答

3

这种情况看起来像一个很好的候选人使用虚拟属性Event模型来表示自然语言日期和时间视图的目的,而真正的属性备份到数据库中。一般的技术,这screencast

描述所以你可能在你的模型:

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和您的验证将会停止正在保存的记录。

+0

谢谢!这工作很好。 – Reti 2010-07-21 18:39:41

+0

我必须做的一个改变:如果s'改变'self.e_time = Chronic.parse(s)到'self.e_time = Chronic.parse(s.to_date)如果s'你给的那个不会解析真正的UTC日期时间,所以当我编辑表单时,它永远不会分析'2010-07-22 12:00:00 -0700'这样的正确日期(它将返回nil),因为时区的'-700'。 – Reti 2010-07-30 00:15:28

1

我知道猴子补丁现在已经过去了,但我认为这是整合Ruby,Rails和Chronic最直接的方法。我把this gist在我的初始化:

# 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 
0

建立在什么@bjg所做的,这里有一个可行的解决方案,你可以在配置/初始化/ 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