【问题标题】:Validation not preventing destroy - Rails 5验证不能防止破坏 - Rails 5
【发布时间】:2018-09-21 14:46:16
【问题描述】:

我有一条 Customer 记录,与 Location 有多对多关联。

我目前在编辑客户页面上选中/取消选中我想要的特定客户的位置。当我取消选中所有位置并点击提交时,这些位置将从我的模型中删除,并且我的验证会引发错误。我本来希望这些位置不会被删除,因为我有需要它们存在的验证,这是我需要的。

我在Customer 模型中的验证。

validates :locations, presence: true

我的看法:

<% @locations.each do |i| %>
      <li class="list-group-item">
        <%= hidden_field_tag "customer[location_ids][]", '' %>
        <%= check_box_tag "customer[location_ids][]", i.id, @customer.location_ids.include?(i.id) %>
        <%= i.name %>
      </li>
<% end %>

我的控制器:

    @customer = Customer.find(params[:id])
    @customer.assign_attributes(params.require(:customer).permit(:first_name, :middle_initial, :last_name, :location_ids => []))
    @customer.save!

.save! 导致以下错误:

error Locations can't be blank

但是,当您刷新页面或查看数据库记录时,位置引用被破坏了,我还看到它们被 CLI 破坏了。我不明白为什么验证不能阻止位置引用被破坏。提前致谢。

【问题讨论】:

  • 它们都被摧毁了,还是只有一个?您不必验证它们都必须在那里,只是至少必须有一个。
  • @EmilKampp 他们都被删除了。
  • 您是否查看过通过assign_attributes 批量分配嵌套属性的文档?似乎在没有验证的情况下删除记录可能会有一个怪癖,并且只有在您调用 save! 时才会调用您的验证。
  • 您可以在您的Location 模型中使用validates_associated :customer。此方法对关联运行验证。不过,我不确定这是否会在删除时发生。

标签: ruby-on-rails ruby ruby-on-rails-5


【解决方案1】:

这是人们在使用 ActiveRecord 时遇到的常见问题。

问题

location_ids= setter 方法将立即添加/更新/删除记录,这在ActiveRecord Associations guide 中有所提及。这种行为经常让开发人员感到惊讶,并且通常是不受欢迎的。我几乎总是避免它。

在您的代码中,您首先调用assign_attributes,后者又调用location_ids=。更改会立即保存到记录中。随后,当调用save! 时,它会打开一个新事务。如果发生验证错误,则只会回滚该事务中的更改,并且不包括 location_ids= 所做的已持久更改。

@customer.assign_attributes(params...)  # location_ids are saved outside of the `save!` transaction.
@customer.save!                         # validation errors will cause a rollback only to this point, excluding changes from the previous line.

简单的解决方案

使用update_attributes! 替换assign_attributessave!。这将具有将所有更改包装在事务中的效果,以便回滚将撤消您想要的所有内容。耶!

@customer.update_attributes!(params.require(:customer).permit(:first_name, :middle_initial, :last_name, :location_ids => []))

另一种方法

有时可能无法避免分别调用assign_attributessave。这使事情变得更加复杂。对于这种情况,我能想到的任何选择都不是微不足道的。

一种可能的解决方案是使用嵌套属性来更新/销毁子记录。

location_ids 实际上是更新与给定客户关联的每个位置记录的快捷方式。您可以在表单中使用嵌套属性来更新位置,而不是依赖它。此方法可以利用mark_for_destruction (link) 功能进行自动保存。描述这种方法的完整解决方案会非常冗长,但事实证明它对我来说非常有效。

【讨论】:

    【解决方案2】:

    相反,您可以这样做。对于验证,我们应该使用before_destory 回调。当您在取消选中所有位置时保存客户时,这应该可以防止删除位置。

    class Customer < ActiveRecord::Base
      has_many :locations
      before_destroy :check_for_locations?
    
      private
    
      def check_for_locations?
        errors.add(:base, "cannot delete customer with locations") unless locations.count == 0
        errors.blank?
      end
    end
    

    【讨论】:

    • 感谢您查看此内容,但我不担心删除客户。问题是当我更新客户并传递一组位置 ID 时。如果我传递了一个nil 数组,那么客户将被更新而没有任何位置。我试图阻止客户销毁其所有位置引用。
    【解决方案3】:

    我发现 ActiveRecord 通过要求它验证关联 *_ids 数组的长度来验证关联记录的存在更可靠。在你的情况下:

    class Customer < ApplicationRecord
      has_many :locations
      validates :location_ids, length: { minimum: 1 }
    end
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-09-15
      • 1970-01-01
      • 2012-06-12
      • 2014-01-01
      • 2014-01-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多