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=)方法并在保存父(项目)后对子(任务)进行验证,但不保存父(项目)模型如果任何子(任务)无效?

有任何想法吗?

4

6 回答 6

163

使用: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

于 2011-01-24T14:26:42.040 回答
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
于 2009-09-16T01:15:17.930 回答
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
于 2010-03-10T03:06:03.110 回答
2

不幸的是,上述建议都不适用于 Rails 2.3.5。

在我的情况下,如果两个项目都是使用嵌套属性创建的,则任务中的项目始终为零。只有当我删除 validates_presence_of 时,创建才会成功。单元测试和日志显示一切都已正确创建。

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

于 2010-03-09T11:22:56.103 回答
1

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

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

再见

于 2009-06-08T14:50:26.797 回答
0

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

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

于 2009-08-03T16:53:30.187 回答