This question already has answers here:
Rails association with multiple foreign keys
(7 answers)
Closed 7 years ago.
I have two models Game and Team:
class Game < ActiveRecord::Base
belongs_to :home_team, class_name: 'Team', required: true
belongs_to :visitor_team, class_name: 'Team', required: true
end
and
class Team < ActiveRecord::Base
has_many :games
end
The relationship has_many :games doesn't work (I would have to specify class_name but in this case I have two class names). I have to differentiate home and visitor.
Any idea on how to design this?
Thanks
has_many will look for a foreign key with the same name as the class you're working in.
In this case, Rails will assume there is a team_id in the associated Game class, linking back to the Team.This isn't the case, so you need to be more explicit.
In this case, you also don't have a single link from a Game back to a Team. There are two different links between Game and Team, so you need to represent both of these links as associations:
class Team < ActiveRecord::Base
has_many :home_games, class_name: 'Game', foreign_key: :home_team_id
has_many :visitor_games, class_name: 'Game', foreign_key: :visitor_team_id
end
If you want to get all games for a given team, regardless of whether they are home or visitng games, you can define a method:
class Team < ActiveRecord::Base
has_many :home_games, class_name: 'Game', foreign_key: :home_team_id
has_many :visitor_games, class_name: 'Game', foreign_key: :visitor_team_id
def games
Game.where('home_team_id = ? or visitor_team_id = ?', id, id)
end
end
You have to specify the foreign_key, assuming you have columns home_team_id and visitor_team_id in games table:
has_many :home_games, class_name: 'Game', foreign_key: :home_team_id
has_many :visitor_games, class_name: 'Game', foreign_key: :visitor_team_id
Related
I'm trying to create a Referral program on a Rails app and I struggle with the relationships.
My Referral model is pretty simple : godfather_id, godson_id, state
Both godfather and godson ids references an User, which can have many godsons but only one godfather.
class Referral < ApplicationRecord
belongs_to :user
belongs_to :godson, class_name: 'User'
end
The issue comes in my User model. I wan't to be able to do user.godsons to get an array of godsons Users and user.godfather to get the godfather User.
I tried a few things and I think those two where the closest to what I need to do (User model simplified for the example).
class User < ApplicationRecord
has_many :referrals
has_many :godson, -> { where(godfather_id: id) }, through: :referrals
has_one :godfather, -> { where(godson_id: id) }, through: :referrals
end
class User < ApplicationRecord
has_many :godson_relations, class_name: 'Referral', foreign_key: 'godson_id'
has_many :godsons, through: :godson_relations
has_one :godfather_relation, class_name: 'Referral', foreign_key: 'godfather_id'
has_one :godfather, through: :godfather_relations
end
I'm really unsure about how to materialize this relationship, any help will be appreciated 🙏
To make an actual self-referential assocation you would just add a column on the users table that points back to the same table:
class AddGodfatherToUsers < ActiveRecord::Migration[6.1]
def change
add_reference :users, :godfather, null: true,
foreign_key: { to_table: :users }
end end
class User
belongs_to :god_father,
class_name: 'User',
optional: true,
inverse_of: :god_children
has_many :god_children,
class_name: 'User',
foreign_key: :god_father_id
inverse_of: :god_father
end
If you must store Referalls as a separate table you were kind of on the right track but you got the foreign keys backwards:
class Referral < ApplicationRecord
# you better be explicit here or its going to get extremely confusing
belongs_to :godfather, class_name: 'User'
belongs_to :godson, class_name: 'User'
end
class User < ApplicationRecord
has_many :referrals_as_godfather,
class_name: 'Referral', foreign_key: 'godfather_id'
has_one :referral_as_godson,
class_name: 'Referral',
foreign_key: 'godfather_id'
has_many :godsons, through: :referrals_as_godfather
has_one :godfather, through: :referral_as_godson
end
It should be noted that has_one in no way guarentees that a user can have only one referral (and thus one godfather). It just adds a LIMIT 1 to the query. You would have to enforce that with a uniqueness constraint and validations.
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.
I have one user model and one viewed_contractor model. I am treating user model as customer and contractor. customer can view many contractors by visiting their respective profile.Contractor can be viewed by many customers. I have customer_id and contractor_id in my viewed_contractor. I want to handle this relation as has_many through. Is it possible thorough has_many through?
It is possible. First, you'd need to specify the class_name option for the belongs_to associations in your ViewedContractor model so that they both refer to your User class. Then you could specify the has_many through: relations in your User model.
Something like this should work:
# viewed_contractor.rb
class ViewedContractor < ActiveRecord::Base
belongs_to :contractor, class_name: 'User', foreign_key: :contractor_id
belongs_to :customer, class_name: 'User', foreign_key: :customer_id
end
# user.rb
class User < ActiveRecord::Base
has_many :viewed_contractors_as_contractor, class_name: 'ViewedContractor', foreign_key: :contractor_id
has_many :viewed_contractors_as_customer, class_name: 'ViewedContractor', foreign_key: :customer_id
has_many :visited_contractors, through: :viewed_contractors_as_customer, source: :contractor
has_many :visited_customers, through: :viewed_contractors_as_contractor, source: :customer
end
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
OS: Windows 7
Rails: 4.2.0
Hello, If I have two models lets say Game, and Team.
One game can have many teams, but teams can also belong to many games.
I can't seem to figure out the right way to do this, Belongs_to_many does not exist, and I don't think team can have_many games and game can have_many teams.
There are 2 ways to solve this:
One is like #Alex Pan's answer: using has_and_belongs_to_many
The other one is using belongs_to and has_many relation with a junction table.
in game.rb:
has_many :game_team_maps
has_many :teams, through: :game_team_maps
in team.rb
has_many :game_team_maps
has_many :games, through: :game_team_maps
in game_team_map.rb
belongs_to :game
belongs_to :team
In my opinion, the second way is more flexible and easier to maintain. I choose it for my own project.
There is a very detailed and useful cast from railscast that help you to solve this problem: http://railscasts.com/episodes/47-two-many-to-many
has_and_belongs_to_many
I believe is what you need
has_and_belongs_to_many
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
Or you create a joining model (e.g. TeamMembership), that belongs_to Team and Game
You want to use the has_and_belongs_to_many association.
Your models should look like this:
class Games < ActiveRecord::Base
has_and_belongs_to_many :teams #append this line to your model
end
class Teams < ActiveRecord::Base
has_and_belongs_to_many :games #append this line to your model
end
You will also want to create a join table for Games and Teams like so:
class CreateGamesTeams < ActiveRecord::Migration
def change
create_table :gamess_teams do |t|
t.integer :game_id
t.integer :team_id
end
end
end
Remember to run rake db:migrate to create this join table.
While has_and_belongs_to_many is certainly a valid option, most "games" that I know of have a home team and an away team, so it might make sense to do something like:
class Game < ActiveRecord::Base
belongs_to :home_team, class_name: 'Team'
belongs_to :away_team, class_name: 'Team'
end
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
end
You could also model it with a has_many :through:
class Game < ActiveRecord::Base
has_many :team_games
has_many :games, through: :team_games
end
class TeamGame < ActiveRecord::Base
belongs_to :game
belongs_to :team
scope :home, -> { where(home: true) }
scope :away, -> { where(home: false) }
end
class Team < ActiveRecord::Base
has_many :team_games
has_many :games, through: :team_games
has_many :home_games, -> { TeamGame.home }, through: :team_games, source: :game, class_name: 'TeamGame'
has_many :away_games, -> { TeamGame.away }, through: :team_games, source: :game, class_name: 'TeamGame'
end