2

We are about to introduce a social aspect into our app, where users can like each others events. Getting this wrong would mean a lot of headache later on, hence I would love to get input from some experienced developers on GAE, how they would suggest to model it.

It seems there is a similar question here however the OP didn't provide any code to begin with.

Here are two models:

class Event(ndb.Model):
    user = ndb.KeyProperty(kind=User, required=True)
    time_of_day = ndb.DateTimeProperty(required=True)
    notes = ndb.TextProperty()
    timestamp = ndb.FloatProperty(required=True)

class User(UserMixin, ndb.Model):
    firstname = ndb.StringProperty()
    lastname = ndb.StringProperty()

We need to know who has liked an event, in case that the user may want to unlike it again. Hence we need to keep a reference. But how?

One way would be introducing a RepeatedProperty to the Event class.

class Event(ndb.Model):
    ....
    ndb.KeyProperty(kind=User, repeated=True)

That way any user that would like this Event, would be stored in here. The number of users in this list would determine the number of likes for this event.

Theoretically that should work. However this post from the creator of Python worries me:

Do not use repeated properties if you have more than 100-1000 values. (1000 is probably already pushing it.) They weren't designed for such use.

And back to square one. How am I supposed to design this?

Community
  • 1
  • 1
Houman
  • 64,245
  • 87
  • 278
  • 460

3 Answers3

1

RepeatProperty has limitation in number of values (< 1000).

One recommended way to break the limit is using shard:

class Event(ndb.Model):
    # use a integer to store the total likes.
    likes = ndb.IntegerProperty() 

class EventLikeShard(ndb.Model):
    # each shard only store 500 users. 
    event = ndb.KeyProperty(kind=Event)
    users = ndb.KeyProperty(kind=User, repeated=True)

If the limitation is more than 1000 but less than 100k. A simpler way:

class Event(ndb.Model):
    likers = ndb.PickleProperty(compressed=True)
lucemia
  • 6,349
  • 5
  • 42
  • 75
  • Sharding the counter is the best way to do it. This is also recommended in order to avoid too many updates per second on the same entity if many users are liking the post at the same time. – Nick Jan 08 '15 at 19:44
0

Use another model "Like" where you keep the reference to user and event. Old way of representing many to many in a relational manner. This way you keep all entities separated and can easily add/remove/count.

qubird
  • 529
  • 1
  • 6
  • 4
  • May you kindly provide a code snippet like above please? It would be easier to follow your thoughts. – Houman Oct 13 '14 at 11:01
0

I would recommend the usual many-to-many relationship using an EventUser model given that the design seems to require unlimited number of user linking an event. The only tricky part is that you must ensure that event/user combination is unique, which can be done using _pre_put_hook. Keeping a likes counter as proposed by @lucemia is indeed a good idea.

You would then would capture the liked action using a boolean, or, you can make it a bit more flexible by including an actions string array. This way, you could also capture action such as signed-up or attended.

Here is a sample code:

class EventUser(ndb.Model):
    event = ndb.KeyProperty(kind=Event, required=True)
    user = ndb.KeyProperty(kind=User, required=True)
    actions = ndb.StringProperty(repeated=True)

    # make sure event/user is unique
    def _pre_put_hook(self):
        cur_key = self.key        
        for entry in self.query(EventUser.user == self.user, EventUser.event == self.event):
            # If cur_key exists, means that user is performing update
            if cur_key.id():
                if cur_key == entry.key:
                    continue
                else:
                    raise ValueError("User '%s' is a duplicated entry." % (self.user))
            # If adding
            raise ValueError("User Add '%s' is a duplicated entry." % (self.user))
marcoseu
  • 3,892
  • 2
  • 16
  • 35