Rails STI与子类的关联

时间:2012-06-20 11:21:06

标签: sql ruby-on-rails activerecord sti

在使用STI时,从使用rails 3的has_many关联中获取集合时,我遇到了一些奇怪的行为。我有:

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

但如果可能,我真的想避免这种情况。任何人,任何想法?

编辑:

finder_sql和counter_sql还没有真正解决它,因为看起来父关联似乎没有使用它,所以organisation.employees has_many :employees, through: :branches将再次只包含User::Employee类在选择中。

2 个答案:

答案 0 :(得分:19)

基本上,问题只存在于根据需要加载类的开发环境中。 (在制作中,课程已加载并保持可用。)

由于解释程序在您第一次运行Admins等电话时尚未看到EmployeeEmployee.find的类型,因此问题就出现了。

(请注意,它稍后使用IN ('User::Employee', 'User::BranchAdmin')

每次使用深度超过一级的模型类时都会发生这种情况,但只能在开发模式下使用。

子类始终自动加载其父层次结构。基类不会自动加载他们的子级别。

哈克修复:

您可以通过显式要求基类rb文件中的所有子类来强制在dev模式下执行正确的行为。

答案 1 :(得分: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