在Rails中更新验证失败后,嵌套属性未被销毁

时间:2012-06-10 09:15:02

标签: ruby-on-rails ruby-on-rails-3 nested-attributes

我很难自己解决这个问题所以我希望我能解释清楚。我有一个嵌套模型的表单。事物的简化版本如下(使用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”,如果我再次填写“thing”并提交表单,则更新的父级只有1个孩子。

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

显然,我已经把自己捆绑在一起,我已经尝试了大量的替代方法和修改,这是我迄今为止所提出的最好的方法。它非常接近,但我仍然有这种奇怪的边缘情况,在实践中可能永远不会发生,但这表明我不太了解我的控制器和模型的交互方式。

有人能让我直截了当吗?

3 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:1)

要了解发生了什么,我们首先需要了解Rails如何处理数据库交互。也就是说,Rails在事务中包装所有数据库交互,因此如果某些内容失败,则所有内容都将被还原。这就是为什么当出现验证错误时,您会在日志中看到“ROLLBACK”。它试图提交更改,但是有一个错误,所以它将其回滚。没有伤害。

同样,当您以相同的形式操作父记录和子记录时,它们都在事务中处理。这实际上是一件好事。如果某些内容失败,您不希望经历一些更改而一个更改失败。

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

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

希望这有助于您了解幕后发生的事情!

答案 2 :(得分:0)

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