具有 AR 枚举范围的 HABTM

HABTM with AR enum scope

如何让模型与同一模型的记录子集相关联,由 AR 枚举范围定义?

我认为像下面这样的东西会起作用,但它不起作用。如果 has_many :through 可以简化事情,我愿意使用它,但现在看来有点矫枉过正。

class User < ActiveRecord::Base
  enum role: [:doctor, :patient, :clinician]
  has_and_belongs_to_many :doctors, &:doctor?, class_name: "User"
end

更正建议的版本会出现语法错误;

class User < ActiveRecord::Base
  enum role: [:doctor, :patient, :clinician]
  has_and_belongs_to_many :doctors, &:doctor, class_name: "User"
end

错误:

SyntaxError: ~/m2/app/models/user.rb:3: syntax error, unexpected ',', expecting keyword_end
  has_and_belongs_to_many :doctors, &:doctor, class_name: "User"
                                             ^

使用 ->{doctor} 语法可以,但我想知道为什么 & 语法是错误的。

好的,请看下面为什么 & 语法是错误的,显然 :doctor.to_proc 技巧不起作用,因为 to_proc 将当前对象绑定到 proc 而它应该是传递给 proc,这意味着 ->{}Proc.new{} 在这种情况下确实是必需的。

问题是 scope 过程是在 class 上下文中计算的(好吧,从技术上讲,关系上下文从您的模型继承了 class 方法)。例如,来自 docs for has_and_belongs_to_many:

has_and_belongs_to_many :projects, -> { includes :milestones, :manager }

使用此代码,调用 SomeModel.projects 将等同于调用 SomeModel.projects.includes(:milestones, :manager)。现在,这是您的代码:

has_and_belongs_to_many :doctors, &:doctor?, class_name: "User"

使用这段代码,调用 User.doctors 将等同于调用 User.doctors.doctor?,这是行不通的,因为 doctor? 是 User 上的实例方法,而不是 class方法。

对于解决方案,我们只需要检查docs for enum。在底部有这个例子:

In rare circumstances you might need to access the mapping directly. The mappings are exposed through a class method with the pluralized attribute name:

Conversation.statuses # => { "active" => 0, "archived" => 1 }

Use that class method when you need to know the ordinal value of an enum:

Conversation.where("status <> ?", Conversation.statuses[:archived])

Where conditions on an enum attribute must use the ordinal value of an enum.

因为 Rails 自动为枚举创建范围,例如User.doctor,我们其实可以做得更好:

has_and_belongs_to_many :doctors, ->{ doctor }, class_name: "User"

您可能想尝试将其缩短为以下内容:

has_and_belongs_to_many :doctors, &:doctor, class_name: "User"

...但是正如您已经发现的那样,这会给您一个 SyntaxError。原因是 & 运算符告诉 Ruby 将给定对象转换为块并将其用作方法的块参数。如果对象不是 proc(如 :doctor),则首先调用其 to_proc 方法,这就是为什么例如[1,2].reduce(&:+) 有效。但是该块必须始终是最后一个参数(并且只能有一个),因此当您在 &:doctor.

之后放置另一个参数时会出现语法错误

但是,您可以这样做:

has_and_belongs_to_many :doctors, :doctor.to_proc,
                        class_name: "User"

...但我不认为你真的从中得到了什么。