Active Record包括STI

dbo*_*d68 5 activerecord ruby-on-rails 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
Run Code Online (Sandbox Code Playgroud)

StudentDetail和StaffDetails有额外的信息,我试图避免在一个STI用户表中使用它,因为必须使用类似于每个表模式的具体类的东西

我可以很容易地做到这一点

Event.includes(:attendances => :user).where(...)
Run Code Online (Sandbox Code Playgroud)

但我想能够包括取决于用户类型,例如

Event.includes(attendances: {:user => :student_details })
Run Code Online (Sandbox Code Playgroud)

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

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

现在最好的解决方案是分散用户出席学生和员工即

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

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

Xav*_*hay 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
Run Code Online (Sandbox Code Playgroud)

但这并不理想.要进一步自定义预加载,可以Preloader在Rails中使用该类.首先,加载所有记录而不包含任何包含,然后迭代它们并预加载所需的关联:

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
Run Code Online (Sandbox Code Playgroud)

请注意,此API 在Rails 4中已更改.在版本3和更早版本中,您只使用了该preload_associations方法.

前段时间我写了一篇关于同样问题博客文章,其中包括其他一些巧妙的技巧(比如说你得到了正确的行为).

  • 语法再次改变,对于Rails 4.1我相信.新语法是`ActiveRecord :: Associations :: Preloader.new.preload(records,associations)`.有关详细信息,请参阅[commit](https://github.com/rails/rails/commit/6e5a2cb9519aab568ea0cfea2f42364de8ccf655). (2认同)