Reputation: 9335
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
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
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