2012-06-08 57 views
2

我在寫預訂系統,它使用ice_cube寶石處理經常性預訂。 A Booking has_many BookingItem s,重複規則中每次出現一次,並且這些都是由Booking的after_save回調調用的方法創建的。如何返回after_save創建的對象的驗證錯誤?

這一切都工作正常,直到我加入了驗證BookingItem,通過檢查在給定時間還沒有BookingItem避免重複預訂。此驗證會產生一個錯誤,我想在預訂表單上顯示,但目前它只是靜靜地阻止Booking被保存 - 因爲錯誤是由BookingItem引發的,因此它不會被傳回到Booking的表單。

應用程序/模型/ booking.rb

class Booking < ActiveRecord::Base 
    include IceCube 

    has_many :booking_items, :dependent => :destroy 

    after_save :recreate_booking_items! 

    # snip 

    private 

    def recreate_booking_items! 
    schedule.all_occurrences.each do |date| 
     booking_items.create!(space: self.requested_space, 
          booking_date: date.to_date, 
          start_time: Time.parse("#{date.to_date.to_default_s} #{self.start_time.strftime('%H:%M:00')}"), 
          end_time: Time.parse("#{date.to_date.to_default_s} #{self.end_time.strftime('%H:%M:00')}")) 
    end 
    end 
end 

應用程序/模型/ booking_item.rb

class BookingItem < ActiveRecord::Base 
    belongs_to :booking 

    validate :availability_of_space 

    # snip 

    private 

    def availability_of_space 
     unless space.available_between? DateTime.parse("#{booking_date}##{start_time}"), DateTime.parse("#{booking_date}##{end_time}") 
     errors[:base] << "The selected space is not available between those times." 
     end 
    end 
end 

應用程序/視圖/預訂/ _form.html.erb

<% if @booking.errors.any? %> 
    <div id="error_explanation"> 
    <p><%= pluralize(@booking.errors.count, "error") %> prohibited this booking from being saved:</p> 
    <ul> 
     <% @booking.errors.full_messages.each do |msg| %> 
     <li><%= msg %></li> 
     <% end %> 
    </ul> 
    </div> 
<% end %> 

<%= form_for(@booking, :html => { :class => "nice custom"}) do |f| %> 
    ... 
<% end %> 
+0

你目前如何嘗試顯示錯誤(你的表單代碼是什麼)? – cdesrosiers

+0

我已經添加了表單代碼的相關位。我想我真正需要的是將'BookingItem's'錯誤添加到'@ booking'的某種方式。 – Simon

回答

2

如果您使用after_save回調來創建BookingItem對象,則您的選項會受到一定限制。

而不是使用after_save,我會使用before_validation,並作出一些調整,以適應這一點。

1)構建BookingItem對象在before_validation回調當你驗證Booking對象,我使用的build代替create!

before_validation :recreate_booking_items! 

def recreate_booking_items! 
    schedule.all_occurrences.each do |date| 
    booking_items.build(...... 
    end 
end 

注,中的新對象集合也將被驗證。主要的Booking對象的錯誤集合中將包含任何錯誤,並且您可以像往常一樣在視圖中顯示它們,因爲Booking對象將無法保存。當Booking對象進行驗證,因爲它們是新的記錄,屬於has_many協會

1)BookingItem對象會被自動驗證。如果它們被保留(即已經在數據庫中),它們將不會被自動驗證。

2)before_validation根據您的代碼,回調可以在對象的生命週期中多次調用。在這種情況下,每次調用回調時都會構建BookingItem對象,這會導致重複。爲了防止這種情況,你可以在recreate_booking_items!開頭添加以下行:

booking_items.delete_all 

當然,你可能不希望這樣做,如果你已在數據庫中堅持BookingItem對象(見下文)。

3)此代碼是爲創建Booking對象而明確設計的。如果您正在編輯已存在的Booking對象,而這些對象已持續存在BookingItem對象,則可能需要進行某些修改,具體取決於您所需的功能。

UPDATE:

爲了解決@西門的跟進在下面的意見的問題。

我能想到的,你可能想要做這兩種方式:

1)保持在驗證作爲BookingItem你擁有它。

然後,我將有一個自定義驗證Booking這樣的:

validate :validate_booking_items 

def validate_booking_items 
    booking_items.each do |bi| 
    if bi.invalid? 
     errors[:base] << "Booking item #{bi.<some property>} is invalid for <some reason>" 
    end 
    end 
end 

這使得一個不錯的自定義消息在Booking每個無效BookingItem,但它也給每個BookingItem它自己的錯誤集合,可以用於識別哪個booking_items無效。您可以參考無效的booking_items這樣:

@ booking.booking_items.select {| bi | bi.errors.present}

然後,如果你想顯示在您的視圖無效booking_items

f.fields_for :booking_items, f.object.booking_items.select {|bi| bi.errors.present? } do |bi| 
end 

這種方法的問題是,BookingItem可能有以下幾個原因無效的,並試圖將所有這些原因添加到基地Booking錯誤收集可能會變得混亂。

因此,另一種方法:

2)忘記自定義驗證在Booking。依靠Rails對has_many集合的非持久成員的自動驗證來運行每個對象的驗證檢查。這會給他們每個人一個錯誤集合。

然後,在您的視圖中,您可以遍歷無效的booking_items並顯示其各自的錯誤。

<ul> 
    <% @booking.booking_items.select {|bi| bi.errors.present? }.each do |bi| %> 
    <li> 
     Booking item <%= bi.name %> could not be saved because: 
     <ul> 
     <% bi.errors.full_messages.each do |msg| %> 
      <li><%= msg %></li> 
     <% end %> 
     </ul> 
    </li> 
    <% end %> 
</ul> 

如果使用這種方法,你將擁有通用的「預定項目是無效的」錯誤您Booking對象錯誤集合中,所以你可能要忽略那些不知何故,使他們不顯示。

注:我不熟悉的冰塊,但如果你在表單中通過nested_attributes_for顯示BookingItem對象,可能與在before_validation回調建設BookingItem對象發生衝突。

+0

這已經差不多完成了,謝謝。我從來沒有把注意力放在'build'方法之前:)現在的問題是,不是在表單上顯示驗證錯誤,而是針對每個衝突重複顯示「預訂項目無效」,而不是我在'BookingItem'驗證中寫入的錯誤。任何想法如何讓這條消息顯示在窗體上?最終目標是允許用戶爲每次衝突再發生選擇不同的時間或地點。 – Simon

+1

@simon:你可以在'Booking'對象中放置一個自定義的驗證器,通過'booking_items'循環檢查它們是否存在計劃衝突。然後,您可以將任何您想要的錯誤消息添加到「Booking」對象,並且您甚至可以指定哪個特定的「BookingItem」導致問題,例如包括時間片。如果你這樣做,你可能想關閉相關的'BookingItem'對象的現有自動驗證,以便除了特定的錯誤之外,你不會得到你已經得到的通用錯誤。 – Nathan

+0

完美,謝謝。將驗證移動到「預訂」已經成功了,現在我的錯誤大致如下:「游泳池在2012年6月10日星期日08:45至11:45之間不可用。」這比「預訂項目無效」更有幫助(並且語法正確)。如果你可以給我一個關於如何爲表單視圖提供錯誤的'BookingItem'對象的指針,那麼我將非常感激。現在我有'f.fields_for:booking_items',但是返回所有人:/ – Simon