概要
弊社では一部の社内プロジェクトでSails.jsを利用しています。その中で得られたベストプラクティスやらバッドプラクティスをまとめていこうと思います。
第5回はバッドプラクティスについて。
動作環境
node.js v0.12.7
Sails.js v0.11.2
sails-mysql v0.11.0
Sails.jsのバッドプラクティス
node.jsのMVCフレームワークはRailsのようにデファクトが無い状態です。デファクトがない理由はやはり、どのフレームワークも成熟しているとは言いがたい状態でしょう。Sails.jsはその中でも(たぶん人気がないので)微妙な点が多いです。そういった微妙な点と、その解決策についてまとめてみました。
DeepPopulateが出来ない
Sails.js(のO/R MapperであるWaterline)では、SQLで言うところのJOINをするために以下のように記述します。
Article.findOne(1).populate('tags').then(articles => {
// {id: 1, title: "hoge", tags: [{id: 1, name: "Sails"}]}
});
しかし、populate()
は現時点で1段階のJOINのみ(交差テーブルの時は2段階まで)しか対応していません。なので例えばArticleにwriterをJOINして、さらにwriterの所属企業情報をJOINするというQueryが1度で書けないようになっています。
そのため、2つのQueryを投げて自力でJOINする必要があります。
Article.find().populate('writer').then(articles => {
const IDs = _.map(articles, a => a.writer.company);
return Company.find(IDs);
}).then(companies => {
// ここで自力でarticlesにcompaniesを連結
});
一応、DeepPopulate対応はしばらく前からPullRequestができていて、実装は進んでいるようです。
Model.destroy()
の結果がcolumnNameに対応していない
Waterlineは以下のようにDBスキーマとマッピングするときに、tableName
やcolumnName
を指定することでSails上で別の名前で扱う事ができます。
module.exports = {
tableName: 'users',
attributes: {
displayName: {
columnName: 'display_name',
type: 'string',
required: true
}
}
};
ただし、執筆時点ではModel.destroy()
の結果として返ってくるモデルにはこの制約が効かないようです(上記の例だと、keyがdisplay_name
としてObjectが返ってくる)。まあ普通にdisplay_name
でアクセスしたらいいのですが、気持ち悪さはかなりある。
User.destroy(1).then(user => {
// => [{id: 1, display_name: 'hoge'}]
});
この問題、以前に似たようなPullRequst(そのときはpopulate
内のwhere句でcolumnNameが効かない問題だった)を出したことがあるのでだいたい検討は付くのですが、この場合は完全にBreakingChangeになってしまい面倒で調査していません。。
junctionTableにcreatedAt/updatedAtがない
これが一番困ったところで、createdAt/updatedAtがjunctionTableには付かないようです。例えばAritcle/Userという2つのテーブルが存在し、「ユーザがお気に入りした記事」というものをArticleFavorite
というjunctionTableで表現しようとしたときに、createdAt/updatedAtが保存されません。updatedAtはjunctionTableなので不要ですが、createdAtが無いと例えば「日付ごとのお気に入り数」がMySQLのデータのみだと取れなくなってしまいます。
対策としては、ArticleFavoriteをjunctionTbleではなく普通のModelとして定義することくらいでしょうか。ただsave()
が使えなくなるので微妙な所。
ArticleFavorite.create({user: 1, article: 1});
他のO/R Mapperだとどうなんでしょう?