事前にお詫び申し上げます。これは長い質問になります。
短縮版:
date
、、、start_time
およびを含む会議モデルがありend_time
ます。これらは時間オブジェクトであり、もちろんユーザーが入力するのは面倒なので、仮想属性を使用して、保存前にChronicによって解析される文字列を受け入れています。
フォームからこれらの仮想属性を受け取り、それらをモデルに渡すプレーンなバニラレールコントローラーがあります。コントローラは次のとおりです。
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で設定され、フロントエンドの現地時間では表示されません。
更新時に、日付は更新されません。時間は更新されますが、2000-01-01の間はUTCのままです。
長いバージョン
これを私にとって非常に奇妙なものにしているのは、これらすべてがモデル層で機能することを示す適切なテストカバレッジがあることです。
モデルは次のとおりです。
# 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
後のテスト方法で一括割り当てを介してレコードを作成および更新することで、それらが期待どおりに機能することを確認するために、特に混合しました。これらのテストはすべて合格です。
以下についての洞察に感謝します。
controller#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!あなたの提案は私を私の死角を越えさせました。