mph372
mph372

Reputation: 25

Multiple belongs_to associations with the same class

I've been looking a lot on here for an answer to this, and I feel like I've come close, but most of the other examples don't quite match what I am trying to do.

I have a "Person" class, and a "District" class. Each district has many people -- one of those people is an incumbent (elected official), and the others are candidates. That part is working fine and fairly straightforward. On the bottom of the "Show" page for Districts, I list all associated people who are running for that district, with the incumbent in bold (if they are on the ballot). Again, working great.

However: there are some cases, where a Person may be an incumbent in one office (so they'd have that District_ID), but are a candidate for ANOTHER office (for instance, someone who is an incumbent Mayor for a City, but is also running for the Legislature to move up the chain).

So I am trying to figure out how to best allow a person under these circumstances to show up on both the District they are currently the incumbent for, as well as for the district they are seeking office.

If anyone could guide me toward the right association approach, I would be greatly appreciative!

Upvotes: 1

Views: 122

Answers (1)

SztupY
SztupY

Reputation: 10546

I'm going to go with the following assumptions:

  1. Each district has exactly one incumbent
  2. Each person can only be a candidate in one district
  3. You can be a candidate in a different district where you are incumbent

In this case I'd set up the system as follows:

  1. Person
belongs_to :district, inverse_of: :candidates, class_name: 'District', optional: true, 
has_one :incumbent_district, inverse_of: :incumbent, class_name: 'District', optional: true
  1. District
has_many :candidates, inverse_of: :district, class_name: 'Person'
belongs_to :incumbent, inverse_of: :incumbent_district, class_name: 'Person', foreign_key: 'incumbent_id', optional: true

And in the database:

create_table 'people' do |t|
  # (...)

  t.integer "district_id"
  t.index "district_id"
end

create_table 'districts' do |t|
  # (...)

  t.integer "incumbent_id"
  t.index "incumbent_id"
end

After which you should be able to do:

p = Person.first
p.districts # returns all districts where the person is a candidate
p.incumbent_district # returns the district where the candidate is the incumbent, or `nil` if there isn't

d = District.first
d.incumbent # returns the incumbent person
d.candidates # returns the candidates for this district

Notes:

  1. You could switch the has_one/belongs_to for the incumbent - it doesn't matter where you have the has_one and where the belongs_to as its a 1-1 relation. However usually you would put the belongs_to to the class which is usually going to have a link, and the has_one to the class, where this is optional. Since each district likely has an incumbent, but most people are just candidates and don't hold office this is going to be true.
  2. You can likely remove some of the optional: true marks from above, e.g. if all districts will always have an incumbent you could remove that from the District class.
  3. You might want to look into has_and_belongs_to_many in case one person can be a candidate in multiple districts. When doing so you could also mark who the incumbent is in the join table between districts and people, which would allow for more complex scenarios (e.g. multiple incumbents, historical data, etc.)

Upvotes: 1

Related Questions