2012-06-10 23 views
2

我正在努力让自己的头脑对这个问题感兴趣,所以我希望我能够很好地解释它。我有一个嵌套模型的表单。一个简化版本的东西如下(使用Rails 3.0.13);在Rails更新验证失败后未被破坏的嵌套属性

#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个孩子,然后将'thing'设置为空,则表单无法按预期验证(因为'thing'不存在)并且编辑视图被重新渲染。在结果看来,我删除的孩子再次出现!其隐藏的_destroy字段设置为“true”,如果我再次填写“thing”并提交表单,则更新的父级只有1个子级。

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

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

有人可以设置我吗?

回答

1

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

+0

这是一个好主意 - 我真的希望不要下井AJAX途径在这个应用程式所有,但它会解决这个问题。 – brad

+0

您可以在更新操作开始时在控制器中使用AJAX。 – user2340939

1

为了理解发生了什么,我们首先需要看看Rails如何处理数据库交互。也就是说,Rails将事务中的所有数据库交互包装在一起,这样如果某件事失败了,一切都会恢复。这就是为什么当出现验证错误时,您会在日志中看到“ROLLBACK”。它试图进行修改,但出现了错误,所以它将其回滚。没有伤害完成。

同样,当您在同一表单中操作父记录和子记录时,它们都在事务中处理。这实际上是一件好事。如果某件事失败了,你不需要做一些改变,一件事会失败。

只要您将_destroy设置为true,您只需将该子记录标记为销毁即可。但是,这种破坏不会立即发生。相反,孩子会一直等到父母被保存(save返回true)。在你的情况下,这不会发生:由于验证错误,致电save失败。但是,这是一件好事:如果父记录未能保存,应该没有理由删除子记录。

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

希望这有助于你理解发生了什么事情!

+0

这绝对是一个有用的解释,并符合我的想法。尽管如此,我并不完全遵循你所建议的数据销毁元素。过去如何确保孩子不被处理? – brad

+0

数据销毁元素的使用仅仅是隐藏包含标记为破坏的儿童的适当div的手段。这是所有的用户界面。 –

+0

我已经通过设置.fields div样式来显示:无。这不是显示问题,而是模型更新时不会观察到_destroy。 – brad

0

除了增加hidedeleted记录在用户端,你可以在模型定制验证​​添加跳过那些隐藏