Home > Net >  Rails has_many :through association uses wrong foreign key for query
Rails has_many :through association uses wrong foreign key for query

Time:12-14

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 
  • Related