Rails 7.0.4
I have two Rails model, Person
and Marriage
. The idea is that a Person can have many wives and husbands through a Marriage join model, and can call Person.wives
and Person.husbands
to get the respective data.
Code for models:
class Person < ApplicationRecord
has_many :marriages
has_many :husbands, through: :marriages, class_name: 'Person'
has_many :wives, through: :marriages, class_name: 'Person'
end
class Marriage < ApplicationRecord
belongs_to :husband, class_name: 'Person'
belongs_to :wife, class_name: 'Person'
end
Code for Marriage migration:
class CreateMarriages < ActiveRecord::Migration[7.0]
def change
create_table :marriages do |t|
t.references :husband, foreign_key: { to_table: :people }, index: true
t.references :wife, foreign_key: { to_table: :people }, index: true
t.date :marriage_date
t.date :termination_date
t.timestamps
end
end
end
Persons have been created and a Marriage was created using this on the console:
husband = Person.first
wife = Person.second
Marriage.create(husband: husband, wife: wife)
However, calling husband.wives
returns this
Person Load (0.3ms) SELECT "people".* FROM "people" INNER JOIN "marriages" ON "people"."id" = "marriages"."wife_id" WHERE "marriages"."person_id" = ? [["person_id", 1]]
(Object doesn't support #inspect)
=>
It seems the the last part of the query, looking for marriages.person_id
, is wrong, as I expected it to use marriages.husband_id
. How to make it so that it uses the correct foreign key?
I have tried adding options to the associations, such as foreign_key: :wife_id
, source: :husband
, and inverse_of: :wife
to has_many :husbands
and similarly on the has_many :wives
, but the same result appears.
CodePudding user response:
You can't actually use a single has_many
assocation with two different foreign keys on the other table. Instead you need two different assocations:
class Person < ApplicationRecord
has_many :marriages_as_wife,
class_name: 'Marriage',
foreign_key: :wife_id,
inverse_of: :wife
has_many :marriages_as_husband,
class_name: 'Marriage',
foreign_key: :husband_id,
inverse_of: :husband
has_many :husbands,
through: :marriages_as_wife,
class_name: 'Person'
has_many :wives,
through: :marriages_as_husband,
class_name: 'Person'
end
inverse_of
doesn't actually change the generated queries in any way. Its simple used to setup the reference in memory to avoid a potential additional database query.
On a side note I would probally use a very different solution with a many to many join table instead to avoid making the hetronormative assumptions:
class Person
has_many :marriage_memberships
has_many :marriages,
through: :marriage_memberships
has_many :spouses,
through: :marriages,
source: :people
end
class Marriage
has_many :marriage_memberships
has_many :people, through: :marriage_memberships
end
# I can't come up with a better name
class MarriageMemberships
belongs_to :person
belongs_to :marriage
end