0

mongooseにschema関係なくなんでも保存する

mongooseはスキーマで宣言されていないkey:valueは保存してくれない。MongoDBっぽくないけど安全ではある。
でも、webhookで色々なjsonがサーバープッシュされてくるAPIのデータを全部保存したい場合に困る。例えばtwitterやjawbone upのstream APIとかを、mongooseでスキーマレスになんでも保存したい。


方法1 Mixedを使う


調べると、Schema.Types.Mixedを使えとか書いてある。

var Any = new Schema({ any: Schema.Types.Mixed });
ただこの方法だと、なんでも保存できるanyの中に保存しろって事なんだけど、documentの中の深い部分を検索する事になってめんどい。


方法2 mongo driverを直接使う


mongoose.connections配列がnode-mongodb-nativeなので、ここに直接insertすればschema無いデータも保存できる。

例として、jawbone-up24がwebhookでプッシュしてくるjsonを全部eventsというcollectionに保存する。


model定義

## 空のschemaを作る
eventSchema = new mongoose.Schema

## 直接insert
eventSchema.statics.insert_webhook = (data, callback = ->) ->
mongoose.connections[0].collection('events').insert data, callback

mongoose.model 'Event', eventSchema
スキーマ関係なく直接保存するメソッドをmongooseのmodelに生やした。
mongooseのmodel Eventにスキーマ登録すると、mongoでは複数形のevents collectionになる


expressでwebhook受信してmongo driverに直接保存

Event = mongoose.model 'Event'
app.use bodyParser.json()

app.post '/webhook', (req, res) ->

## jawboneはevents[0]にデータが入ってるので、それを保存する
Event.insert_webhook req.body.events[0], (err, res) ->
console.log "保存した" unless err

return res.end "ok"


この方法だとcontroller側から使う部分もmongooseっぽいし、query投げるのもmongoose風に普通に書ける

eventSchema.statics.last_move_of_user = (user_id, callback) ->
@find
user_xid: user_id
type: 'move'
.sort
timestamp: 'desc'
.limit 1
.exec callback

0

express+socket.io+mongooseのテンプレ作った

作った
https://github.com/shokai/express-template

簡単なチャット。チャットログはMongoDBに保存する。
https://express-chat-template.herokuapp.com/


ガチwebサービスではなくゆるふわプロトタイピング用のテンプレになっている。expressあんまりよくわかってないから変な所あったらissueとか建てて盛大にdisって欲しい。
基本的にgeta6/coahを参考にしてるんだけど、ブラウザの為にcoffeeをコンパイルしてjsにするとか諸々の強烈なasset pipelineを取り外した。

構成はこんな感じ

  • express 4.4
  • socket.io 1.0
  • mongoose 3.8
  • coffee-script
  • ブラウザもcoffee-script.js
  • herokuにデプロイ
  • mocha + supertest + Travic CI
  • gruntでwatchしてcoffeelintとmocha回す


ブラウザでcoffeeコンパイルして実行してるけど、文法エラーはlintかけてるし、実行時エラーはコンソールにも下の様にちゃんと出るからsourcemapとかいらないしでだいぶすっきり構成になったと思う。

0

mongooseのvirtual attributes

Query投げて返ってきたDocumentsが関数を持っていて欲しい時にVirtual Attributes使う。

  • スキーマ.virtual(‘名前’).get(function(){ /**/ })
  • スキーマ.virtual(‘名前’).set(function(){ /**/ })
でgetter/setterを付けれる。

例えば、MongoDBにはidしか保存しないようにして、permalinkのURL等はidから組み立てる場合、そういう関数はmodelに持たせたい。


実装例

RecipeSchema.virtual(‘url’).get に関数を登録しておくと、 doc.url で呼び出せる。

mongoose_virtual_attr.js
var _ = require('underscore');
var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/test', function(err){
if(err){
console.error(err);
process.exit(1);
}
});

var Schema = mongoose.Schema, ObjectId = Schema.ObjectId;

var RecipeSchema = new Schema({
id : {type: String, unique: true},
title : {type: String},
created_at : {type: Date, default: Date.now}
});
RecipeSchema.virtual('url').get(function(){
return 'http://shokai.org/'+this.id;
});

var Recipe = mongoose.model('Recipe', RecipeSchema);
Recipe.latests = function(num){
return this.find().sort('created_at', -1).limit(num);
};

var recipe = new Recipe({id: '29jg', title: '肉じゃが'});
recipe.save();

Recipe.latests(3).exec(function(err, docs){
if(err){
throw err;
}
else{
_.each(docs, function(doc){
console.log(doc.url);
});
}
mongoose.disconnect();
});


% npm install mongoose underscore
% node mongoose_virtual_attr.js
http://shokai.org/29jg


というのがやり方わかんないんだよねぇ・・と相談したらmyatsumoto氏が教えてくれた。

0

HerokuでMongoDB+Node.js

HerokuでMongoDB+Sinatraと同じ、1行メモ的なものをNode.js+Express+mongooseで作った。


mongooseはmongoidと同じ雰囲気のQueryチェインが使えて便利。


MongoDBに接続、スキーマ定義

アドオンのMongoLabかMongoHQを入れてたらそっちに接続するようにした。
var mongoose = require('mongoose');
mongoose.connect(process.env.MONGOLAB_URI || process.env.MONGOHQ_URL || 'mongodb://localhost/memo', function(err){
if(err){
console.error(err);
process.exit(1);
}
});

var Schema = mongoose.Schema, ObjectId = Schema.ObjectId;

var MemoSchema = new Schema({
body : {type: String},
created_at : {type: Date, default: Date.now}
});

var Memo = mongoose.model('Memo', MemoSchema);

Memo.latests = function(num){
return this.find().sort('created_at', -1).limit(num);
};

Memo.find_by_id = function(id){
return this.find({'_id':id});
};


読み書き

modelのオブジェクト作ってsaveするとMongoDBに保存される。
var mongoose = require('mongoose');
var Memo = mongoose.model('Memo');

var m = new Memo({body: "はい"});
m.save();

代入しても良い。Schema定義した時にgetterが生成されている。
var m = new Memo();
m.body = "はい";
m.save();


Queryを繋げて書けて良い。
var _ = require('underscore');
var mongoose = require('mongoose');
var Memo = mongoose.model('Memo');

Memo.where(body: /はい/).sort('created_at', -1).limit(30).exec(function(err, docs)){
if(err){
console.error('error');
}
else{
_.each(docs, function(i){
console.log(i.body + ' - ' + i.created_at);
});
}
});


さっきschema定義の時にmodelに付けておいたメソッドを使う
Memo.latests(3).exec(function(err, docs){
if(err) console.error(err);
else{
_.each(docs, function(d){
console.log(d.body + ' - ' + d.created_at);
});
}
});

データの出し入れはcontrollerviewのコードが参考になるかも。
async.parallelで2つのクエリを同時に投げて結果待ちしたりしてみた。なんとなく速そう。


jadeとhamlのちがい

にはまった。

この部分

hamlだとこう書くけど
%ul
- memos.each do |memo|
%li
#{memo.body} -
%a(:href => "/#{memo.id}") #{memo.created_at}


jadeだとliと同じ行に書かないと、下のaタグの中身の変数(memo.created_atとか)が展開されなくなる。
ul
- each memo in memos
li #{memo.body} -
a(href="/#{memo.id}") #{memo.created_at}