Rails的协会 - 问题改变价值观,而过分的缓存! [英] Rails associations - problems with altering values, and too much caching!
问题描述
假设我有一个卡片游戏应用程序,其特点是播放器
模型,其中有一个动作
整数列;和卡
模式。玩家可以扮演他们自己的存储卡,花费一个动作;一个特定的卡补助时,它的打了两个动作。
Suppose I've got a card-game app, which features a Player
model, which has an actions
integer column; and a Card
model. A player can play a card they own, which costs an action; one particular card grants two actions when it's played.
如果我code此如下:
If I code this as follows:
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
...那么播放器#play_card
的净效应是递减动作
1。只有这样,我发现使双方更改应用到同一个对象,从而导致1行动的净增量,是这样定义的功能:
... then the net effect of Player#play_card
is to decrement actions
by 1. The only way I've found to make both changes apply to the same object, thereby resulting in a net increment of 1 action, is to define the functions like this:
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 that turns a single database write into two writes and a read! Surely there must be a better way. What am I missing?
推荐答案
在您的code要装入的表玩家在同一行,但同时你期待的第一个版本的Rails足够聪明地认识到,它在内存中已经加载此行,铁轨不这样的。所以,当你在播放器发出+ = 2确实他+对另一个实例比你做了一个2 = - = 1
In the first version of your code you are loading the same row of the table players but while you are expecting rails to be smart enough to recognize that it has already load this row in memory, rails doesn't work that way. So when you are issuing a +=2 on player it does he +=2 on another instance than the one on which you have done -=1.
我设置一个小例子来说明,有太多相同的行实例:
i've setup a little example to show that there are too 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
所以最后你还没有实例保存与+ = 2应用,这里只有一个与保存在-1
So finally as you haven't save the instance with the +=2 applied, there's only the one with the -1 that is saved
更新
您可以尝试欺骗轨玩家使用的同一个实例的所有道路。这是一个有点难看,但它的工作原理。
You can try to trick rails to use the same instance of player all the way. This is a little bit 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 input those 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 right number of actions in player, and in the logs card is only loaded 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
要总结一下整个事情,我会说,有一些错误在code设计。如果我没有理解好,你想的是一个表行的每个AR实例是在ObjectSpace中相同的对象,但我想,如果轨道是构建方式,它会引入怪异的行为,你可以工作在半支持对象改变在验证等挂钩。
To sum up the whole thing, I would say that there's something wrong in your code design. If i understand well,what you would like is that every AR instance of a table row is the same object in the ObjectSpace, but I guess that if rails was build that way it would introduce strange behaviors where you could work on half backed object changed in validations and other hooks.
这篇关于Rails的协会 - 问题改变价值观,而过分的缓存!的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!