Active Record 包含在 STI 中

Active Record includes with STI

我有以下型号

class Event < ActiveRecord::Base
  has_many :attendances

class Attendance < ActiveRecord::Base
 belongs_to :user

class Student < User
  has_one  :student_detail

class StudentDetail < ActiveRecord::Base
  belongs_to :school

class Staff < User
  has_one :staff_detail

class StaffDetail < ActiveRecord::Base

StudentDetail 和 StaffDetails 有更多信息,我试图避免将所有信息都放在一个 STI 用户 table 中,因为必须使用类似于 具体 class 的东西每个 table 模式

我可以轻松做到这一点

Event.includes(:attendances => :user).where(...)

但我希望能够根据用户类型包含 例如

Event.includes(attendances: {:user => :student_details })

这将失败,因为一些用户是 Staff 对象。

我知道 rails 不支持开箱即用,但任何人都有任何技巧可以让它工作

目前最好的解决方案是将用户按出勤率分配给学生和教职员工 即

class Attendance < ActiveRecord::Base
  belongs_to :student, -> {includes(:staff_detail) }
  belongs_to :staff, -> {includes(:student_detail) }
  #belong_to :user

这并不理想。 有人有任何提示吗?解决这个问题的方法。

如何将 includes 作为 default_scope 放在 STI 模型上?

class Event < ActiveRecord::Base
  has_many :attendances

class Attendance < ActiveRecord::Base
 belongs_to :user

class Student < User
  has_one  :student_detail
  default_scope includes(:student_detail)

class StudentDetail < ActiveRecord::Base
  belongs_to :school

class Staff < User
  has_one :staff_detail
  default_scope includes(:staff_detail)

class StaffDetail < ActiveRecord::Base

那么我认为:

Event.includes(:attendances => :user).where(...)

应该为学生和教职员工预加载。

最简单的方法是将 has_one 关联向下移动到用户。由于只有 Staff 条记录会有 staff_details,预加载将正常工作。

class User < ActiveRecord::Base
  has_one :staff_detail
  has_one :student_detail
end

class Staff < User; end
class Student < User; end

但这并不理想。要进一步自定义预加载,您可以使用 Rails 中的 Preloader class。首先,加载所有 不包含 的记录,然后遍历它们并预加载您需要的关联:

events = Event.includes(:attendances => :user)
users = events.users.flatten
users.group_by(&:class).each do |klass, records|
  associations = {
    Staff:   [:staff_detail],
    Student: [:student_detail]
  }.fetch(klass, [])

  ActiveRecord::Associations::Preloader.new(records, associations).run
end

注意这个APIchanged in Rails 4. In versions 3 and earlier, you just used the preload_associations方法。

前段时间我写了一篇 blog post about this same problem,其中包括一些其他巧妙的技巧(例如指定您获得正确的行为)。

您可以使用命名作用域让您的生活更轻松一些。

class Event < ActiveRecord::Base
  has_many :attendances
  scope :for_students, -> { includes(:attendances => { :users => :student_detail }).where('users.type = ?', 'Student') }
  scope :for_staff, -> { includes(:attendances => { :users => :staff_detail }).where('users.type = ?', 'Staff') }
end

那你就可以Event.for_students