Rails associations - problems with varying values ​​and too much caching!

Suppose I have a card game application that has a Player model that has an actions integer column; and a Card . The player can play his card, which stands for the action; one specific card provides two actions when playing.

If I encode it like this:

 class Player < ActiveRecord::Base has_many :cards def play_card(card) raise "Not yours!" unless cards.include? card self.actions -= 1 card.play save! end end class Card < ActiveRecord::Base belongs_to :player def play player.actions += 2 end end 

... then the pure effect of Player#play_card is to reduce actions by 1. The only way I found to make both changes relates to the same object, thereby leading to a clean increment of 1 action, is to define such functions :

 class Player < ActiveRecord::Base has_many :cards def play_card(card) raise "Not yours!" unless cards.include? card self.actions -= 1 // Stick that change in the Database save! card.play end end class Card < ActiveRecord::Base belongs_to :player def play // Force reload of the player object player(true).actions += 2 // And save again player.save! end end 

But it turns one database into two records and a read! Of course, there must be a better way. What am I missing?

0
source share
1 answer

In the first version of your code, you load the same row of players into the table, but while you expect the rails to be smart enough to recognize that it is already loading this row into memory, the rails do not work that way. Therefore, when you set + = 2 on the player, it + = 2 on a different instance than the one on which you did - = 1.

I have a small example showing that there is too one instance of the same row:

 ruby-1.8.7-p174 > p_instance_1 = Player.first => #<Player id: 1, actions: -1, created_at: "2010-10-13 17:07:22", updated_at: "2010-10-13 17:11:00"> ruby-1.8.7-p174 > c = Card.first => #<Card id: 1, player_id: 1, created_at: "2010-10-13 17:07:28", updated_at: "2010-10-13 17:07:28"> ruby-1.8.7-p174 > p_instance_2 = c.player => #<Player id: 1, actions: -1, created_at: "2010-10-13 17:07:22", updated_at: "2010-10-13 17:11:00"> ruby-1.8.7-p174 > p_instance_1.object_id => 2158703080 ruby-1.8.7-p174 > p_instance_2.object_id => 2156926840 ruby-1.8.7-p174 > p_instance_1.actions += 1 => 0 ruby-1.8.7-p174 > p_instance_2.actions += 1 => 0 

So, since you did not save the instance with + = 2 attached, there will only be one that has -1, which is saved

UPDATE

You can try to trick the rails to use the same player instance. This is a little ugly, but it works.

 class Player < ActiveRecord::Base has_many :cards def play_card(card) raise "Not yours!" unless cards.include? card new_self = card.player card.play new_self.actions -= 1 new_self.save! end end class Card < ActiveRecord::Base belongs_to :player def play player.actions += 2 end end 

so when you enter these commands:

 ruby-1.8.7-p174 > p = Player.first => #<Player id: 1, actions: 0, created_at: "2010-10-14 13:33:51", updated_at: "2010-10-14 13:33:51"> ruby-1.8.7-p174 > p.play_card(Card.first) => true ruby-1.8.7-p174 > p => #<Player id: 1, actions: 0, created_at: "2010-10-14 13:33:51", updated_at: "2010-10-14 13:33:51"> ruby-1.8.7-p174 > p.reload => #<Player id: 1, actions: 1, created_at: "2010-10-14 13:33:51", updated_at: "2010-10-14 13:34:40"> 

You have the correct number of actions in the player, and in the logs the map is loaded only once:

  Player Load (0.5ms) SELECT * FROM "players" LIMIT 1 Card Load (0.2ms) SELECT * FROM "cards" LIMIT 1 Card Load (0.2ms) SELECT "cards".id FROM "cards" WHERE ("cards"."id" = 1) AND ("cards".player_id = 1) LIMIT 1 Player Load (0.1ms) SELECT * FROM "players" WHERE ("players"."id" = 1) Player Update (0.6ms) UPDATE "players" SET "updated_at" = '2010-10-14 13:34:40', "actions" = 1 WHERE "id" = 1 

To summarize everything, I would say that there is something wrong with your code. If I understand well what you would like, each AR instance of a table row is the same object in ObjectSpace, but I assume that if the rails were constructed this way, this would create strange behavior that you could work in on a partially reinforced object in checks and other hooks.

0
source

All Articles