假设我有属于Project
的Projects
和People
。Person
可以是领导者,也可以不是领导者,并且有这方面的范围。一个Project
必须至少有一个领导人员,否则它是无效的。所以我试了这个:
class Project < ActiveRecord::Base
has_many :people
validate :has_a_leader
def has_a_leader
unless self.people.lead.size > 0
puts 'Must have at least one leader'
errors.add(:people, 'Must have at least one leader')
end
end
end
class Person < ActiveRecord::Base
belongs_to :project
scope :lead, -> { where(:is_lead => true) }
end
不幸的是,验证仅适用于保存的记录,因为新记录上的范围始终为空:
p = Project.new
p.people.build(:is_lead => true)
=> #<Person ..., is_lead: true>
p.people
=> #<ActiveRecord::AssociationRelation [#<Person ..., is_lead: true>]>
p.people.lead
=> #<ActiveRecord::AssociationRelation []>
p.valid?
'Must have at least one leader'
=> false
另一个尝试使用另一种语法:
p = Project.new
p.people.lead.build
=> #<Person ..., is_lead: true>
p.people.lead
=> #<ActiveRecord::AssociationRelation []>
p.people
=> #<ActiveRecord::AssociationRelation []> # <-- first syntax at least got something here
p.valid?
'Must have at least one leader'
=> false
所以看起来我必须像这样重写验证并在创建新项目时使用第一种语法:
def has_a_leader
unless self.people.find_all(&:is_lead).size > 0
puts 'Must have at least one leader'
errors.add(:people, 'Must have at least one leader')
end
end
但是现在我有两个地方定义了什么是领导者:在验证方法和范围lambda中。我再说一遍。有效,但不是 Rails 的方式。
有没有更好的方法可以做到这一点?
您可以通过添加另一个关联来解决问题:
class Project < ActiveRecord::Base
has_one :leader, -> { where(is_lead: true) }, class_name: 'Person'
validates :leader, presence: true
end
创建Project
时,您可以非常轻松地设置潜在客户:
def create
project = Project.new(params[:project])
project.leader.new(name: 'Corey') #=> uses the scope to set `is_lead` to `true`
end
Person
模型中仍然复制了lead
范围,但由于已经定义了,让我们使用它:
class Project < ActiveRecord::Base
has_one :leader, Person.method(:lead), class_name: 'Person'
end
这样做的好处是,也更容易抓住项目的领导者。
您是否考虑过在项目表中添加leader_id或main_leader_id? 我知道你的项目可以有多个领导者,但你的实现的一个潜在问题是:假设你创建了一个项目,它有一个人是领导者,所以它是有效的 - 太好了。 稍后,该人员将从项目中删除(通过更改人员的project_id属性)。 除非您对 Person 进行回调,否则您的项目不会知道它不再有领导者,并且它将处于无效状态。 如果您有其他代码假定 Project 有效并且至少有一个领导者(即 my_project.leaders.first.do_something),则可能会导致问题。 如果你有类似main_leader_id的东西,那么你可以在项目模型中简单地验证它(有状态:true),如果你需要获得所有的领导者,你仍然可以使用has_many关系。