Rails验证一组对象

时间:2012-04-16 21:02:43

标签: ruby-on-rails validation model

我有两个对象,Project和User,它们由一个名为ProjectAssignment的对象连接。 ProjectAssignments对象有一个附加字段:project_role。模型如下所示。

class Project < ActiveRecord::Base
  #relationships
  has_many :project_assignments
  has_many :users, :through => :project_assignments
end

class ProjectAssignment < ActiveRecord::Base
  belongs_to :project
  belongs_to :user
  belongs_to :project_role
end

class User
  has_many :project_assignments
  has_many :projects, :through => :project_assignments
end

我必须验证对于给定项目,任何时候都只有一个ProjectAssignment,其project_role为“Principal Investigator”。我有点不确定如何在ProjectAssignment模型中编写验证。如果我首先取消当前的PI,那么主要调查员少于1个,如果在取消设置另一个之前将用户设置为Principal Investigator,则超过1个。

class ProjectAssignment
  validates :allow_exactly_one_pi

  def require_exactly_one_pi  
    if self.project_role.name == 'Principal Investigator' and other_princ_inv_exists
      #more than one principle investigator set => error
    elseif was_principle_investigator
      #no principle investigator set => error
    end
  end
end

有关如何处理此事的任何建议吗?

2 个答案:

答案 0 :(得分:0)

非常好的咀嚼问题。

首先,您必须将某个模型的某些内容暴露给另一个模型,有一种特定类型的角色(主要调查员),而ProjectAssignment必须知道该“特殊情况”角色。但!它应该是跟踪特殊状态的项目角色,因此我将向ProjectRole模型添加一个方法:

ProjectRole < ActiveRecord::Base
  def ispi?
    self.name == 'Principal Investigator'
  end
end

然后你必须弄清楚如何迭代所有project_asssignments并确定它们中的任何一个是否是主要的调查员。您必须从project_assignment的实例访问Class方法。

class ProjectAssignment < ActiveRecord::Base
  validate :there_can_only_be_one_principal_investigator

  def there_can_only_be_one_principal_investigator
    error = false
    self.class.where('project_id = ?',self.project_id).each do |p|
      if p.project_role.ispi?
         error = true
         break
      end
    end
    if error
      #whatever
    end
  end
end

现在你必须改变你的联想,ProjectAssignment只能有一个角色,所以

class ProjectAssignment < ActiveRecord::Base
  belongs_to :project
  belongs_to :user
  has_one :project_role
end

现在您可以放心,如果该项目已存在Principal Investigator,则无法使用project_role == Principal Investigator添加ProjectAssignment。

如果更新,您使用project_role == PI更新ProjectAssignment,并且已经有另一个带有PI的ProjectAssignment,验证将捕获该更新。

现在,您如何确保至少有一个PI?我认为这意味着任何一个项目的FIRST ProjectAssignment都必须是PI。这是你必须有点hackish的地方,你必须直接在ProjectAssignment模型中公开有关ProjectRole模型的知识。

validate  :there_must_be_at_least_one_principal_investigator

def there_must_be_at_least_one_principal_investigator
  if self.class.where('project_id = ?', self.project_id).count() == 0 AND !self.project_role_id == 1
     #error
  end
end

我真的不喜欢这个解决方案,因为PI角色的id为1(或其他任何东西)的事实在另一个模型中被硬编码!为了使它有点令人反感,你可以在ProjectRole模型中添加一个Class方法

class ProjectRole < ActiveRecord::Base
  def self.piid
     1 # or whatever it is
  end
end

然后这样做:

def there_must_be_at_least_one_principal_investigator
  if self.class.where('project_id = ?', self.project_id).count() == 0 AND ! self.project_role_id == ProjectRole.piid
     #error
  end
end

现在,你如何改变首席调查员?你必须采取单独的行动,即

class ProjectAssignmentController < ApplicationController
  def change_pi
     @proj_assignment1 = ProjectAssignment.find(params[:orig_pi_id])
     @proj_assignment2 = ProjectAssignment.find(params[:new_pi_id])
     @proj_assignment1.project_role_id = params[:new_role_for_orig_pi].to_i
     @proj_assignment1.save :validate=>false # it's OK,  you're taking care of it below
     @proj_assignment2.project_role_id = ProjectRole.piid
     @proj_assignment2.save
  end
end

答案 1 :(得分:0)

这是一种完全不同的方法,摘要格式为:

1)当您创建一个新项目时,请询问应该是PI的用户,并构建第一个ProjectAssignment记录,这样就无需验证是否至少有一个PI。

2)在ProjectController和项目编辑视图中,创建某种“更改PI”界面,在该界面中,您需要知道成为新PI的用户的user_id,以及project_role_id,如果他留在项目上,如果project_role_id为nil,则需要分配当前PI,这意味着应该从项目中删除用户。

这种方法完全取消了验证!

第一个答案是一个非常有趣的练习,我讨厌建议有人改变他们的整个方法,我宁愿回答问题,尽我所能。但在看到“验证”变得多么复杂之后,我认为最好不要在你的情况下依赖它们,你只需要代码你的应用程序以确保你的条件得到满足。

HTH