Active Record包括STI

时间:2015-01-06 02:34:36

标签: ruby-on-rails activerecord 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用户表中使用它,因为必须使用类似每个表模式的具体类

我可以轻松地做到这一点

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

但我希望能够根据用户类型进行包含 e.g。

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

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

3 个答案:

答案 0 :(得分:7)

最简单的方法是将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类。首先,加载所有记录,不用任何包含,然后迭代它们并预加载所需的关联:

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

请注意,此API changed in Rails 4。在版本3及更早版本中,您只使用了preload_associations方法。

前段时间我写了一个blog post about this same problem,其中包含了一些其他巧妙的技巧(例如说明你得到了正确的行为)。

答案 1 :(得分:2)

如何将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(...)

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

答案 2 :(得分:1)

您可以使用命名范围让您的生活更轻松。

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