【问题标题】:rails new record validation with scopes使用范围进行新记录验证
【发布时间】:2014-03-06 22:51:55
【问题描述】:

假设我有属于ProjectProjectsPeoplePerson 可以是领导者,也可以不是领导者,并且有这个范围。一个Project必须至少有一个leader,否则无效。所以我尝试了这个:

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 方式。

有没有更好的方法来做到这一点?

【问题讨论】:

    标签: ruby-on-rails validation ruby-on-rails-4 rails-activerecord has-many


    【解决方案1】:

    您可以通过添加另一个关联来解决您的问题:

    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
    

    这样做的好处是更容易抓住项目的领导者。

    【讨论】:

    • 感谢您的回答。当我这样做时,领导者不会以人的身份出现(project.people)。但也许这总比没有好。也许我应该简单地使用来自Person 的 STI 创建一个新的Leader 类来明确这一点?
    • 您可能希望将Person 添加到两个关联中。您还可以覆盖leader=,使其调用people &lt;&lt;,然后将调用传递给super。我不会将这与 STI 复杂化,除非“领导者”的行为与Person 不同。
    • 我只看到project.leader = Person.new(name: 'Corey') 上的is_lead 属性未设置为true - 所以保存项目后,领导者消失了。
    • ...你的意思是Person.lead.new(name: 'Corey')
    • 啊,是的,很抱歉——你是对的,你需要在创建 Person 时引用范围。如果您的创作流程更适用,您应该可以直接使用project.build_leader(name: 'Corey')
    【解决方案2】:

    您是否考虑将 leader_id 或 main_leader_id 添加到您的项目表中?我知道您的项目可以有多个领导者,但您的实施的一个潜在问题是:假设您创建了一个项目,并且分配了一个领导者,所以它是有效的 - 很好。稍后,该人从项目中移除(通过更改 Person 的 project_id 属性)。除非您对 Person 进行回调,否则您的项目将不会知道它不再有领导者,并且它将处于无效状态。如果您有其他代码假设 Project 有效并且至少有一个领导者(即 my_project.leaders.first.do_something),这可能会导致问题。如果你有类似 main_leader_id 的东西,那么你可以简单地在你的项目模型中验证它(存在:true),如果你需要获取所有领导者,你仍然可以使用 has_many 关系。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-10-05
      • 2014-06-14
      • 2020-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多