2012-12-20 76 views
3

我提前道歉,這將是一個長期的問題。Rails:控制器不會正確更新模型

短版:

我有,有一個datestart_timeend_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. 爲什麼沒有在控制器#更新行動的日期更新?

  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. 我正確地使用了慢性,我的錯誤是在數據庫層!我將數據庫中的字段設置爲time而不是datetime,這會毀掉所有內容。教訓任何人讀這個:從來沒有使用time作爲數據庫領域(除非你明確地知道它是什麼以及你爲什麼使用它而不是日期時間)。

  3. 與上面相同的問題,更改字段爲datetime修復了問題。

  4. 這裏的問題與訪問模型和視圖中的時間有關。如果我將這些時間格式化方法移到幫助器中,以便在當前請求範圍內調用它們,它們將正常工作。

謝謝AJ!你的建議讓我過去了我的盲點。

回答

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_zonehttp://api.rubyonrails.org/classes/Time.html#method-i-in_time_zone並且每次都明確指定您的時區。

不確定您是否已經這樣做,但嘗試在數據庫中明確地保存UTC時間,然後在本地時間顯示它們。

+0

非常感謝你,這不是100%的答案,但你幫我找到了一些新的調試方法,我得到了它的工作!我已經更新了答案,並提供了一些關於如何解決此問題的說明。 – Andrew