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 Group
s 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?
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.