CouchDB の show フォーマットを使った時の罠

1時間ぐらいはまった。

結論。CouchDBでドキュメントを更新(HTTP PUT)をするときに、ドキュメントが _revisions メンバーを持つ場合、厳しいリビジョンチェックが行われるので注意。


(1) こんな具合に、ドキュメントのJSONをHTMLに埋め込むようなテンプレートを使う。

// template/test.html
var doc =  <%= toJSON(page) %>;

(2) このテンプレートを、show フォーマット機能を使ってHTML出力する。

function(doc, req) {
  // !json templates.page
  // !code vendor/couchapp/template.js
  // !code vendor/couchapp/path.js
  if( doc ){
    try{
      var html = template(templates.page, {
                            page : doc,
                            assetPath: assetPath()
                          });
      return {body:html};

(略)

この場合、page オブジェクト(doc) は、_revisions というプロパティを持つようだ。0.9.0 からだと思いまする。

{"_id":"pages:top","_rev":"1-1962362123", 
  // (略)
 "_revisions":{"start":1,"ids":["1962362123"]}}

で、(1)で実際にはき出されるコードは、次のようになる。

var doc = {"_id":"pages:top","_rev":"1-1962362123", 
  // (略)
 "_revisions":{"start":1,"ids":["1962362123"]}}

ここで jquery.couch.db オブジェクトを使って(CouchAppの初期化に参照可能なデータベース接続オブジェクト)、saveDocメソッド経由で、ドキュメントを保存する。

app.db.saveDoc(doc);

これは、成功する。しかし、jquery.couch.db では、次のように、保存の成功時に、_id, _rev 自体は更新するが、_revisions はケアしない。つまり、上記のコード実行後、docオブジェクトは次のような状況になりうる。

{"_id":"pages:top","_rev":"2-2038989889", 
  // (略)
 "_revisions":{"start":1,"ids":["1962362123"]}}

_rev が 2-xxxxxx となっているので、本来 _revisions は {"start": 2, "ids": ["2038989889", "1962362123"]} となっていなければならないが、そうはなっていないのだ。

で、こんな状態で再び、

app.db.saveDoc(doc);

と投げると、409 Conflict が起こりました。

対策は簡単で、show フォーマトでテンプレート適用前に、_revisions をundefinedにすると、JSONに出力されなくて、厳密なリビジョンチェックが行われなくなります。

function(doc, req) {
  // !json templates.page
  // !code vendor/couchapp/template.js
  // !code vendor/couchapp/path.js
  if( doc ){
    try{
      doc._revision = undefined;   // <-- _revisions はクライアントに返さない。
      var html = template(templates.page, {
                            page : doc,
                            assetPath: assetPath()
                          });
      return {body:html};