Search code examples
ruby-on-railsrubyruby-on-rails-4modelpolymorphic-associations

"has_many :through" association through a polymorphic association with STI


I have two models that use the people table: Person and Person::Employee (which inherits from Person). The people table has a type column.

There is another model, Group, which has a polymorphic association called :owner. The groups table has both an owner_id column and an owner_type column.


app/models/person.rb:

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

app/models/person/employee.rb:

class Person::Employee < Person
end

app/models/group.rb:

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

The problem is that when I create a Person::Employee with the following code, the owner_type column is set to an incorrect value:

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 should be set to "Person::Employee", but instead it is set to "Person".


Strangely, this doesn't seem to cause any problems when calling Group#owner, but it does cause issues when creating an association like the following:

app/models/supervisor.rb:

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

With this type of association, calling Supervisor#employees will yield no results because it is querying for WHERE "groups"."owner_type" = 'People::Employees' but owner_type is set to 'People'.

Why is this field getting set incorrectly and what can be done about it?


Edit:

According to this, the owner_type field is not getting set incorrectly, but it is working as designed and setting the field to the name of the base STI model.

The problem appears to be that the has_many :through association searches for Groups with a owner_type set to the model's own name, instead of the base model's name.

What is the best way to set up a has_many :employees, through: :group association that correctly queries for Person::Employee entries?


Solution

  • You're using Rails 4, so you can set a scope on your association. Your Supervisor class could look like:

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

    Then you can ask for a supervisor's employees like supervisor.employees, which generates a query like:

    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]]
    

    This lets you use the standard association helpers (e.g., build) and is slightly more straightforward than your edit 2.