如何返回after_save创建的对象的验证错误



我正在编写一个预订系统,该系统使用 ice_cube gem 处理定期预订。一个 Booking has_many BookingItem s,重复规则中每个出现一个,这些是在由 Booking 的 after_save 回调调用的方法中创建的。

这一切都很好,直到我向BookingItem添加验证,通过检查给定时间是否还没有BookingItem来避免重复预订。此验证引发了一个错误,我想在预订表单上显示该错误,但目前它只是静默地阻止保存Booking - 因为错误是由BookingItem引发的,因此它没有传递回Booking的表单。

app/models/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

app/models/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

app/views/booking/_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 %>

如果使用after_save回调来创建BookingItem对象,则选项会受到一定限制。

与其使用 after_save ,我会使用 before_validation 并进行一些调整以适应这种情况。

1) 在before_validation回调中构建BookingItem对象

before_validation :recreate_booking_items!

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

请注意,我使用的是build而不是create!

验证Booking对象时,还将验证booking_items集合中的新BookingItem对象。 任何错误都将包含在主Booking对象的错误集合中,您可以像往常一样在视图中显示它们,因为Booking对象将无法保存。

笔记

1) 验证Booking对象时,将自动验证BookingItem对象,因为它们是新记录并且属于has_many关联。 如果它们被持久化(即已经在数据库中),则不会自动验证它们。

2) before_validation回调可以在对象的生命周期中多次调用,具体取决于您的代码。 在这种情况下,每次调用回调时都会构建BookingItem对象,这将导致重复。为了防止这种情况,您可以在recreate_booking_items!的开头添加以下行:

booking_items.delete_all

当然,如果您已在数据库中保留了BookingItem对象,则可能不希望这样做(见下文)。

3) 此代码专为创建Booking对象而设计。如果要编辑已保留BookingItem对象的现有Booking对象,则可能需要进行某些修改,具体取决于所需的功能。

更新:

在下面的评论中解决@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

这会为每个无效BookingItem Booking一个很好的自定义消息,但它也为每个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 集合的非持久成员的自动验证来运行每个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>

如果使用此方法,则Booking对象错误集合中将出现通用的"预订项目无效"错误,因此您可能希望以某种方式忽略这些错误,以便它们不显示。

注意:我不熟悉 IceCube,但如果您通过 nested_attributes_for 在表单中显示BookingItem对象,这可能会与在before_validation回调中构建BookingItem对象发生冲突。

相关内容

  • 没有找到相关文章

最新更新