anon_swe
anon_swe

Reputation: 9335

Ruby on Rails: Use Association For Two Fields?

I currently have two models in my Rails app: Team and Player, like this:

class Team < ApplicationRecord
  has_many :players
end

class Player < ApplicationRecord
  belongs_to :team
end

I want to add a Matchup class to represent two teams playing each other. In normal OOP-land, I might do something like:

class Matchup
  attr_accessor :first_team, :second_team
end

But I'm not 100% sure what the idiomatic Rails way of setting this up is. Some options I'm considering:

1) Use associations: A Matchup has many teams and a Team belongs to many matchups. This is a little awkward, since now I don't get to specify a field for each of first_team and second_team.

2) Stick with OOP approach. Matchup has two fields: first_team and second_team, both referring to Team objects. Since I mainly plan on allowing users to view matchups, I don't need resourceful routing here.

Thanks in advance for any help you can offer!

Upvotes: 1

Views: 797

Answers (2)

max
max

Reputation: 101831

Just placing two foreign key columns on the matchups table might seem like the the simplest solution but the devil is in the details. Since you have two foreign keys you can't just create a has_many :matchups association since Rails can't know which column on matchups corresponds to teams.id. So this leads to really awkward hacks like:

class Team
  has_many :matchups_as_a, class_name: "Matchup"
                           foreign_key: 'team_a'
  has_many :matchups_as_b, class_name: "Matchup"
                           foreign_key: 'team_b'
  def matchups
     matchups_as_a + matchups_as_b
  end
end

Which breaks ordering and a lot of other things.

Or:

Matchup.where('team_a = :x OR team_b = :x', x: team)

Which is not an association and can't be eager loaded for example.

An alternative way of doing this is by creating a join table:

class Matchup
  has_many :matchup_teams
  has_many :teams, though: :matchup_teams
end

# rails g model MatchupTeam match_up:references team:references
class MatchupTeam
  belongs_to :match_up
  belongs_to :team
end

class Team
  has_many :matchup_teams
  has_many :matchups, through: :matchup_teams
end

Which might seem more complex but actually lets you do:

team.matchups
matchup.teams

# Get matchups between team and other_team
matches = Matchups.joins(:teams)
           .where(teams: { id: [team, other_team] })
           .group('matchups.id')
           .having('count(*) = 2')

Upvotes: 0

CWitty
CWitty

Reputation: 4526

A Matchup could belong to two teams. You can specify a different association name and then tell it the class to use.

belongs_to :team_a, class_name: "Team"
belongs_to :team_b, class_name: "Team"

Then a team has many matchups, which makes sense as well.

Upvotes: 3

Related Questions