• Home
  • Учебник по ExtJS
  • О сайте
  •  



    Хранение пользовательских профилей

    Оригинал статьи http://blog.appenginefan.com/2008/04/saving-user-specific-data.html

    В одной из веток форума по App Engine развернулась дискуссия о том, как можно обеспечить уникальность каким-то данным. Одним из примеров, где необходимо будет использовать этот подход, является сохранение нового профиля пользователя в хранилище, только в том числе, если он еще не существует. Я хочу предложить способ сделать это для конкретного рассматриваемого случая, и если считаете, что существует более правильный вариант – дайте знать.

    Представим, что существует несколько наборов данных для каждого пользователя (настройки, статистика, история работы с сайтом и тому подобное). Лучшим способом, который я знаю, будет использование уникальной комбинации для первичного ключа модели, в нашем случае это будет пользователь и категория данных:

    key_name = '%s||%s' % (user.email(), category)

    В нашем примере существует проблема, что пользователь может в любой момент сменить свой адрес электронной почты. Несмотря на то, что реально это происходит не так часто, но все-таки эту проблему нельзя снимать со счетов (особенно если приложение работает с аккаунтами доменов, обслуживаемых в Службах Google). Допустим сотрудница может поменять свою фамилию, когда выходит замуж или разводится. Так как после смены адреса первичный ключ уже не будет указывать на нужный нам объект, можно воспользоваться GQL запросом, который вернет нужную информацию.

    Код, представленный ниже, занимается обработкой таких ситуаций. Он создает расширенную модель, которая позволяет работать с данными даже при смене адреса электронной почты. Модель содержит два метода-помощника: Load() и Modify() для удобного доступа к данным и созданию при необходимости нового объекта, К примеру, обновление счетчика по данным, как часто пользователь заходил на определенную страницу, будет выглядеть так:

    
    def modify(x):
        x.count = getattr(x, 'count', 0) + 1
    updated_stats = UserData.Modify(modify, 'stats')
    

    Алгоритм получения и создания объекта состоит из трех шагов (для примера смотрите код метода Load(), приведенный ниже):

    • попытка загрузки объекта с использованием его «стандартного» первичного ключа
    • если объект не был найден, выполнить GQL запрос (обработка изменений адреса электронной почты пользователя)
    • если и в этом случае ничего не найдено – создается новый объект, ассоциированный с пользователем, с использованием стандартного первичного ключа (методом get_or_create во избежание конфликтов)

    Пример реализации приведен ниже:

    
    from google.appengine.api import users
    from google.appengine.ext import db
    
    """Хранение пользовательских профилей."""
    
    class UserData(db.Expando):
     """Расширенная модель, которая хранит определенный набор
        данных заданного пользователя. Содержит два обязательных
        свойства - объект, ассоциированный с пользователем и категория
        хранимых данных (к примеру, "settings"). Класс использует
        первичный ключ из этих обоих свойств для создания уникального
        набора данных с помощью внутренних методов Load и Modify.
     """
     user = db.UserProperty(required=True)
     category = db.StringProperty(required=True)
    
     @staticmethod
     def Load(category='None', user=None):
       """Загрузка пользовательских данных определенной категории
          (например, "settings") из хранилища. Если такие данные не
          существуют, то будет создан новый объект
       """
    
       # Если не передан пользователь, берем параметры текущего.
       # Если текущий не авторизован, функция возвращает None
       if not user:
         user = users.GetCurrentUser()
       if not user:
         return None
    
       # В большинстве случаев достаточно выполнить загрузку по ключу
       key_name = '%s||%s' % (user.email(), category)
       data = UserData.get_by_key_name(key_name)
       if data:
         return data
    
       # Если это не сработало - делаем GQL запрос, в тех случаях, если
       # адрес изменился и мы не можем найти объект
       data = UserData.gql('WHERE user=:1 AND category=:2',
           user, category).get()
       if data:
         return data
    
       # Ok, в хранилище нужных нам данных не обнаружилось, Мы будем
       # создавать новый объект при помощи переданного функции ключа.
       # В теории существует вероятность что произойдет попытка создания
       # дублирующегося объекта (если пользователь изменил адрес в ту же
       # секунду, когда мы создаем новый объект для этого пользователя),
       # но шанс что это случится - ничтожно малый.
       data = UserData.get_or_insert(key_name,
           user=user, category=category)
       return data
    
     @staticmethod
     def Modify(logic, category='None', user=None):
       """Запрашивает объект с данными, вносит в него изменения с помощью
          транзакции и возвращает объект. Параметр logic должен быть функцией,
          возвращающей объект.
       """
    
       # Попытка загрузки объекта из хранилища;
       data = UserData.Load(category, user)
       if not data:
         return None
       key = data.key().name()
       if not key:
         raise 'Ошибка: невозможно найти объект'
    
       # Переключаемся в режим транзакции
       def modify():
         data = UserData.get_by_key_name(key)
         logic(data)
         data.put()
         return data
       return db.run_in_transaction(modify)
    

    One Response to “Хранение пользовательских профилей”

    1. [...] данные, часть вторая Недавно мы рассматривали пример того, как можно сохранять и транзакционно [...]

    Leave a Reply