我有一条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中被破坏了。我不明白为什么验证不能阻止位置引用被销毁。预先感谢。
答案 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_attributes
和save!
。这将具有包装事务中所有更改的效果,以便回滚将撤消所有您想要的内容。是的!
@customer.update_attributes!(params.require(:customer).permit(:first_name, :middle_initial, :last_name, :location_ids => []))
有时可能无法避免分别调用assign_attributes
和save
。这使事情变得更加复杂。对于这种情况,我认为没有其他选择是简单的。
一种可能的解决方案是使用嵌套属性来更新/销毁子记录。
location_ids
实际上是更新与给定客户关联的每个位置记录的快捷方式。不必依赖于此,您可以在表单中使用嵌套属性来更新位置。此方法可以利用mark_for_destruction
(link)功能进行自动保存。描述这种方法的完整解决方案的时间过长,但事实证明对我来说非常有效。
答案 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