2

我自己也在努力解决这个问题,所以我希望我能很好地解释它。我有一个带有嵌套模型的表格。事情的简化版本如下(使用 Rails 3.0.13);

#parent.rb

class Parent < ActiveRecord::Base
  has_many :children, :dependent => destroy
  accepts_nested_attributes_for :children, :allow_destroy => true
  validates_presence_of :thing
  validate :children_unique

  def children_unique
    errors[:base] << "You have the same child listed more than once!" if self.children.map{|x| [child.name, child.age]} != self.children.map{|x| [child.name, child.age]}.uniq
  end
end


#child.rb

class Child < ActiveRecord::Base
  belongs_to :parents
end

#parents_controller.rb

class ParentsController < ApplicationController
  load_and_authorize_resource :parent   #Using cancan for authorization. This calls @parent = Parent.find(params[:id]); authorize! :update, @parent; at the start of the update method

  def update
    if @parent.update_attributes(params[:operation])
      redirect_to @parent.admission, :notice => 'Operation was updated successfully'
    else
      flash.now[:warning] = "Parent was NOT updated!"
      render :action => "edit"
    end
  end
end

到目前为止,一切都很标准。我的表格也以非常标准的方式设置。parent_form.fields_for :children我为 fields_for 块中的孩子调用并渲染部分内容。每个子部分表单都包含一个删除链接,当单击它时,javascript 用于设置隐藏字段,因此 _destroy 的属性设置为“1”,并且部分从视图中隐藏。

这在大多数情况下都很有效,但我发现的奇怪问题如下;

如果我正在编辑一个已经有 2 个孩子的现有父母,并且我删除了其中 1 个孩子,然后将“事物”设置为空白,则表单无法按预期验证(因为“事物”不存在)并且编辑视图被重新渲染。结果看来,我删除的孩子又出现了!其隐藏的 _destroy 字段设置为“true”,如果我再次填写“事物”并提交表单,更新后的父级只有 1 个子级。

我通过向嵌套的子 div 添加一个条件样式标签来处理这个问题,<div class='fields'<%= " style='display: none;'" if f.object._destroy %>>因此如果在尝试更新记录之前将其删除,它将不再出现。这实现了它的目标,但是,如果我这样做并提交表单而不更正空的“事物”字段,那么模型再次验证失败,并且在出现的下一个编辑表单中,我添加一个与之前删除的相同的新子并填写“事物”字段,模型现在无法通过children_unique验证,即使第一个相同的孩子的 _delete 属性设置为“真”。

很明显,我在这里束手无策,我尝试了多种替代方法和修改,这是迄今为止我想出的最好的方法。它非常接近,但我仍然有这种奇怪的边缘情况,在实践中可能永远不会真正发生,但这表明我不太了解我的控制器和模型的交互方式。

有人可以让我直截了当吗?

4

3 回答 3

1

要了解发生了什么,我们首先需要了解 Rails 如何处理数据库交互。也就是说,Rails 将所有数据库交互包装在一个事务中,因此如果出现故障,一切都会恢复。这就是当出现验证错误时您会在日志中看到“ROLLBACK”的原因。它尝试提交更改,但出现错误,因此将其回滚。没有造成伤害。

同样,当您以相同的形式操作父记录和子记录时,它们都在事务中处理。这实际上是一件很棒的事情。如果某件事情失败了,您不希望进行一些更改而其中一项失败。

每当您设置_destroy为 true 时,您只是将该子记录标记为销毁。然而,这种破坏不会立即发生。相反,孩子会一直徘徊,直到父母被保存(save返回 true)。save在您的情况下,这不会发生:由于验证错误,调用失败。但是,这是一件好事:如果您的父记录无法保存,则不应删除子记录。

话虽这么说,我的建议是将这些子记录包装在 a 中div,添加一个数据元素,比如说data-destroy,它设置为属性_destroy。每当单击删除链接时,将其设置为 true,并且在加载页面时确保隐藏所有设置为 true 的divs 。data-destroy

希望这可以帮助您了解引擎盖下发生的事情!

于 2012-06-11T00:05:25.437 回答
1

您应该使删除链接成为对控制器的 ajax 调用,该控制器立即删除子项并从页面中删除它的标记,这样,即使验证确实失败,您也已经将其删除,并且这两个问题都不会发生。

于 2012-06-10T10:45:13.697 回答
0

除了在用户端添加记录之外hidedeleted您还可以在模型自定义验证中添加children.reject(&:marked_for_destruction?)以跳过那些隐藏的

于 2017-10-25T14:54:03.243 回答