validates_presence_of与belongs_to关联,正确的方式

时间:2011-05-27 16:52:38

标签: ruby-on-rails validation activerecord

我正在调查validates_presence_of实际上是如何工作的。假设我有两个模型

class Project < ActiveRecord::Base
  [...]
  has_many :roles
end

class Role < ActiveRecord::Base
  validates_presence_of :name, :project

  belongs_to :project
end

我希望它能让角色永远属于现有项目,但我刚从this example发现这可能导致无效(孤立)角色保存到数据库中。因此,正确的方法是在我的角色模型中插入validates_presence_of :project_id,它似乎有效,即使我认为在语义上更有意义来验证项目的存在而不是项目ID。

除此之外,如果我只是验证project_id的存在,我认为我可以放置一个无效的id(对于一个非现有的项目),因为默认情况下AR不会为迁移添加完整性检查,即使我添加它们手动某些DB不支持它们(即MySQL与MyISAM或sqlite)。这个例子证明了

# with validates_presence_of :name, :project, :project_id in the role class
Role.create!(:name => 'foo', :project_id => 1334, :project => Project.new)
  AREL (0.4ms)  INSERT INTO "roles" ("name", "project_id") VALUES ('foo', NULL)
+----+------+------------+
| id | name | project_id |
+----+------+------------+
| 7  | foo  |            |
+----+------+------------+

当然我不会写这样的代码,但我想在数据库中防止这种错误的数据。

我想知道如何确保角色总是与(真实的和已保存的)项目相关联。

我找到了validates_existence宝石,但除非绝对必要,否则我不想在项目中添加宝石。

有没有想过这个?

更新

validates_presence_of :project并在迁移中为project_id列添加:null => false似乎是一个更清晰的解决方案。

2 个答案:

答案 0 :(得分:6)

如果找不到具有id的对象,Rails将尝试查找id并添加验证错误。

class Role < AR::Base
  belongs_to :project
  validates_presence_of :project, :name
end


Role.create!(:name => "admin", :project_id => 1334)# Project 1334 does not exist
# => validation error raised

我看到你的问题也想处理提供作者对象的情况但是是新的而不是db。如果存在检查不起作用。会解决。

Role.create!(:name => "admin", :project => Project.new) # Validation passes when it shouldn't.

更新: 在某种程度上,您可以通过对关联的项目进行验证来减轻传递虚拟新对象的影响。

class Role < ActiveRecord::Base
  belongs_to :project
  validates_presence_of :project
  validates_associated :project
end

如果Project.new.valid?为false,则Role.create!(:name => "admin", :project => Project.new)也会引发错误。但是,如果Project.new.valid?为真,则上面将在保存时创建项目对象。

使用validates_associated :project会对您有所帮助吗?

答案 1 :(得分:2)

我尝试了很多验证器组合,但最干净的解决方案是使用validates_existence gem。有了它,我可以写这样的代码

r = Role.new(:name => 'foo', :project => Project.new) # => #<Role id: nil, name: "foo", project_id: nil, created_at: nil, updated_at: nil> 
r.valid? # => false 
r.errors # => {:project=>["does not exist"], :project_id=>["does not exist"]} 

所以我的最终模型就像

一样简单
class Role < ActiveRecord::Base
  belongs_to :project
  validates_existence_of :project
  # or with alternate syntax
  validates :project, :existence => true
  [...]
end

使用db验证和Aditya解决方案(即:迁移中的null =&gt; false和模型中的validates_presence_of:项目)Role#valid?将返回true,Role#save将在数据库级别引发异常project_id为null。