2012-06-20 3 views
6

Я получаю странное поведение при наборе коллекций из ассоциации has_many с рельсами 3 при использовании STI. У меня есть:Rails STI-ассоциация с подклассами

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 

Желаемое поведение является то, что branch.employees возвращает все сотрудники, включая администраторов филиалов. Админы ветви только кажется, быть «загружен» под этой коллекции, когда они были прочитаны в branch.admins, это выход из консоли:

Branch.first.employees.count 
=> 2 

Branch.first.admins.count 
=> 1 

Branch.first.employees.count 
=> 3 

Это можно увидеть в созданном SQL, в первый раз:

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

и второй раз:

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

я мог бы решить эту проблему, просто указав:

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

, так как все они находятся в их branch_id, но это создает проблемы в контроллере, если я хочу сделать branch.employees.build то класс будет по умолчанию User и я должен взломать на колонке типа где-то. Я обошел это сейчас:

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}) 
    } 

, но я бы очень хотел избежать этого, если это возможно. Кто-нибудь, какие-нибудь идеи?

EDIT:

finder_sql и counter_sql не реально решить это для меня, потому что кажется, что родительские ассоциации не использовать это и так organisation.employees, что has_many :employees, through: :branches снова включать только User::Employee класс в выборе.

ответ

17

В основном проблема возникает только в среде разработки, где классы загружаются по мере необходимости. (В производстве классы загружаются и остаются доступными.)

Проблемы возникает из-за переводчик не собирало еще виден, что Admins являются типом Employee при первом запуске Employee.find и т.д. вызовом.

(Обратите внимание, что он позже использует IN ('User::Employee', 'User::BranchAdmin'))

Это происходит с каждым использованием модельных классов, которые более чем на один уровень, но только в DEV-режиме.

Подклассы всегда автоматически загружают свою родительскую иерархию. Базовые классы не загружают свои детские иерархии.

Hack-фикс:

Вы можете заставить правильное поведение в Dev-режиме, явно требуя все дочерние классы от гь файла базового класса.

+2

Это замечательный улов, спасибо. В любом случае структура модели была фактически изменена, поэтому проблема исчезла, но я не думаю, что даже считал бы ее результатом воздействия окружающей среды! –

2

Вы можете использовать :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 

Это был бы мой предпочтительный метод. Другим способом сделать это может быть добавление области по умолчанию для полиморфных моделей.

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

Я попытался использовать условия, но у меня были проблемы с этим. Теперь структура приложения изменилась, поэтому мне не нужно было об этом беспокоиться, возможно, это было исправлено в рельсах 3.2.7. –

Смежные вопросы