accepts_nested_attributes_for子关联验证失败


71

我在我的一个Rails模型中使用accepts_nested_attributes_for,我想在创建父代后保存子代。

该表单可以正常运行,但是验证失败。为简单起见,请想象以下内容:

class Project < ActiveRecord::Base
  has_many :tasks
  accepts_nested_attributes_for :tasks
end

class Task < ActiveRecord::Base
  belongs_to :project

  validates_presence_of :project_id
  validates_associated :project
end

我正在跑步:

Project.create!(
  :name => 'Something',
  :task_attributes => [ { :name => '123' }, { :name => '456' } ]
)

保存项目模型后,任务验证失败,因为它们没有project_id(因为尚未保存项目)。

看起来Rails遵循以下模式:

  • 验证项目
  • 验证任务
  • 保存项目
  • 保存任务

该模式应为:

  • 验证项目
  • 通过时:保存项目并继续...
  • 验证任务
    • 通过时:保存任务
    • 失败:删除项目(也许回滚?)

所以我的问题可以归结为:如何在保存了父项(项目)之后如何让Rails运行project_id =(或project =)方法并在子项(任务)上进行验证,但没有保存父项(项目)模型是否有任何孩子(任务)无效?

有任何想法吗?

Answers:


12

将此答案用于Rails 2,否则请参见以下:inverse_of答案

您可以通过检查关联的项目是否有效来检查project_id来解决此问题。


class Task < ActiveRecord::Base
  belongs_to :project

  validates_presence_of :project_id, :unless => lambda {|task| task.project.try(:valid?)}
  validates_associated :project
end

这对我不起作用。在validates_presence_of:project_id中,调用'project'返回nil,导致它尝试验证project_id并通过验证。我创建了另一个问题,因为我认为这个问题有所不同,但似乎是相同的stackoverflow.com/questions/2102724/…
约翰·达夫

10
下面的inverse_of解决方案对于Rails 3.0及更高版本更正确。
格兰特·哈钦斯

162

使用:inverse_ofvalidates_presence_of :parent。这应该可以解决您的验证问题。

   class Dungeon < ActiveRecord::Base
     has_many :traps, :inverse_of => :dungeon
   end

   class Trap < ActiveRecord::Base
     belongs_to :dungeon, :inverse_of => :traps
     validates_presence_of :dungeon
   end

http://apidock.com/rails/ActiveModel/Validations/HelperMethods/validates_presence_of

https://github.com/rails/rails/blob/73f2d37505025a446bb5314a090f412d0fceb8ca/activerecord/test/cases/nested_attributes_test.rb


Rails 3.2.3的未定义方法`validate_presence_of'。该行应为validates_presence_of:dungeon。
simeonwillbanks'5

在3.1.4上,它对我不起作用。即使子模型无效且未保存,也会保存父模型。
user938363 2012年

11
这应该是公认的答案,因为另一个只是解决方法。
gucki 2012年

这是什么原因呢?
maletor

1
我想再次投票赞成这个答案,因为它再次挽救了我。
hisa_py 2015年

9

仅验证关系,不验证ID:

class Task < ActiveRecord::Base
  belongs_to :project

  validates_presence_of :project
end

一旦关联被填充,ActiveRecord将认为验证成功,无论是否保存模型。您可能还想研究自动保存,以确保始终保存任务的项目:

class Task < ActiveRecord::Base
  belongs_to :project, :autosave => true

  validates_presence_of :project
end

2

不幸的是,以上建议均不适用于Rails 2.3.5。

就我而言,如果两个任务都是使用嵌套属性创建的,则该项目始终为零。只有删除了validates_presence_of,创建过程才能成功进行。单元测试和日志显示所有创建均正确。

因此,我现在倾向于向数据库而不是Rails添加约束,因为这似乎更可靠。


1

您可以只创建项目,并且仅在通过验证时添加项目:

tasks = params.delete(:task_attributes)
if Project.create(params)
  Project.update_attributes(:task_attributes => tasks)
end

再见


0

与bigo的建议相反,先保存父对象然后再保存子对象并不总是可以接受的。通常,在开始保存对象之前,您要确保所有对象都经过验证。这使用户有机会重新编辑输入表单并更正任何错误。

您描述的问题将在Rails 3.0中修复。我本来可以发布到Lighthouse票证的链接,但stackoverflow.com不允许这样做,因为我是新用户(#fail)。但是暂时,您可以使用插件“ parental_control ”,它将修复您的“错误”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.