2012-06-20 22 views
6

Otrzymuję dziwne zachowanie podczas pobierania kolekcji z asocjacji has_many z szynami 3 przy użyciu STI. Mam:Szynowe powiązanie STI z podklasami

class Branch < ActiveRecord::Base 
    has_many :employees, class_name: 'User::Employee' 
    has_many :admins, class_name: 'User::BranchAdmin' 
end 

class User < ActiveRecord::Base 
end 

class User::Employee < User 
    belongs_to :branch 
end 

class User::BranchAdmin < User::Employee 
end 

Pożądanym zachowaniem jest to, że branch.employees zwraca wszystkich pracowników, w tym administratorów oddziałów. Administratorzy Branża tylko wydają się być „załadowany” w tej kolekcji, gdy zostały udostępnione przez branch.admins, to wyjście z konsoli:

Branch.first.employees.count 
=> 2 

Branch.first.admins.count 
=> 1 

Branch.first.employees.count 
=> 3 

Widać to w wygenerowanym SQL, po raz pierwszy:

SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee') AND "users"."branch_id" = 1 

i drugi raz:

SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee', 'User::BranchAdmin') AND "users"."branch_id" = 1 

mogę rozwiązać ten problem, po prostu określając:

class Branch < ActiveRecord::Base 
    has_many :employees, class_name: 'User' 
    has_many :admins, class_name: 'User::BranchAdmin' 
end 

ponieważ wszystkie one znajdują się z ich branch_id ale to stwarza problemy w sterowniku, jeśli chcę zrobić branch.employees.build następnie klasa będzie domyślnie User i muszę się włamać na kolumnie typu gdzieś. Mam wokół tego teraz z:

has_many :employees, class_name: 'User::Employee', 
    finder_sql: Proc.new{ 
     %Q(SELECT users.* FROM users WHERE users.type IN   ('User::Employee','User::BranchAdmin') AND users.branch_id = #{id}) 
    }, 
    counter_sql: Proc.new{ 
     %Q(SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee', 'User::BranchAdmin') AND "users"."branch_id" = #{id}) 
    } 

ale naprawdę chciałbym tego uniknąć, jeśli to możliwe. Ktoś, jakieś pomysły?

EDIT:

finder_sql i counter_sql naprawdę nie rozwiązać go dla mnie, bo wydaje się, że związki macierzyste nie korzystać z tej i tak organisation.employees że has_many :employees, through: :branches będzie znowu tylko obejmują klasę w doborze User::Employee.

Odpowiedz

17

Zasadniczo problem występuje tylko w środowisku programistycznym, w którym klasy są ładowane w razie potrzeby. (Podczas produkcji klasy są ładowane i przechowywane).

Problem jest spowodowany tym, że tłumacz nie zauważył jeszcze, że Admins jest typu Employee podczas pierwszego uruchomienia połączenia Employee.find.

(Zauważ, że później wykorzystuje IN ('User::Employee', 'User::BranchAdmin'))

Dzieje się tak przy każdym wykorzystaniu modelu klas, które są głęboko więcej niż jeden poziom, ale tylko w dev-mode.

Podklasy zawsze automatycznie dodają do swojej hierarchii macierzystej. Klasy podstawowe nie powodują autoloadowania swoich hierachii.

Hack-fix:

Można wymusić poprawne zachowanie w dev-mode jawnie wymagając wszystkich klas potomnych z rb pliku klasy bazowej.

+2

To wspaniały chwyt, dzięki. Struktura modelu została właściwie zmieniona, więc problem zniknął, ale nie sądzę, bym nawet pomyślał, że to efekt środowiska! –

2

Czy można użyć :conditions?

class Branch < ActiveRecord::Base 
    has_many :employees, class_name: 'User::Employee', :conditions => {:type => "User::Employee"} 
    has_many :admins, class_name: 'User::BranchAdmin', :conditions => {:type => "User::BranchAdmin"} 
end 

To byłaby moja preferowana metoda. Innym sposobem na to może być dodanie domyślnego zakresu do modeli polimorficznych.

class User::BranchAdmin < User::Employee 
    default_scope where("type = ?", name) 
end 
+0

Próbowałem używać warunków, ale miałem z tym problemy. Struktura aplikacji zmieniła się teraz, więc nie musiałem się o to martwić, mogło to być naprawione w szynach 3.2.7. –