验证不能防止破坏-Rails 5

时间:2018-09-19 21:59:29

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

我有一条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中被破坏了。我不明白为什么验证不能阻止位置引用被销毁。预先感谢。

3 个答案:

答案 0 :(得分: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_destructionlink)功能进行自动保存。描述这种方法的完整解决方案的时间过长,但事实证明对我来说非常有效。

答案 1 :(得分:0)

相反,您可以这样做。为了进行验证,我们应该使用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

答案 2 :(得分:0)

我发现ActiveRecord通过要求验证关联的*_ids数组的长度来验证关联记录的存在更为可靠。就您而言:

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