2015-01-06 17 views
5

Ich habe folgendes ModellActive Record enthält mit 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 

Die StudentDetail und StaffDetails haben weitere Informationen, die ich versuche, sie alle in einer STI Benutzertabelle zu vermeiden, dass aufgrund ähnlicher, mit etwas Arbeit, die zu konkrete Klasse pro Tabelle Muster

kann ich diese leicht genug tun

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

aber ich will ein sein Es ist möglich, je nach Benutzertyp z.

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

Dies wird fehlschlagen, da einige der Benutzer Staff-Objekte sind.

Ich weiß, Schienen wird dies nicht aus der Box unterstützen, aber jemand irgendwelche Tricks zu bekommen diese

zu arbeiten

beste Lösung jetzt auf die Teilnahme an Schüler aufgeteilt Benutzer und Mitarbeiter dh

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

was nicht ideal ist. Hat jemand irgendwelche Tipps? Weg, dies zu lösen.

Antwort

6

Der einfachste Weg ist, die has_one Assoziationen einfach auf den Benutzer zu verschieben. Da nur Staff Datensätze staff_details haben, funktioniert das Vorladen einfach.

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

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

Das ist jedoch nicht ideal. Um das Vorladen weiter anzupassen, können Sie die Klasse Preloader in Rails verwenden. Laden Sie zunächst alle Datensätze ohne jede enthält, dann über sie iterieren und Vorspannung die Verbände benötigen Sie:

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 

Beachten Sie, dass diese API changed in Rails 4. In Version 3 und früher haben Sie nur die Methode verwendet.

Vor einer Weile schrieb ich eine blog post about this same problem, die ein paar andere nette Tricks enthält (z. B. die Angabe, dass Sie richtiges Verhalten erhalten).

+2

Die Syntax wieder geändert, für Rails 4.1 Ich glaube. Die neue Syntax lautet 'ActiveRecord :: Associations :: Preloader.new.preload (records, associations)'.Weitere Informationen finden Sie unter [commit] (https://github.com/rails/rails/commit/6e5a2cb9519aab568ea0cfea2f42364de8ccf655). – Leito

2

Wie wäre es mit dem includes auf die STI-Modelle als default_scope?

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 

Dann denke ich dies:

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

Sollte eifrig Last für beide Studenten und Mitarbeiter.

1

Sie könnten einfach benannte Bereiche verwenden, um Ihr Leben ein wenig einfacher zu machen.

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 

Dann können Sie einfach tun Event.for_students