いろんなもの使ってSBM その2 データアクセス

さて、ブックマークを保存する部分を考えよう。

深く考えずに、ひとまずDBに入れることにする。



データアクセスにはSQLAlchemyを使う。

オブジェクトをテーブルをまたいでマッピングできたり、継承構造をマッピングする方法を選択できたりと柔軟なO/Rマッパーだ。


DB情報保持のためのモジュールを先に作っておこう。

# database.py
from sqlalchemy import *
from sqlalchemy.ext.sessioncontext import SessionContext
meta = DynamicMetaData()
context = SessionContext(create_session)

metaはDBに関する情報を持っている。
contextはスレッドローカルなセッションを保持していて、実際のセッションにはcontex.current でアクセスできる。
どちらもグローバルに使用できるように作られていて、プログラム中では、直接この2つを扱えばいい。



ここで、モデルを考えよう。

主なモデルは2つブックマークとユーザーだ。

ユーザーはブックマークを複数持っている。

ここで、ブックマークはユーザーが自由に入力したものとして扱い、複数ユーザーで共有することはないものとする。

つまり1:N関連となるわけだ。

あとは最近のソーシャルブックマークから適当に引っ張ってこよう。

タグ付けは必須機能と言っていいほど、どのSBMも持っている機能だ。

また、ブックマークにメモ書きできる。

ラベルはユーザー共通としておこう。


u = User()

b = Bookmark()

l = Label()



u.name

u.bookmarks

u.labels

u.password



b.title

b.url

b.memo

b.user

b.labels



l.name

l.bookmarks

ラベルの識別子はラベル名(name)、bookmarkはユーザーごとにurlが識別子、ユーザーはユーザー名が識別子となる。

label = Label.byName(labelname)

user = User.byName(username)

bookmark = user.bookmarkByUrl(url)

bookmarks = user.selectBookmarkByLabel(label)

Label.byNameでは該当するラベルがなかった場合は、その名前で作成したラベルを作ってしまえばいい。

user.bookmarkByUrlも同様に考えることにしよう

としても、既存のものかどうかは知りたいと思うので、それを調べるメソッドも考えよう。

Label.exists(labelname)

User.exists(username)

user.hasBookmark(url)

さてこいつらをテーブルにマッピングしよう。

基盤として、クラスをそのままテーブルに割り当ててみる

from database import meta

user_table = Table("User", meta)

bookmark_table = Table("Bookmark", meta)

label_table = Table("Label", meta)

それぞれに機械的なIDをつける

user_table = Table("User", meta,
                                  Column("user_id", Integer, primary_key=True))
bookmark_table = Table("Bookmark", meta,
                                  Column("bookmark_id", Integer, primary_key=True))
label_table = Table("Label", meta)
                                  Column("label_id", Integer, primary_key=True))

関連付けのために外部キーを付ける

user_table = Table("User", meta,
                                  Column("user_id", Integer, primary_key=True))
bookmark_table = Table("Bookmark", meta,
                                  Column("bookmark_id", Integer, primary_key=True),
                                  Column("user_id", Integer, ForeignKey("user.user_id")))
label_table = Table("Label", meta,
                                  Column("label_id", Integer, primary_key=True))

N:M関連のためにブリッジテーブルを追加する

label_bookmark_table = Table("label_bookmark", meta,
                                  Column("label_id", Integer, ForeignKey("label.label_id")),
                                  Column("bookmark_id", Integer, ForeignKey("bookmark.bookmark_id")))

識別子の列を追加する

user_table = Table("User", meta,
                                  Column("user_id", Integer, primary_key=True),
                                  Column("name", String, unique=True))
bookmark_table = Table("Bookmark", meta,
                                  Column("bookmark_id", Integer, primary_key=True),
                                  Column("user_id", Integer, ForeignKey("user.user_id")),
                                  Column("url", String))
bookmark_table.append_constraint(UniqueConstraints("user_id", "url"))
label_table = Table("Label", meta,
                                  Column("label_id", Integer, primary_key=True),
                                  Column("name", Unicode, unique=True))

追加の情報について列を追加

bookmark_table = Table("Bookmark", meta,
                                  Column("bookmark_id", Integer, primary_key=True),
                                  Column("user_id", Integer, ForeignKey("user.user_id")),
                                  Column("url", String),
                                  Column("title", Unicode),
                                  Column("memo", Unicode))
bookmark_table.append_constraint(UniqueConstraints("user_id", "url"))

マッピング
assign_mapperを使って少し楽に使えるようにしておこう。

#database.py
from sqlalchemy import *
from sqlalchemy.ext.sessioncontext import SessionContext
from sqlalchemy.ext.assignmapper import assign_mapper

meta = DynamicMetaData()
context = SessionContext(create_session)
def assgin(*args, **kwargs):
    return assign_mapper(context, *args, **kwargs)

とりあえずそれぞれのクラス定義をしておく

class Bookmark(object):
    pass

class Label(object):
    pass

class User(object):
    pass

クラスとテーブルをマッピング

assgin(User, user_table)
assign(Bookmark, bookmark_table)
assign(label, label_table)

関連をマッピング

assign(Bookmark, bookmark_table,
       properties=dict(user=relation(User, 
                                     backref="bookmarks"),
                       labels=relation(Label, 
                                       secondary=label_bookmark_table,
                                       backref="bookmarks"))