我正在编写一个使用ice_cube宝石来处理定期预订的预订系统。 Booking
has_many BookingItem
,每次出现在重复规则中,并且这些是在Booking
的after_save回调调用的方法中创建的。
这一切都正常,直到我向BookingItem
添加了验证,通过检查在给定时间内是否已经BookingItem
来避免双重预订。此验证引发了一个错误,我希望在预订表单上显示该错误,但目前它只是默默地阻止Booking
被保存 - 因为错误是由BookingItem
提出的&# 39;没有被传回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 :(得分:2)
如果您使用after_save
回调来创建BookingItem
个对象,则您的选项会受到限制。
我会使用after_save
而不是使用before_validation
,而是进行一些调整以适应这种情况。
1)在BookingItem
回调
before_validation
个对象
before_validation :recreate_booking_items!
和
def recreate_booking_items!
schedule.all_occurrences.each do |date|
booking_items.build(......
end
end
请注意,我使用build
代替create!
验证Booking
对象时,BookingItem
集合中的新booking_items
对象也将得到验证。任何错误都将包含在主Booking
对象的错误集合中,您可以像往常一样在视图中显示它们,因为Booking
对象将无法保存。
注释
1)BookingItem
对象在验证Booking
对象时自动验证,因为它们是新记录并属于has_many
关联。如果它们被持久化(即已经在数据库中),它们将不会被自动验证。
2)before_validation
回调可以在对象的生命周期中多次调用,具体取决于您的代码。在这种情况下,每次调用回调时都会构建BookingItem
个对象,这会导致重复。为防止这种情况,您可以在recreate_booking_items!
的开头添加以下行:
booking_items.delete_all
当然,如果您在数据库中保留BookingItem
个对象,则可能不希望这样做(见下文)。
3)此代码专门用于创建Booking
个对象。如果您正在编辑已存在Booking
个对象的现有BookingItem
个对象,则可能需要进行某些修改,具体取决于您所需的功能。
更新:
在下面的评论中提及@ Simon的后续问题。
我可以想到你可能想要做的两种方式:
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&#39;自动验证has_many
集合的非持久成员,以对每个BookingItem
对象运行验证检查。这将为每个人提供一个错误集合。
然后,在您的视图中,您可以遍历无效的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>
如果您使用此方法,您将拥有通用&#34;预订项目无效&#34;您的Booking
对象错误集合中存在错误,因此您可能希望以某种方式忽略这些错误,以便它们不会显示。
注意:我不熟悉IceCube,但如果您通过BookingItem
在表单中显示nested_attributes_for
个对象,则可能会与构建BookingItem
发生冲突before_validation
回调中的对象。