Rails:在has_many关联中验证parent_id的存在

23

我有一个项目资源,里面有很多任务。我想要确保每个任务都有project_id,所以在任务模型中加入 validates_presence_of :project_id

然而,在创建带有任务的新项目时,只有在记录保存之后才会有project_id可用,因此我不能使用 validates_presence_of :project_id

那么我的问题是,如何验证任务模型中的project_id存在性?我想要确保每个任务都有父对象。

...

class Project < ActiveRecord::Base

  has_many :tasks, :dependent => :destroy
  accepts_nested_attributes_for :tasks, :allow_destroy => true

...

class Task < ActiveRecord::Base

 belongs_to :project
 validates_presence_of :project_id

这个问题对我来说没有太多意义。你想让一个任务属于一个项目,但是又没有先有项目... 怎么可能为不存在的东西获取ID呢? - porkeypop
3
你在创建项目时,是否通过嵌套表单创建任务? - Steve Weet
7个回答

19

您的代码可以有效运行:

  • 如果您使用 validates_presence_of :project,则只要项目存在,它就会进行验证。但是,如果您的项目未保存,则仍然可以保存任务。
  • 如果您使用 validates_presence_of :project_id,则必须存在一个整数,表示已保存的值。

这里有一个 rSpec 测试,证明了这一点。如果您验证 :project_id,则不能在未保存项目的情况下保存任务。

class Task < ActiveRecord::Base
  belongs_to :project
end

/specs/model_specs/task_spec.rb

require File.dirname(__FILE__) + '/../spec_helper'

describe Task do

  before(:each) do 
    @project = Project.new
  end

  it "should require a project_id, not just a project object" do
    task = Task.new
    task.project = @project
    Task.instance_eval("validates_presence_of :project_id")
    task.valid?.should == false
  end

  it "should not be valid without a project" do
    task = Task.new
    task.project = @project
    Task.instance_eval("validates_presence_of :project")
    task.valid?.should == false
    task.save.should == false
  end

end

我想将你的答案标记为正确,但是勾选标记不见了 :/ - deb
谢谢Deb!也许它很快就会回来。 - Jesse Wolgamott

15

请参考这里获取权威答案:

class Project < ActiveRecord::Base

  has_many :tasks, :dependent => :destroy, :inverse_of => :project
  accepts_nested_attributes_for :tasks, :allow_destroy => true

class Task < ActiveRecord::Base

 belongs_to :project
 validates_presence_of :project

如果问我,这并不是很优雅... 应该透明地进行验证。


3
也许我理解有误,但是你的操作似乎是试图欺骗Rails。为何不按照以下方式进行操作呢:
class Task < ActiveRecord::Base
  belongs_to :project
  validate_presence_of :project
end

这是一个非常老的问题:但是,你不能进行简单的关联验证的原因是任务在:fields_for中...它们嵌套在:project表单中。在保存时,project_id尚不可用于验证,验证将失败。您可以通过简单地删除对:project的验证来测试此功能。:inverse_of确保在保存时关联完整无缺。https://viget.com/extend/exploring-the-inverse-of-option-on-rails-model-associations - hellion

2

我认为你遇到了我曾经处理过的相同问题。我有两个模型,账户和用户,当创建账户时,第一个用户通过@account.users.build创建。 用户模型具有validates_presence_of :account验证。

为了使第一个用户通过验证,我在我的账户模型中添加了以下代码:

  before_validation_on_create :initialize_users

  def initialize_users
    users.each { |u| u.account = self }
  end

2

这完全忽略了创建操作。不好。使用:inverse_of确保在嵌套字段中保存时关联您的父对象。https://viget.com/extend/exploring-the-inverse-of-option-on-rails-model-associations - hellion

1
在现实中,你需要两者都有:

validates_presence_of project
validates_presence_of project_id

如果数据库中只有两个有效项目,即项目ID为99无效,则在以下情况下任务将不会保存:

task.project_id = 99
task.save

task.project = Project.new
task.save

我希望这对某些人有所帮助。


0

你的 Project 类必须定义

accepts_nested_attributes_for :tasks

请参考 Railscasts上的嵌套模型表单,了解如何制作表单的更多细节。


编辑:

在您的表单中应该有类似于以下内容:

_form.html.erb

<% form_for @project do |f| %> 
    # project fields...
    <% f.fields_for :tasks do |builder| %>
        <%= render 'task_fields', :f => builder %>
    <% end %>
    <p><%= link_to_add_fields "Add task", f, :tasks %></p>
    <%= f.submit %>
<% end %>

_task_fields.html.erb

<%= f.label :name, "Task name:" %>
<%= f.text_field :name %>
# task fields...
<%= link_to_remove_fields "Delete task", f, :tasks %>

link_to_add_fieldslink_to_remove_fields是在application_helper中定义的方法,用于动态添加/删除字段。


好问题。那么当它们一起保存时,它不会先创建项目,然后再创建任务吗? - digitalWestie
首先,它会保存项目,然后将结果id传递给任务的“project_id”。 - True Soft

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接