如何使用Chronic在日期时间text_field中解析日期
问题描述:
我试图让我的用户可以输入一些可由慢性宝石解析的文本字段。这里是我的模型文件:如何使用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,但那也行不通。
我怎么能得到这个工作?
感谢
答
这种情况看起来像一个很好的候选人使用虚拟属性在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
和您的验证将会停止正在保存的记录。
答
我知道猴子补丁现在已经过去了,但我认为这是整合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
答
建立在什么@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
谢谢!这工作很好。 – Reti 2010-07-21 18:39:41
我必须做的一个改变:如果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