【问题标题】:Rails Validation of a Group of ObjectsRails 验证一组对象
【发布时间】:2012-04-16 21:02:42
【问题描述】:

我有两个对象,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

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

【问题讨论】:

  • 澄清一下,您提到对于给定的项目,有一个项目分配。如果是这种情况,那么这不应该是 has_one 吗?或者一个特定的项目可以有多个项目分配?
  • 一个项目可以有多个项目分配,但只有一个 project_role 是主要调查员的项目分配。
  • 我明白了。只是为了挑战你的想法,也许一个项目分配可以有很多用户并且属于一个项目。这样,项目分配中的用户就有了角色。否则,您只是将每个用户包装在他们自己的项目分配中——这真的是您想要的吗?
  • 但是每个用户可以有多个项目,他们可以有不同的角色。我认为这不能在您描述的模型中表示。
  • 当然,一个用户可以属于多个 PA。一个项目可以有多个 PA(如果您愿意,也可以有一个 PA)。所以现在只需验证一个 PA 只能有 1 个角色为 PI 的用户。

标签: ruby-on-rails validation model


【解决方案1】:

很好的问题。

首先,您必须将一个模型的某些内容暴露给另一个模型,有一种特定类型的角色(主要调查员),并且 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

【讨论】:

  • 如果我读错了代码,请纠正我,但我认为这只是说明了我遇到的问题。必须始终只有一个 PI。上面的代码在ispi时抛出错误?对于另一个项目分配是正确的。此外,我必须确保永远不会为项目分配首席研究员。如果我在项目分配验证中同时执行这两条规则,我将永远无法更改该项目的首席研究员。
  • 好的,我现在更清楚地了解您的困境了。我会编辑答案。
  • 好的,我添加到答案中,但发布后,我认为简单的答案是您无法更改 PI,并且仅依赖验证,您必须创建一些 UI专门用于更改 PI,因为无论您如何更改 PI,在某些时候验证都会失败。答案的最后一部分只是一些示例伪代码,它可能应该在 ProjectController 中完成,而不是 ProjectAssignment 控制器,但我想你会明白基本的想法。
【解决方案2】:

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

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

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

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

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

HTH

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-30
    • 1970-01-01
    • 2022-11-25
    • 2015-10-24
    • 2018-08-21
    • 1970-01-01
    • 2018-05-06
    • 1970-01-01
    相关资源
    最近更新 更多