代码之家  ›  专栏  ›  技术社区  ›  Chowlett

Rails关联-改变值的问题,以及太多的缓存!

  •  0
  • Chowlett  · 技术社区  · 16 年前

    Player 模型,它有一个 actions 整数列;和 Card 模型。玩家可以玩自己拥有的一张牌,这需要一个动作;一张特定的牌在玩的时候会给予两个动作。

    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
    

    ... 那么 Player#play_card 行动 按1。我发现对同一个对象进行这两个更改的唯一方法是定义如下函数:

    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
    

    但这会把一个数据库的写操作变成两个写操作和一个读操作!肯定有更好的办法。我错过了什么?

    1 回复  |  直到 16 年前
        1
  •  0
  •   hellvinz    16 年前

    在您的代码的第一个版本中,您正在加载表播放器的同一行,但是当您希望rails足够聪明,能够识别它已经在内存中加载了这一行时,rails并不是这样工作的。所以当你在player上发出a+=2时,它会在另一个实例上执行he+=2,而不是在你已经执行过的实例上执行-=1。

    我设置了一个小示例来说明同一行的实例太多:

    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
    

    更新

    你可以尝试欺骗rails,让它一直使用player的同一个实例。这有点难看,但很管用。

    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
    

    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"> 
    

    你在player中有正确数量的动作,而在logs卡中只加载一次:

      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
    

    总而言之,我想说你的代码设计出了问题。如果我理解得很好,您希望的是表行的每个AR实例都是ObjectSpace中的同一个对象,但是我猜如果rails是这样构建的,那么它会引入一些奇怪的行为,您可以在验证和其他钩子中处理更改的半备份对象。

    推荐文章