GAEで簡単な多対多的なモノ
db.ReferenceProperty(db.Mode)を使って、テーブルを作る。
(http://ondras.zarovi.cz/sql/demo/)
こんな感じに、テーブルを設計した。MySQL用のER図なので、フィールドの名前が違うけどなんとなくやりたい事は通じると思う。っていうか、ワカレ。
左のTableというテーブル内のid_Coreとid_Tagが、ReferencePropertyで定義されるFKとなる。
モデルは
class Table(db.Model): ref_core = db.ReferenceProperty(Core) ref_tag = db.ReferenceProperty(Tag) create_at = db.DateTimeProperty(auto_now_add = True)
と言う風に書いてみた。
やりたい事
- 文章(以下Core)と一緒にメタデータ(以下Tag)を保存する
- Coreに対して、複数のTagを付けられるようにしたい
- 同じ内容のTagは使い回したい
- Tagから、Coreを検索したい
Tagを、classとしたいのは、メタデータに対して使用頻度などのメタメタデータを付加するため。また、今後Tagエンジンを追加する際にTag情報だけのDBをたてやすくする為
実装してみる
気づいてしまえば、なんでこんな簡単なことを気づかなかったのだろうと思う。1時間ほどで出来てしまった。(ユーザー情報がめんどくさかったので実装からは外した)
# main.py # encoding:utf-8 #! /usr/bin/env python import wsgiref.handlers from google.appengine.ext import db from google.appengine.ext import webapp from google.appengine.ext.webapp \ import template class Core(db.Model): body = db.StringProperty(required = True) create_at = db.DateTimeProperty(auto_now_add = True) class Tag(db.Model): body = db.StringProperty() create_at = db.DateTimeProperty(auto_now_add = True) class Table(db.Model): ref_core = db.ReferenceProperty(Core) ref_tag = db.ReferenceProperty(Tag) create_at = db.DateTimeProperty(auto_now_add = True) def tagSplit(str): """文字列をコンマ区切りに配列で返す""" return str.split(',') class index(webapp.RequestHandler): def get(self): tables = db.GqlQuery( 'SELECT * from Table ORDER BY create_at DESC') cores = db.GqlQuery( 'SELECT * from Core ORDER BY create_at DESC') tags = db.GqlQuery( 'SELECT * from Tag ORDER BY create_at DESC') tableObj = Table() data_arr, num = [], 0 # すべてのcoreデータで処理を回す for c in cores: tag_list = [] # tableデータ内でcore毎にtagを取り出しtagListを作成する for i in tableObj.all().filter('ref_core = ', c): if i.ref_core.key()==c.key(): tag_list.append(i.ref_tag) # coreをKeyにして、TagListをappend data_arr.append({ 'core' : c, 'tag' : tag_list }) values = { 'tables' : tables, 'cores' : cores, 'tags' : tags, 'core_cnt' : cores.count(), 'tag_cnt' : tags.count(), 'table_cnt' : tables.count(), 'core_list' : data_arr, } self.response.out.write( template.render('main.html', values)) def post(self): # コアのみ保存する core = Core(body = self.request.get('core')) core.put() tt = Table() # 複数タグ対応 for i in tagSplit(self.request.get('tag')): tag = Tag(body = i) tag.put() table = Table( ref_tag = tag, ref_core = core ) # tagとtableをタグ毎にcoreと結びつけて保存する table.put() self.redirect('/') def main(): app = webapp.WSGIApplication([ (r'.*', index)], debug=True) wsgiref.handlers.CGIHandler().run(app) if __name__ == '__main__': main()
<H1>mrs test</H1> <hr> <form action="" method="post" accept-charset='utf-8'> <p>Core: <input type="text" name="core" value="" id=""></p> <p>Tag: <input type="text" name="tag" value="" id=""></p> <p><input type="submit" value="save"></p> </form> {% for i in core_list.values %} {{i.core.body}}<br> {% for ii in i.tag %} [{{ii.body}}] {% endfor %}<br> {{i.core.create_at}} <p> {% endfor %} <hr> core数:{{core_cnt}}<br> tag数:{{tag_cnt}}<br> table数:{{table_cnt}}<br>
見苦しくて申し訳ない。
テーブルを元にして、coreとTagを取得するとCoreの数がTable分増えてしまうので、HashのKeyにCoreのKeyを使用してCoreの数分だけのデータを生成した。
Datastoreのドキュメントには、逆参照のサンプルが載っていたがうまく動かなかった。現在は、Coreの数だけTableに総当たりで探しにいっているのでこのやり方はまずいと思う。CoreをSet型で作って、TagをAddしていけば、CoreとTableを一回ずつの走査になるかと思ったけどやり方がよくわからなかったので、泥臭いことをしてしまった。
今後の課題
- 逆参照のやり方を身につける
- Set型を使いこなして多対多の関連をシンプルに抜き出す
- Tagから、使用されているCoreを逆参照を用いてfilterする
- Tagが毎回作られているので、get_or_insertを使うようにする
追記
フィルター有無でスピード計ってみた。
開発コンソール上で計ったので正しくないかも知れない
import datetime import wsgiref.handlers from google.appengine.ext import db from google.appengine.ext import webapp from google.appengine.ext.webapp \ import template class Core(db.Model): body = db.StringProperty(required = True) create_at = db.DateTimeProperty(auto_now_add = True) class Tag(db.Model): body = db.StringProperty() create_at = db.DateTimeProperty(auto_now_add = True) class Table(db.Model): ref_core = db.ReferenceProperty(Core) ref_tag = db.ReferenceProperty(Tag) create_at = db.DateTimeProperty(auto_now_add = True) tim1 = datetime.datetime.now() for h in Core.all(): print h.body for i in Table.all().filter('ref_core =', h): print "["+i.ref_tag.body+"]" print '' tim2 = datetime.datetime.now() tim2d = tim2-tim1 for h in Core.all(): print h.body for i in Table.all(): if i.ref_core.key() == h.key(): print "["+i.ref_tag.body+"]" print '' tim3 = datetime.datetime.now() tim3d = tim3 - tim2 print '---' print tim2d print tim3d print Core.all().count() print Table.all().count()
結果は
--- 0:00:00.401738 0:00:00.781612 17 14
と言う事で、14個程度しか無いにもかかわらず倍近く違うので、やっぱ総当たりは良くないよね。
上のソースも、フィルターありに変えました。