ActiveRecord has_many where two columns in table A are primary keys in table B
Asked Answered
P

3

7

I have a model, Couple, which has two columns, first_person_id and second_person_id and another model, Person, whose primary key is person_id and has the column name

Here's the usage I want:

#including 'Person' model for eager loading, this is crucial for me
c = Couple.find(:all, :include => :persons)[0]
puts "#{c.first_person.name} and #{c.second_person.name}"

So how can I do this?

Pardner answered 24/1, 2010 at 0:57 Comment(2)
If you're using Rails, why would the primary key on the Person model be "person_id" and not just "id"?Pyo
I changed the names of my models for this post as I'd like to keep the names off the internet. It should be id but that's not a difficult fix. Just use set_primary_key.Pardner
P
14

The relationships declared in Couple should look like this:

class Couple
  named_scope :with_people, { :include => [:first_person, :second_person] }
  belongs_to :first_person, :class_name => 'Person'
  belongs_to :second_person, :class_name => 'Person'
end

#usage:
Couple.with_people.first
# => <Couple ... @first_person: <Person ...>, @second_person: <Person ...>>

Those in Person depend on whether a Person can be part of more than one Couple. If a Person can only belong to one Couple and can't be the "first" Person on one and the Second on another, you might want:

class Person
  has_one :couple_as_first_person, :foreign_key => 'first_person_id', :class_name => 'Couple'
  has_one :couple_as_second_person, :foreign_key => 'second_person_id', :class_name => 'Couple'

  def couple
    couple_as_first_person || couple_as_second_person
  end
end

If a Person can belong to several Couples, and there's no way to tell whether they're the "first" or "second" in any given Couple, you might want:

class Person
  has_many :couples_as_first_person, :foreign_key => 'first_person_id', :class_name => 'Couple'
  has_many :couples_as_second_person, :foreign_key => 'second_person_id', :class_name => 'Couple'

  def couples
    couples_as_first_person + couples_as_second_person
  end
end
Patten answered 25/1, 2010 at 22:56 Comment(3)
This is a well-written, well thought-out answer. Thanks so much. I'll test this soon and let you know.Pardner
haven't tested it yet, but you WIN :)Pardner
I just did something like this using AREL: def widgets; Widget.where( Wiget.arel_table[:asset1_id].eq(id).or( Widget.arel_table[:asset2_id].eq(id) ) ); endImpassable
M
0

Untested, but according to the Rails API documentation, maybe something like:

class Couple < ActiveRecord::Base
    has_one :person, :foreign_key => :first_person_id
    has_one :person, :foreign_key => :second_person_id
end
Merriman answered 24/1, 2010 at 1:7 Comment(1)
Those should be belongs_to, not has_one, since the foreign keys are on this model.Pyo
G
0

Theory only, untested:

Create two subclasses of Person:

class FirstPerson < Person
   belongs_to :couple

class SecondPerson < Person
   belongs_to :couple

Couple class has_many of each:

class Couple
   has_many :first_persons, :foreign_key => :first_person_id
   has_many :second_persons, :foreign_key => :second_person_id

Then find:

 Couple.all(:include => [:first_persons, :second_persons])
Grigsby answered 24/1, 2010 at 3:37 Comment(1)
shouldn't a couple has_one :first_person and :second_person?Pardner

© 2022 - 2024 — McMap. All rights reserved.