1

我有两个对象,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,则 Principal Investigator 少于 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

有什么建议应该如何处理?

4

2 回答 2

0

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

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

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

这种方法将完全消除验证!

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

高温高压

于 2012-04-17T16:42:36.793 回答
0

很好的问题。

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

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

然后,您必须弄清楚如何遍历所有 project_assignments 并确定其中是否有任何一个是主要调查员。您必须从 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?我认为这意味着任何一个项目的第一个项目分配必须是 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
于 2012-04-16T23:31:34.510 回答