mongooseの使い方まとめ
随時更新予定
目次
クエリ
動的query
- ToDo
where
とexec
var query; if(screen_name) { query = query.where({ screen_name: screen_name }); } else if(user_id) { query = query.where({ user_id: user_id }); } query.exec(function(err, data) { console.log(data); });
要素数のカウント
query.count
で要素数をカウントできる.limitとskipは無視される.
query.find(function(err, count) { console.log(count); });
_id
uniqueで配列じゃなければなんでも使える
var User = new Schema({ _id: Number, screen_name: String });
埋め込みオブジェクトも使える
var Good = new Schema({ _id: { from: Number, to: Number }});
_id
を明示的に定義しなかったら,ObjectId
という12byteの型で定義される.
ObjectId
には4byteのタイムスタンプが含まれるのでcreated_at
を定義する必要は無い.
var Id = new Schema({ text: String }); mongoose.model('Id', Id); Id = mongoose.model('Id'); var id = new Id({ text: 'hoge' }); console.log(id._id); console.log(id._id.getTimestamp());
結果
5073865056f6147bb1000001 Tue Oct 09 2012 11:05:04 GMT+0900 (JST)
node.js+MongoDBでtwitter apiを使うときに注意すること
こちらになんの変哲もないjsonがあります.
{"id":255113756093329409,"id_str":"255113756093329409"}
これをJSON.parse
すると
{ id: 255113756093329400, id_str: '255113756093329409' }
お分かりいただけるだろうか?不思議な力によりidの下1桁が9から0になってしまった. このidは先程自分が腹減ったとTwitterでつぶやいたもののstatus idなので最近のつぶやきを扱うには何らかの対策が必要である.
対策1: id_strを使う
Twitter APIは64bit整数が扱えない残念な言語の為にidとつくフィールド全てにid_strというidを文字列にしただけのフィールドを持っている. なのでよっぽどのことがない限りこちらを使えばよい.
対策2: MongoDBのNumberLongを使う
MongoDBではNumberLong('255113756093329409')
と書けば64bit整数が扱える.
sortしたい,コレクションが大量にあるのでidに20Byteも使うのは勿体無い等の場合こちらを使えばいいだろう.
みんなのアイドルmongooseでNumberLongを使いたい時は,mongoose-longというモジュールがある.
const mongoose = require('mongoose'); require('mongoose-long')(mongoose); const Schema = mongoose.Schema; var Status = new Schema({ id: Schema.Types.Long }); mongoose.model('Status', Status); Status = mongoose.model('Status'); var status = new Status({ id: '255113756093329409' }); // Stringで渡す console.log(status.id); console.log(status.id.toString(10));
結果
{ _bsontype: 'Long', low_: 155332609, high_: 59398300 } 255113756093329409
しかしこのmongoose-longは不完全で検索,更新ができなかったりするので,mongooseのlib/schema/number.jsあたりとmongoose-longのlib/index.jsを参考にcastForQuery
関数を追加したりする必要がある.
Expressの5行でできるCSRF対策
特殊なウィルス怖いですね.今後CSRF対策してないフォームを公開したらウイルス作成罪で逮捕されるかもしれません. NodeのWEBフレームワークExpress(が使ってるミドルウェアフレームワークのConnect)では数行追加するだけで,セッション毎の固定トークン方式でCSRF対策ができるので紹介する.
基本的にはapp.use(express.session());
とapp.use(app.router);
の間に2つミドルウェアを追加し,ビューのformに隠し項目でトークンを追加するだけである.
// app.js ... app.use(express.session()); app.use(express.csrf()); // 追加 app.use(function(req, res, next) { // 追加 res.locals._csrf = req.session._csrf; next(); }); app.use(app.router); ...
app.use(express.csrf());
の部分はconnectのmiddleware/csrf.js参照.
req.session._csrf
にトークンが追加され,POSTメソッド等でtokenが一致してるかチェックされるようになる.
res.locals._csrf = req.session._csrf;
で_csrfをビューから参照できるようにしている.
後はビューのフォームにトークンを追加する.
// index.jade ... form(method='post') legend= 'CSRF対策済みフォーム' input(type='text', name='text') input(type='hidden', name='_csrf', value=_csrf) // 追加 input(type='submit') ...
Redisで全文検索
台風一過で天気が良いのでnodeとRedisのSETを転置インデックスに使った全文検索を作った.
wikipediaのタイトル一覧(約100万件)をtri-gramで分解しRedisに突っ込んだ結果,約800万件の転置インデックスを挿入するのにおよそ3分,サイズは700MB程度だった.
同じようなことをmongodbでもできるが挿入速度ではRedisが有利だろう.
転置インデックス作成
const redis = require('redis'); const byline = require('byline'); const fs = require('fs'); const client = redis.createClient(); const ngram = function(str, n) { n = n ? n : 3; var grams = []; for(var i = 0; i <= str.length - n; i++) { grams.push(str.substr(i, n).toLowerCase()); } return grams; }; if(process.argv.length < 3) { process.stderr.write('argv error\n'); process.exit(1); } var rs = fs.createReadStream(process.argv[2], { encoding: 'utf8' }); var ls = byline.createLineStream(rs) var titles = []; var iis = []; var index = 0; var ic = 0; ls.on('data', function (data, line) { titles.push('key-' + index); titles.push(data); var grams = ngram(data); // Tri-gram for(var i = 0; i < grams.length; i++) { iis.push(['sadd', grams[i], index]); } index++; if(titles.length > 100000) { ls.pause(); client.mset(titles, function(err, res) { ls.resume(); }); titles = []; } if(iis.length > 500000) { ic += iis.length; ls.pause(); client.multi(iis).exec(function(err, res) { ls.resume(); }); iis = []; } }); ls.on('end', function () { client.mset(titles, function(err, res) { client.multi(iis).exec(function(err, res) { ic += iis.length; console.log(ic); client.end(); }); }); });
検索
const redis = require('redis'); const client = redis.createClient(); const byline = require('byline'); const fs = require('fs'); const ngram = function(str, n) { n = n ? n : 3; var grams = []; for(var i = 0; i <= str.length - n; i++) { grams.push(str.substr(i, n).toLowerCase()); } return grams; }; const search = function(str, n) { var grams = ngram(str, n); var t = new Date; client.sinter(grams, function(err, res) { res = res.map(function(e) { return 'key-' + e; }); client.mget(res, function(err, res) { console.log(res); console.log('----------------------------------------'); console.log('' + res.length + 'results(' + (new Date - t) + 'ms)'); client.end(); }); }); }; if(process.argv.length < 3) { process.stderr.write('argv error\n'); process.exit(1); } search(process.argv[2]);
Debianにrvmでrubyインストールメモ
#apt-get install build-essential libssl-dev libreadline5-dev zlib1g-dev make curl git-core -y $bash < <(curl -s https://rvm.beginrescueend.com/install/rvm) $source ~/.bashrc $rvm install 1.9.2 $rvm use 1.9.2 --default