通过与STI的多态关联“has_many:through”关联

时间:2014-06-23 09:27:08

标签: ruby-on-rails ruby ruby-on-rails-4 model polymorphic-associations

我有两个使用people表的模型:PersonPerson::Employee(继承自Person)。 people表格有type列。

还有另一个模型Group,它具有称为:owner的多态关联。 groups表格同时包含owner_id列和owner_type列。


应用程序/模型/ person.rb:

class Person < ActiveRecord::Base
    has_one :group, as: :owner
end

应用程序/模型/人/ employee.rb:

class Person::Employee < Person
end

应用程序/模型/ group.rb:

class Group < ActiveRecord::Base
    belongs_to :owner, polymorphic: true
    belongs_to :supervisor
end

问题是,当我使用以下代码创建Person :: Employee时,owner_type列设置为不正确的值:

group = Group.create
=> #<Group id: 1, owner_id: nil, owner_type: nil ... >
group.update owner: Person::Employee.create
=> true
group
=> #<Group id: 1, owner_id: 1, owner_type: "Person" ... >

owner_type应设置为"Person::Employee",但设置为"Person"


奇怪的是,在调用Group#owner时,这似乎不会导致任何问题,但在创建如下关联时确实会导致问题:

应用程序/模型/ supervisor.rb:

class Supervisor < ActiveRecord::Base
    has_many :groups
    has_many :employees, through: :groups, source: :owner, 
                         source_type: 'Person::Employee'
end

使用此类关联时,调用Supervisor#employees将不会产生任何结果,因为它正在查询WHERE "groups"."owner_type" = 'People::Employees',但owner_type设置为'People'

为什么此字段设置不正确以及可以采取哪些措施?


修改

根据thisowner_type字段设置不正确,但它按设计工作并将字段设置为 base的名称 STI模型。

问题似乎是has_many:through关联搜索Group s并将owner_type设置为模型的拥有名称,而不是 base 模型的名称。

设置正确查询has_many :employees, through: :group条目的Person::Employee关联的最佳方法是什么?

2 个答案:

答案 0 :(得分:5)

您正在使用Rails 4,因此您可以在关联中设置范围。您的Supervisor课程可能如下:

class Supervisor < ActiveRecord::Base
  has_many :groups
  has_many :employees, lambda {
    where(type: 'Person::Employee')
  }, through: :groups, source: :owner, source_type: 'Person'
end

然后你可以要求主管的员工如supervisor.employees,它会生成如下查询:

SELECT "people".* FROM "people" INNER JOIN "groups" ON "people"."id" = 
"groups"."owner_id" WHERE "people"."type" = 'Person::Employee' AND
"groups"."supervisor_id" = ? AND "groups"."owner_type" = 'Person' 
[["supervisor_id", 1]]

这使您可以使用标准关联助手(例如build),并且比编辑2更简单。

答案 1 :(得分:0)

我确实提出了这种解决方法,它添加了一个回调来为owner_type设置正确的值:

class Group < ActiveRecord::Base
    belongs_to :owner, polymorphic: true
    before_validation :copy_owner_type

    private

    def copy_owner_type
        self.owner_type = owner.type if owner
    end
end

但是,我不知道这是否是最佳和/或最优雅的解决方案。


修改

在发现owner_type字段假设要设置为基本STI模型后,我想出了以下方法来查询Person::Employee条目Group型号:

class Supervisor < ActiveRecord::Base
    has_many :groups

    def employees
        Person::Employee.joins(:groups).where(
            'people.type' => 'Person::Employee',
            'groups.supervisor_id' => id
        )
    end
end

但是,这似乎并没有缓存其结果。


编辑2:

我提出了一个更优雅的解决方案,包括设置has_many:通过关联与基本模型关联,然后创建一个范围来仅查询继承的模型。

class Person < ActiveRecord::Base
    scope :employees, -> { where type: 'Person::Employee' }
end

class Supervisor < ActiveRecord::Base
    has_many :groups
    has_many :people, through: :groups, source: :owner, source_type: 'Person'
end

有了这个,我可以致电Supervisor.take.people.employees