Confusion about has_many / belongs_to


#1

I have a table called players and its model Player, and I have a table
called games and its model Game.

In the games table I have:

player1_id int(11)
player2_id int(11)

I want the tables to be associated so that I can access the player
objects using table.player1 and table.player2 rather than having to
lookup the objects based on the ids.

I am not sure how to go about this but I suspect I need to use either
has_many or belongs_to or one of the mixed versions in my Game/Player
models.

I would be very grateful if someone could shed light on what I need to
do here.

Thank you
Andy


#2

Well, if a game has more than one player, and your players can take part
in
more than one game (that’s an assumption, so it could be wrong;), then
what
you really have is a has_and_belongs_to_many relationship. That would
give you
a games_players join table.

In fact, if you wanted to maintain separate player meta data, for each
game
they participated in, as well as game-specific meta data, you could
actually
structure this relationship as a has_many :through. This would probably
change
the games_players table into a participants table (or something
similar). And
you would put:

has_many :games, :through => :participants # in the Player model
has_many :players, :through => :participants # in the Game model

and in the Participant model

belongs_to :game
belongs_to :player

-Brian


#3

A player can only participate in a single game, which should make the
situation easy but I can’t quite figure it out…

Does the Game model has_many :player ?

Does the Player model belongs_to :game?

and once thats sorted out would I be able to access those associated
objects with game.player1 and game.player2 ?

That is, does rails know that because games has player1_id and
player2_id that games.player1 and games.player2 are objects from the
players table?

You can see I am confused! :wink:

-Andy

Brian V. Hughes wrote:

Well, if a game has more than one player, and your players can take part
in
more than one game (that’s an assumption, so it could be wrong;), then
what
you really have is a has_and_belongs_to_many relationship. That would
give you
a games_players join table.

In fact, if you wanted to maintain separate player meta data, for each
game
they participated in, as well as game-specific meta data, you could
actually
structure this relationship as a has_many :through. This would probably
change
the games_players table into a participants table (or something
similar). And
you would put:

has_many :games, :through => :participants # in the Player model
has_many :players, :through => :participants # in the Game model

and in the Participant model

belongs_to :game
belongs_to :player

-Brian


#4

On Apr 7, 2006, at 9:07 AM, Andrew C. wrote:

That is, does rails know that because games has player1_id and
player2_id that games.player1 and games.player2 are objects from the
players table?

You can see I am confused! :wink:

-Andy

I believe what you actually need is a has_one relationship. Although
the game does have many players, the way the table is set up, it only
has one player1 and one player2. Try this:

class Game < ActiveRecord::Base
has_one :player1, :class_name => ‘Player’
has_one :player2, :class_name => ‘Player’
end

You need to specify the class name because otherwise it will look for
a class called Player1 which of course doesn’t exist. You should then
be able to use game.player1 or game.player2 to access the players.

However, you may find this database design will cause problems later.
If, for example, you want to support more than two players, you will
need to add more columns for player3, player4, etc. Then what if you
want to support, say 16 players. That is a lot of columns and could
get messy! If this is at all a possibility, you should go with a true
one-to-many relationship.

You would do this by putting a game_id column in the players table
and removing the player1 and player2 columns from the games table.
You could then use the “has_many :players” relationship method in
your Game model. You can then grab the players with game.players[0]
or game.players[1], etc. This also allows you to iterate through all
players instead of having to refer to each one individually.

Ryan


#5

On Apr 7, 2006, at 12:40 PM, Andrew C. wrote:

I setup the model as suggested, identically and still have player1_id
and player2_id defined the players table, but now it seems
game.player1
and game.player2 always refer to the same object, sort of like player2
is an alias of player1. I suspect has_one is causing this
relationship…

Sorry about that, I should have used “belongs_to” instead of
“has_one” in my last e-mail. I tried this and it works:

class Game < ActiveRecord::Base
belongs_to :player1, :class_name => ‘Player’, :foreign_key =>
‘player1_id’
belongs_to :player2, :class_name => ‘Player’, :foreign_key =>
‘player2_id’
end

The problem was “has_one” uses the game_id column in the players
table to do the matching. The players.game_id column is actually
unnecessary if you are doing it this way since it will use the
player1_id and player2_id columns to do the matching.

Ryan


#6

Ryan, I was really hoping your first suggestion would work as it sounded
like a great way to implement this being that I have no plans to expand
for more players down the road ( its intended for head-to-head only ).

I setup the model as suggested, identically and still have player1_id
and player2_id defined the players table, but now it seems game.player1
and game.player2 always refer to the same object, sort of like player2
is an alias of player1. I suspect has_one is causing this
relationship…

However, it appears I already did setup game to have a has_many
relationship with the players table and indeed I can access them via
game.players[0] and game.players[1] – I had no idea I could so this is
a definite improvement and should prove useful.

Thank you very much, its all getting a little clearer to me now.

-Andy

Ryan B. wrote:

On Apr 7, 2006, at 9:07 AM, Andrew C. wrote:

That is, does rails know that because games has player1_id and
player2_id that games.player1 and games.player2 are objects from the
players table?

You can see I am confused! :wink:

-Andy

I believe what you actually need is a has_one relationship. Although
the game does have many players, the way the table is set up, it only
has one player1 and one player2. Try this:

class Game < ActiveRecord::Base
has_one :player1, :class_name => ‘Player’
has_one :player2, :class_name => ‘Player’
end

You need to specify the class name because otherwise it will look for
a class called Player1 which of course doesn’t exist. You should then
be able to use game.player1 or game.player2 to access the players.

However, you may find this database design will cause problems later.
If, for example, you want to support more than two players, you will
need to add more columns for player3, player4, etc. Then what if you
want to support, say 16 players. That is a lot of columns and could
get messy! If this is at all a possibility, you should go with a true
one-to-many relationship.

You would do this by putting a game_id column in the players table
and removing the player1 and player2 columns from the games table.
You could then use the “has_many :players” relationship method in
your Game model. You can then grab the players with game.players[0]
or game.players[1], etc. This also allows you to iterate through all
players instead of having to refer to each one individually.

Ryan