11

我有一个User模型,其中有很多roles. 角色包含一个user_id字段,我想validate_presence_of

问题是:如果我在创建时为用户分配角色,则验证失败,因为没有设置 user_id。现在,我确实想验证 user_id 是否存在,但我需要在检查之前保存用户。

目前的代码如下所示:

@user = User.new(params[:user])
@user.roles << Role.new(:name => 'Peon') unless @user.has_roles?
if @user.save
  # ...

我能想到的解决问题的唯一方法是禁用验证,我不想这样做,或者双重保存到数据库,这不是很有效。

处理这个问题的标准方法是什么?

4

4 回答 4

10

经过一番研究,这个解决方案似乎是最简单的。首先,在您的Role模型中,而不是user_idvalidate , validate user

validates :user, :presence => true

然后,在您的User模型中,添加:inverse_of => :user到您的has_many调用中:

has_many :roles, :inverse_of => :user

然后它按预期工作:

irb(main):001:0> @user = User.new
=> #<User id: nil, created_at: nil, updated_at: nil>
irb(main):002:0> @user.roles << Role.new(:name => "blah")
=> [#<Role id: nil, user_id: nil, name: "blah", created_at: nil, updated_at: nil>]
irb(main):003:0> @user.roles[0].user
=> #<User id: nil, created_at: nil, updated_at: nil>
irb(main):004:0> @user.save
  (0.1ms)  begin transaction
 SQL (3.3ms)  INSERT INTO "users" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", Fri, 04 Jan 2013 02:29:33 UTC +00:00], ["updated_at", Fri, 04 Jan 2013 02:29:33 UTC +00:00]]
 User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 3 LIMIT 1
 SQL (0.2ms)  INSERT INTO "roles" ("created_at", "name", "updated_at", "user_id") VALUES (?, ?, ?, ?)  [["created_at", Fri, 04 Jan 2013 02:29:34 UTC +00:00], ["name", "blah"], ["updated_at", Fri, 04 Jan 2013 02:29:34 UTC +00:00], ["user_id", 3]]
  (1.9ms)  commit transaction
=> true
irb(main):005:0> @user.roles.first
=> #<Role id: 4, user_id: 3, name: "blah", created_at: "2013-01-04 02:29:34", updated_at: "2013-01-04 02:29:34">

但是请注意,这仍然会产生两个 SQL 事务,一个用于保存用户,一个用于保存角色。我不明白你怎么能避免这种情况。

另请参阅:如何验证是否存在与 Rails 的关联?

于 2013-01-04T02:33:25.823 回答
5

如果您将代码更改为如下所示,我认为您可以解决验证问题:

@user = User.new(params[:user])
@user.roles.new(:name => 'Peon') unless @user.has_roles?
if @user.save
  # ...

如果这不起作用,您可以尝试将验证更改为:

class Role < ActiveRecord::Base
  belongs_to :user
  validates :user_id, :presence => true, :unless => Proc.new() {|r| r.user}
end
于 2013-01-04T02:03:28.743 回答
3

您必须查看ActiveRecord 的 Callbacks。可能您会使用before_validation来执行此操作。

于 2013-01-04T02:06:58.683 回答
1

对于任何在谷歌上搜索该问题的解决方案的人has_many :through,截至 2013 年 12 月 5 日,该:inverse_of选项不能与:through( source ) 一起使用。相反,您可以使用@waldyr.ar 建议的方法。例如,如果我们的模型设置如下:

class User < ActiveRecord::Base
  has_many :roles
  has_many :tasks, through: roles
end

class Role < ActiveRecord::Base  
  belongs_to :user  
  belongs_to :task  
end

class Task < ActiveRecord::Base
  has_many :roles
  has_many :users, through: roles
end

我们可以如下修改我们的类,以在保存之前验证两者的Role存在taskuser

class Role < ActiveRecord::Base  
  belongs_to :user  
  belongs_to :task  
  before_save { validates_presence_of :user, :task }
end

现在,如果我们创建一个新的User并添加一对tasks这样的:

>> u = User.new  
>> 2.times { u.tasks << Task.new }

运行u.save将保存UserTask,以及透明地构建和保存一个新Role的,其外键user_idtask_id已正确设置。验证将对所有模型进行,我们可以继续我们的快乐方式!

于 2013-12-05T19:32:26.020 回答