Using two has one relationships to the same model - ruby-on-rails

I'm trying to find the best way to model a game in relation to teams.
The end goal is to be able to call things like:
#game.winner
#game.loser
#team.games
The first two relations are working, but the games one is not. Using has_many (see below), I get ERROR: column games.team_id does not exist which I would normally work around by using whatever the equivalent to :foreign_key => winner_id, but how can I have it checkout both winner_id and loser_id?
Is the only option to create a method in the teams model like so:
def games
won = Game.where(:winner => id)
lost = Game.where(:loser => id)
won + lost
end
So far what I'm doing is:
class Game < ActiveRecord::Base
has_one :winner, class_name: "Team"
has_one :loser, class_name: "Team"
end
class Team
has_many :games
# or something that actually works
end

You didn't say much about the problem. But I think you are working too hard. If a team can play several games and a game includes more than one team, then you you need a many to many relation. This requires a third table, and best practice is a has_many :through relation. It will look something like:
class Game < ActiveRecord::Base
has_many :teams, through: assignment
has_one :winner, class_name: 'Team', through: :assignment, order: 'score DESC'
has_one :loser, class_name: 'Team', through: :assignment, order: 'score ASC'
end
class Team
has_many :games, through: :assignment
end
class Assignment < ActiveRecord::Base
belongs_to :game
belongs_to :team
end
Now you have the winner and loser attributes, but you don't need to roll your own method to count games for a team. Just say team.games.count and similarly game.teams is the teams assigned to the game.

Related

Rails has_many through a HABTM relationship

So I have three models, User, Team, and Game. Currently constructed as such.
class Team < ApplicationRecord
has_and_belongs_to_many :users
has_many :home_games, class_name: 'Game', foreign_key: 'home_team_id'
has_many :away_games, class_name: 'Game', foreign_key: 'away_team_id'
has_many :wins, class_name: 'Game', foreign_key: 'winner_id'
belongs_to :owner, class_name: 'User'
end
class User < ApplicationRecord
has_and_belongs_to_many :teams
has_many :teams_owned, class_name: 'Team', foreign_key: 'owner_id'
has_many :games, through: :teams
end
class Game < ApplicationRecord
belongs_to :home_team, class_name: "Team"
belongs_to :away_team, class_name: "Team"
belongs_to :winner, class_name: "Team", optional: true
end
I want to add an association between users and games. So I can call User.games and Game.users.
I tried adding this:
#in user model
has_many :games, through: :teams
#in team model
has_many :games, ->(team) { where('home_team_id = ? or away_team_id = ?', team.id, team.id) }, class_name: 'Game'
As the api docs said to do. But, when I try to call this association, I get an error that "game.team_id does not exist". Since each game has a home_team_id and away_team_id, but no team_id.
Did I just implement this extremely poorly? Or am I missing something? Any help appreciated.
I would say this isn't a really good solution.
In ActiveRecord you can't actually define associations where the foreign key can potentially be in two different columns like this:
has_many :games, ->(team) { where('home_team_id = ? or away_team_id = ?', team.id, team.id) }, class_name: 'Game'
It definitely won't work as Rails will still join the assocation as JOIN games ON games.team_id = teams.id. Just adding a WHERE clause to the query won't fix that. Since ActiveRecord actually creates a variety of different queries there is no option to simply provide a different join.
A kludge to make this work would be to add an instance method:
class Game < ApplicationRecord
def users
User.joins(:teams)
.where(teams: { id: home_team.id })
.or(Team.where(id: away_team.id))
end
end
As its not an actual association you cant join through it or use an sort of eager loading to avoid n+1 queries.
If you actually want to create a single association that you can join through you would need to add a join table between games and teams.
class Team < ApplicationRecord
# ...
has_many :game_teams
has_many :games, through: :game_teams
end
# rails g model game_team team:belongs_to game:belongs_to score:integer
class GameTeam < ApplicationRecord
belongs_to :team
belongs_to :game
end
class Game < ApplicationRecord
has_many :game_teams
has_many :teams, through: :game_teams
has_many :users, through: :teams
end
This is a better idea since it gives you a logical place to record the score per team.
As an aside if the composition of teams can change and accurate record keeping is important you might actually need additional join tables as the lineup when a game is played may not actually match the current lineup.

Tournament -> Team -> Player Associations

I'm a relative beginner to Rails, but am learning as I go. I'm trying to create a Tournament Entry portal, where a team would enter players for a given tournament. I've done a bit of reading about associations, but am having some trouble wrapping my head around how to apply them in this instance.
As a basic overview:
One tournament, has many teams.
Each team has many players
Therefore one tournament also has many players (through the teams
entered)
Here's my code for this, but I'm not sure it's right because I'm unable to get any tournament_ids associated to players.
(tournament.rb)
class Tournament < ApplicationRecord
has_many :teams
has_many :players, :through => :teams
end
(team.rb)
class Team < ApplicationRecord
belongs_to :tournament
has_many :players
end
(player.rb)
class Player < ApplicationRecord
has_one :team
has_one :tournament, :through => :team
end
Within the Players table there is both team_id & tournament_id fields, however I'm only able to populate the team_id field through association when I try in console.
I'm wondering if there's something amiss with my associations.
The usage of 'belongs_to', 'has_many', 'has_one' depends on the data model in database of course.
If you have team_id foreign key in players table, then you need to define Player class as:
class Player < ApplicationRecord
belongs_to :team
has_one :tournament, :through => :team
end
In addition, I really believe that Tournament <-> Team should have many-to-many association (if team can participate in many tournaments of course). I would suggest adding model TeamTournament and define final model structure as:
class Tournament < ApplicationRecord
has_many :team_tournaments
has_many :teams, :through => :team_tournaments
has_many :players, :through => :teams
end
class TeamTournament < ApplicationRecord
belongs_to :team
belongs_to :tournament
end
class Team < ApplicationRecord
has_many :team_tournaments
has_many :tournaments, :through => :team_tournaments
has_many :players
end
the Player class should have belongs_to associations with Team and Tournament
class Player < ApplicationRecord
belongs_to :team
belongs_to :tournament
end
OK. I assume your question is about your models associations rather than how to set up association for getting tournament_id from player and so on. So I'll try to hand you some tips about your project and associations could be set up for it.
As I got your portal idea... You want the tournament to has many teams and the team to has many players. But then you want to get tournament_id from player. I believe you don't want to do that because in real life tournament indeed may "has" some players but every single player don't has to belong to some tournament. He can take part in many tournaments. So you don't need to set up association for that. Same thing with tournament and teams. But since team has the player he has to belong to that team. So you need association for that.
Wrapping up my setup for you will be like:
(tournament.rb)
class Tournament < ActiveRecord::Base
has_many :teams
end
(team.rb)
class Team < ActiveRecord::Base
has_many :players
end
(player.rb)
class Player < ActiveRecord::Base
belongs_to :team
end
And an example about how you can get the tournament where certain team take part in without the direct association:
team = Team.first # just take some team
Tournament.includes(:teams).where(teams: { id: team.id })
The same way you can achieve your other goals (get the tournament certain player belongs to and so on). But such cases don't need associations. Associations are needed when the object relates to another conceptually.

has_many association with multiple foreign keys to the same table

I have two tables, Teams and Games. I am trying to set up the associations for these tables but running into some issues. Here is my Game model with it's associations:
# Game Model
class Game < ActiveRecord::Base
belongs_to :home_team, class_name: "Team"
belongs_to :away_team, class_name: "Team"
belongs_to :winning_team, class_name: "Team"
end
I may be overthinking this but I'm not sure how to set up my Team model to have_many Games.
With a simple has_many :games in my Team model, my tests return the following error:
Team Associations should have many games
Failure/Error: it { should have_many(:games) }
Expected Team to have a has_many association called games (Game does not have a team_id foreign key.)
I see that it's looking for team_id for Game, and since there's no team_id it errors. But in my Game table I have three foreign keys referencing the same class. So would I need to create a has_many for each home_team, away_team and winning_team?
You'll need something like:
class Team < ActiveRecord::Base
has_many :home_games, class_name: 'Game', foreign_key: 'home_team_id'
has_many :away_games, class_name: 'Game', foreign_key: 'away_team_id'
# This seems like a separate thing to me...
has_many :winning_games, class_name: 'Game', foreign_key: 'winning_team_id'
# Do not include winning games, since it would already be included
def games
self.home_games.to_a + self.away_games.to_a
end
end

has_many through multiple sources

I am trying to create a has_many through relation with multiple sources.
For example a game has a home_team and away_team and a tournament has multiple games.
What is the best way to get all teams in the tournament using a has_many through games relation.
Right now my code looks like this:
class Tournament
has_many :teams, :through => :games, :source => :home_team, :uniq => true
end
but I want some way to make it act like:
class Tournament
has_many :teams, :through => :games, :source => [:home_team, :away_team], :uniq => true
end
EDIT: The many to many relationship is not my problem. Is there a good way to get all the teams in the tournament assuming the structure as follows.
class Game
has_and_belongs_to_many :tournaments
belongs_to :home_team, :class_name => Team, :foreign_key => :home_team_id
belongs_to :away_team, :class_name => Team, :foreign_key => :away_team_id
end
class Tournament
has_and_belongs_to_many :games
end
Is there a way to do Tournament.teams?
After spending some time trying to find a built in solution I just ended up writing a custom query called teams in games. It joins teams to games twice through team_1 and team_2 and checks to see if any of the list of game id's are in either of these two joins.
This solution isn't great since it requires multiple queries (One of which is just a huge list of all game ids), but I spent a lot of time trying to come up with another way and couldn't. At least this way works.
I'd love to learn a better way.
Code inside of games:
def self.teams
joined_tables = Team.joins(:home_team).joins(:away_team)
joined_tables.where('games.id in (?) or away_team_games.id in (?)',
select(:id), select(:id)).uniq
end
Define models like this:
class Tournament
has_many :games
has_many :teams, :through => :games
end
class Game
belongs_to :torunament
belongs_to :team
end
class Team
has_many :games
has_many :tournaments, :through => :games
end
And then call in controller, or wherever:
tournament.teams
EDIT:
you could define scope for this kind of problem in you Tournament model. This is more like some custom query, rather it is supported by rails out of the box. Or at least I can't remember at this very moment.
you can look how to use them.
http://guides.rubyonrails.org/active_record_querying.html
http://apidock.com/rails/ActiveRecord/NamedScope/ClassMethods/scope
http://ablogaboutcode.com/2011/02/19/scopes-in-rails-3/
You can build up a query that would get all the teams. You can create any query you like.

ActiveRecord has two association

What is the best way to achieve a has two association with activerecord?
I have a Team and Game models. Each Team will have_many games #team.games. A Game will have two teams #game.hosting_team and #game.opposing_team.
I started out with two belongs_to/has_one associations but then #team.games would only return their home games.
The other option I can think of is using a HABTM and use a validator to ensure there are only records. The only thing missing is keeping track of the home team. It seems like I need a has many through association but I'm not exactly sure...
Thanks for your help.
This is an example of how the two has_many associations look. The problem here is I would have to call team.games and team.opponents to get a full list of their games
class Team < ActiveRecord::Base
has_many :games
has_many :opponents, :class_name => "Team"#, :foreign_key => ""
end
class Game < ActiveRecord::Base
belongs_to :team, :class_name => "Team" #, :foreign_key => "team_id"
belongs_to :opponent, :class_name => "Team" #, :foreign_key => "opponent_id"
end
I'd like something like this but this obviously isn't how belongs_to works.
class Team < ActiveRecord::Base
has_many :games
end
class Game < ActiveRecord::Base
belongs_to :hosting_team
belongs_to :opposing_team
end
My desired api would look like this.
#team.games # return all games home or away
#game.hosting_team # Team
#game.opposing_team # Team
You can probably still model it with the bt/ho associations, and set up games as an accessor method on the team instead of as an association:
class Team < ActiveRecord::Base
def games
Game.find(:conditions => ["home_team_id = ? OR away_team_id = ?", id, id])
end
end

Resources