views:

72

answers:

2

I am trying to add a transaction to keep from creating two entities with the same attribute. In my application, I am creating a new Player each time I see a new Google user logged in. My current implementation occasionally creates duplicate players when multiple json calls are made by a new Google user within a few milliseconds. When I add the transaction like the one commented out here, I get various errors. What is the easiest way to ensure that I never create two player entities with the same user_id?

  def get_player_from_user(self, user_id):
    player = Player.all().filter('user_id =', user_id).get()    
    if not player:
        #This can result in duplicate players with the same user_id being created. 
        player = self.create_new_player(user_id)
        #This is what I'm trying to do. 
        #player = db.run_in_transaction(self.create_new_player, user_id=user_id)
    return player

  def create_new_player(self,user_id):
        #Check one more time for an existing user_id match.  
        player = Player.all().filter('user_id =', user_id).get()
        if player:
           return player

        player = Player()
        player.user_id = user.user_id()
        player.put()
        return player
+2  A: 

Maybe you can use key name http://goo.gl/RQrI and get_by_key_name is better than filter.

 def create_new_player(self,user_id):
    key_name = "player/%s" % user_id
    player = Player.get_by_key_name (key_name)
    if player is None:
      player = Player (key_name=key_name, user_id=user_id)
      player.put ()
    return player

With the last comment of Nick, i have updated my code, so the better solution is:

    def create_new_player(self,user_id):
      key_name = "player/%s" % user_id
      player = Player.get_or_insert (key_name=key_name, user_id=user_id)
      return player
sahid
+4  A: 

Use the username (or other identifier) as the key name, and use get_or_insert to transactionally create a new entity or return the existing one. Sahid's code won't work, because without a transaction, a race condition is still possible.

Nick Johnson
Thanks Nick. All I have at initially is the GAE user object for signed in users. Would you recommend using the user.user_id as a key?
Chris
Yes, that makes a fine key for entities.
Nick Johnson
One more question, when I use get_or_insert, instead of ending up with id=1234567 I get something like name=1234567890123456789. How do I ensure that I have an id=... rather than name=... for each new entity?
Chris
Because in order to do get_or_insert, you have to provide a key name. Otherwise, there's no way for it to determine if it should be inserting a new entity, or returning an existing one.
Nick Johnson
Thanks. I was hoping to be able to use one of the attributes as the key to avoid creating duplicates, but I still need an ID created rather than a Name since I am using the ID's in various places already.
Chris
You can't have both key name and ID for the same entity. I'd suggest updating your app to use names where it was using IDs.
Nick Johnson