3

事前にお詫び申し上げます。これは長い質問になります。

短縮版:

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

後のテスト方法で一括割り当てを介してレコードを作成および更新することで、それらが期待どおりに機能することを確認するために、特に混合しました。これらのテストはすべて合格です。

以下についての洞察に感謝します。

  1. controller#updateアクションで日付が更新されないのはなぜですか?

  2. 設定された日付から年を取得しないのはなぜですか?これはモデルと仕様で機能しますが、コントローラーを介してフォームを介して送信された場合は機能しません。

  3. フォームから渡されるタイムゾーンに時間が設定されないのはなぜですか?繰り返しますが、これらの仕様は合格ですが、コントローラーの何が問題になっていますか?

  4. フロントエンドのタイムゾーンに時刻が表示されないのはなぜですか?

助けてくれてありがとう、私は何時間もそこに行ってきたので、私はこれの上の木のために森を失っていなければならないように感じます。


アップデート:

AJcodezの助けを借りて、私はいくつかの問題を見ました:

  1. 日付の割り当てが間違っていました、AJに感謝します!現在使用中:

    if @date_string.present?
        self.date = Chronic.parse(@date_string).to_date
    elsif self.date.nil?
        self.date = Date.today
    end
    
  2. Chronicを正しく使用していましたが、間違いはデータベースレイヤーにありました。データベースのフィールドをのではtimeなくに設定しました。これによりdatetime、すべてが台無しになります。これを読んでいる人への教訓:データベースフィールドとして決して使用timeしないでください(それが何をするのか、そしてなぜ日時の代わりにそれを使用するのかを正確に理解していない限り)。

  3. 上記と同じ問題ですが、フィールドを変更しdatetimeて問題を修正しました。

  4. ここでの問題は、モデルとビューの時間へのアクセスに関係しています。これらの時間書式設定メソッドをヘルパーに移動して、現在のリクエストスコープで呼び出されるようにすると、正しく機能します。

ありがとうAJ!あなたの提案は私を私の死角を越えさせました。

4

1 に答える 1

1

さて、ここに行きます..

1. controller#update アクションで日付が更新されないのはなぜですか?

潜在的な問題が 2 つあります。日付を再度解析していないようです。これを試して:

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を試して、 whats in が返されることを確認してください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 でデータベースに保存してから、現地時間で表示してみてください。

于 2012-12-20T05:45:48.490 に答える