我提前道歉,这将是一个很长的问题。
简短版:
我的会议模型有date
,start_time
和end_time
。这些是时间对象,当然这对用户来说很难输入,所以我使用虚拟属性来接受在保存之前由Chronic解析的字符串。
我有一个简单的vanilla rails控制器,它从表单接收这些虚拟属性并将它们传递给模型。这是控制器:
def create
@meeting = @member.meetings.build(params[:meeting])
if @meeting.save
redirect_to member_meetings_path(@member), :notice => "Meeting Added"
else
render :new
end
end
def update
@meeting = @member.meetings.find(params[:id])
if @meeting.update_attributes(params[:meeting])
redirect_to member_meetings_path(@member), :notice => "Meeting Updated"
else
render :new
end
end
我已经验证控制器从表单中接收到正确的参数,例如params[:meeting][:date_string]
已按预期设置。
问题:
在创建时,日期设置正确,但时间分配给2000年,以UTC格式设置,并且不会在前端的本地时间显示。
更新时,日期不会更新。时间更新但保留在UTC中2000-01-01。
更长的版本
这对我来说超级怪异的是我有不错的测试覆盖率,表明所有这些都适用于模型层。
以下是模型:
# DEPENDENCIES
require 'chronic'
class Meeting < ActiveRecord::Base
# MASS ASSIGNMENT PROTECTION
attr_accessible :name, :location, :description, :contact_id, :member_id, :time_zone,
:date, :start_time, :end_time, :date_string, :start_time_string, :end_time_string
# RELATIONSHIPS
belongs_to :member
belongs_to :contact
# CALLBACKS
before_save :parse_time
# Time IO Formatting
attr_writer :date_string, :start_time_string, :end_time_string
# Display time as string, year optional
def date_string(year=true)
if date
str = "%B %e"
str += ", %Y" if year
date.strftime(str).gsub(' ',' ')
else
""
end
end
# Display time as string, AM/PM optional
def start_time_string(meridian=true)
if start_time
str = "%l:%M"
str += " %p" if meridian
start_time.strftime(str).lstrip
else
""
end
end
# Display time as string, AM/PM optional
def end_time_string(meridian=true)
if end_time
str = "%l:%M"
str += " %p" if meridian
end_time.strftime(str).lstrip
else
""
end
end
# Display Date and Time for Front-End
def time
date.year == Date.today.year ? y = false : y = true
start_time.meridian != end_time.meridian ? m = true : m = false
[date_string(y),'; ',start_time_string(m),' - ',end_time_string].join
end
private
# Time Input Processing, called in `before_save`
def parse_time
set_time_zone
self.date ||= @date_string ? Chronic.parse(@date_string).to_date : Date.today
self.start_time = Chronic.parse @start_time_string, :now => self.date
self.end_time = Chronic.parse @end_time_string, :now => self.date
end
def set_time_zone
if time_zone
Time.zone = time_zone
elsif member && member.time_zone
Time.zone = member.time_zone
end
Chronic.time_class = Time.zone
end
end
这是规格。请注意,为了单独测试parse_time
回调,我在这些测试中调用@meeting.send(:parse_time)
,而不是实际创建或更新记录。
require "minitest_helper"
describe Meeting do
before do
@meeting = Meeting.new
end
describe "accepting dates in natural language" do
it "should recognize months and days" do
@meeting.date_string = 'December 17'
@meeting.send(:parse_time)
@meeting.date.must_equal Date.new(Time.now.year,12,17)
end
it "should assume a start time is today" do
@meeting.start_time_string = '1pm'
@meeting.send(:parse_time)
@meeting.start_time.must_equal Time.zone.local(Date.today.year,Date.today.month,Date.today.day, 13,0,0)
end
it "should assume an end time is today" do
@meeting.end_time_string = '3:30'
@meeting.send(:parse_time)
@meeting.end_time.must_equal Time.zone.local(Date.today.year,Date.today.month,Date.today.day, 15,30,0)
end
it "should set start time to the given date" do
@meeting.date = Date.new(Time.now.year,12,1)
@meeting.start_time_string = '4:30 pm'
@meeting.send(:parse_time)
@meeting.start_time.must_equal Time.zone.local(Time.now.year,12,1,16,30)
end
it "should set end time to the given date" do
@meeting.date = Date.new(Time.now.year,12,1)
@meeting.end_time_string = '6pm'
@meeting.send(:parse_time)
@meeting.end_time.must_equal Time.zone.local(Time.now.year,12,1,18,0)
end
end
describe "displaying time" do
before do
@meeting.date = Date.new(Date.today.year,12,1)
@meeting.start_time = Time.new(Date.today.year,12,1,16,30)
@meeting.end_time = Time.new(Date.today.year,12,1,18,0)
end
it "should print a friendly time" do
@meeting.time.must_equal "December 1; 4:30 - 6:00 PM"
end
end
describe "displaying if nil" do
it "should handle nil date" do
@meeting.date_string.must_equal ""
end
it "should handle nil start_time" do
@meeting.start_time_string.must_equal ""
end
it "should handle nil end_time" do
@meeting.end_time_string.must_equal ""
end
end
describe "time zones" do
before do
@meeting.assign_attributes(
time_zone: 'Central Time (US & Canada)',
date_string: "December 1, #{Time.now.year}",
start_time_string: "4:30 PM",
end_time_string: "6:00 PM"
)
@meeting.save
end
it "should set meeting start times in the given time zone" do
Time.zone = 'Central Time (US & Canada)'
@meeting.start_time.must_equal Time.zone.local(Time.now.year,12,1,16,30)
end
it "should set the correct UTC offset" do
@meeting.start_time.utc_offset.must_equal -(6*60*60)
end
after do
@meeting.destroy
end
end
describe "updating" do
before do
@m = Meeting.create(
time_zone: 'Central Time (US & Canada)',
date_string: "December 1, #{Time.now.year}",
start_time_string: "4:30 PM",
end_time_string: "6:00 PM"
)
@m.update_attributes start_time_string: '2pm', end_time_string: '3pm'
Time.zone = 'Central Time (US & Canada)'
end
it "should update start time via mass assignment" do
@m.start_time.must_equal Time.zone.local(Time.now.year,12,1,14,00)
end
it "should update end time via mass assignment" do
@m.end_time.must_equal Time.zone.local(Time.now.year,12,1,15,00)
end
after do
@m.destroy
end
end
end
我甚至专门在后续测试方法中通过质量分配创建和更新记录,以确保它们按预期工作。所有这些测试都通过了。
我很欣赏以下内容:
为什么控制器#update动作中的日期没有更新?
为什么没有时间从设定的日期开始?这适用于模型和规范,但不能通过控制器通过表单提交。
为什么时间不设置为从表单传入的时区?再次,这些规格通过,控制器出了什么问题?
为什么在前端的时区中不会显示时间?
感谢您的帮助,我觉得我必须在这片树上失去森林,因为我已经在这里花了好几个小时。
更新
感谢AJcodez的帮助,我看到了一些问题:
分配日期错误,谢谢AJ!现在使用:
if @date_string.present?
self.date = Chronic.parse(@date_string).to_date
elsif self.date.nil?
self.date = Date.today
end
我正在使用Chronic,我的错误是在数据库层!我将数据库中的字段设置为time
而不是datetime
,这会破坏所有内容。阅读本文的任何人的教训:永远不要将time
用作数据库字段(除非您完全理解它的作用以及为什么要使用它而不是日期时间。)
与上述问题相同,将字段更改为datetime
可解决问题。
此处的问题与模型与视图中的访问时间有关。如果我将这些时间格式化方法移动到帮助器中,以便在当前请求范围内调用它们,它们将正常工作。
谢谢AJ!你的建议让我超越了我的盲点。
答案 0 :(得分:1)
这里好了......
1。 为什么控制器更新操作中的日期没有更新?
我看到两个潜在的问题。看起来你没有再次解析日期。试试这个:
def update
@meeting = @member.meetings.find(params[:id])
@meeting.assign_attributes params[:meeting]
@meeting.send :parse_time
if @meeting.save
...
assign_attributes
设置但不保存新值:http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_attributes
此外,在您的parse_time方法中,您使用此分配:self.date ||=
,如果已分配,它将始终将self.date设置回自身。换句话说,除非它是假的,否则你无法更新日期。
2。 为什么没有时间从设定的日期开始?这适用于模型和规范,但不能通过控制器通过表单提交。
不知道,看起来你正确使用Chronic#parse
。
3。 为什么时间不设置为从表单传入的时区?同样,这些规格通过了,控制器出了什么问题?
尝试调试time_zone
并确保它在params[:meeting][:time_zone]
中返回什么内容。 Chronic看起来也是正确的。
旁注:如果您将无效字符串传递给Time#zone=
,则会出现错误。例如Time.zone = 'utc'
就是坏事。
4。 为什么在前端的时区中不会显示时间?
请参阅Time#in_time_zone
http://api.rubyonrails.org/classes/Time.html#method-i-in_time_zone,并且每次都明确指定您的时区。
不确定您是否已经这样做,但尝试在数据库中显式保存UTC时间,然后在本地时间显示它们。