Webサービスでユーザーの状態をRDBを使って管理している事は多いので、レコードを取得時に無ければ作成といったコードを色んなプロジェクトで見かけます。
Djangoにもテーブルのレコードが有れば取得(SELECT)、無ければレコードの作成(INSERT)を行う get_or_create というメソッドが標準で用意されています。
例えば懸賞サイトでユーザーごとにポイント残高を表示したい場合、ユーザーのレコードが無い場合はデフォルト値である0ポイントになります。
無ければレコード作成という動きは、この0ポイントという情報を わざわざ テーブルに保存するという事ですが
デフォルト値って保存する必要ありますか?
その上、作成(INSERT)は 更新系の処理 です。
更新をするという事は排他制御や2重実行など色々な事を考慮する必要が有ります。INSERTによってロックも発生しますし。
とはいえ単なる get ではモデルのインスタンスが取得できず処理が書きにくいでしょう。
そこで get_or_new です
無ければインスタンスのみ作成してレコード作らない get_or_new を作りましょう。これなら副作用なくインスタンスを取得できます。
コードだとこういう感じになります
# models.py
from django.db import models
class User(models.Model):
point = models.IntegerField('獲得ポイント', default=0)
@classmethod
def get_or_new(cls, pk):
try:
return cls.objects.get(pk=pk)
except cls.DoesNotExist:
return cls(pk=pk)
# view.py
from django.views.generic import TemplateView
from .models import User
class IndexView(TemplateView):
template_name = "index.html"
def get(request, user_id):
user = User.get_or_new(user_id)
self.render_to_response({'user': user})
これなら必要なUserのインスタンスもとれて、もちろん user.point として 0 が返ってきます。他にもメリットがあって
- INSERTを行わないので当然ロックがない
- DBには最初のSELECTしか飛ばないので負荷も殆どない
- デフォルト値のレコードが作成されないのでデータ量が増えない
などなど。
注意点として、get_or_newから取得したデータを元に更新処理を行ってはいけません。2重実行した場合に間違ったデータが最終的に書き込まれます。
今回は参照の話なので、今度更新系の話も書こうと思います。