最近Sails.jsでプロジェクトを一つ回してみたのでそのまとめ。アソシエーション編。
Sails.js、というかそこで使われているO/R Mapperのwaterlineでアソシエーションをどう記述するか。
とりあえず、findだけまとめる。createとかは後でまとめます。
アソシエーション別
One to Many
「記事に複数の画像が紐付いている」という場合。aritcles
とarticle_images
という2つのテーブルがあり、article_images
のarticle_id
がarticle
を参照しているというケース。
モデル定義
module.exports = {
tableName: 'articles',
attributes: {
title: {
type: 'string',
required: true
},
contents: {
type: 'string',
required: true
},
// collection: JOIN先のモデル名(テーブル名ではない)
// via: JOIN先モデルで定義した要素名
images: {
collection: 'ArticleImage',
via: 'article'
}
}
}
moduel.exports = {
tableName: 'article_images',
attributes: {
path: {
type: 'string',
required: true
},
// columnName: 実際のカラム名(同じ場合は省略可能)
// model: 外部キーが指す`module.exportで定義した`モデル名(テーブル名では無い)
article: {
columnName: 'article_id',
model: 'Article',
type: 'integer',
required: true
}
}
}
query
Articleに紐付いた全ての画像をJOINして取得する
// populateでmodelに定義した要素名を指定
Article.findOne(1).populate('images').then(function(article) {
// success
}).catch(function(error) {
// error
});
Many to Many
交差テーブルを利用したMany to Manyの定義。「記事に複数のタグがひも付き、同じタグが紐付いた記事が複数ある」場合。
モデル定義
module.exports = {
tableName: 'articles',
attributes: {
title: {
type: 'string',
required: true
},
contents: {
type: 'string',
required: true
},
// collection: JOIN先のモデル
tags: {
collection: 'Tag'
}
}
}
moduel.exports = {
tableName: 'tags',
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
// JOIN先のモデル
articles: {
collection: 'Article'
}
}
}
重要なのは交差テーブル
module.exports = {
tableName: 'articles_tags',
// ここはモデル名ではなくテーブル名
tables: ['articles', 'tags'],
junctionTable: true,
attributes: {
article: {
columnName: 'article',
type: 'integer',
foreignKey: true,
references: 'tag',
on: 'id',
onKey: 'id',
via: 'article'
},
tag: {
columnName: 'article',
type: 'integer',
foreignKey: true,
references: 'tag',
on: 'id',
onKey: 'id',
via: 'article'
}
}
}
query
find
上記のモデル定義ならArticle/TagのどちらからでもJOIN出来る
// 紐付いたTagと共にArticleを取得
Article.findOne(1).popualte('tags').then(function(article) {
// success
}).catch(function(error) {
// error
});
// 紐付いたArticleとともにTagを取得
Tag.findOne(1).popualte('articles').then(function(tag) {
// success
}).catch(function(error) {
// error
});
create
junctionTableにはアクセス出来ないので、createはO/R Mapperに任せる。
Tag.create({name: 'hoge'}).then(function(tag) {
// created {id: 1, name: 'hoge'}
}).catch(function(error){
});
Article.create({title: 'test', tags: [1]}).then(function(article) {
// ArticleのモデルとjunctionTableが生成される
}).catch(function(error) {
});
注意点
交差テーブルにはアクセス出来ない
次のようなコードはエラーになる。
ArticleTag.find({article: 1}).then(function(article) {
}).catch(function(error) {
// ERROR!!!!!!!
});
これはSails.jsがモデル定義を読みこみ、グローバルオブジェクトとして格納する際にjunctiontable = true
のモデルは破棄(グローバルからアクセス出来ないように)しているため。
交差テーブルの要素名とカラム名は合わせる
何故か、交差テーブルの要素名とカラム名は合わせないといけない。例えば次のようなものはqueryの実行時エラーになる。それっぽいIssueが立ってるのでそのうち直るはず。
module.exports = {
tableName: 'articles_tags',
tables: ['articles', 'tags'],
junctionTable: true,
attributes: {
article: {
// これがエラー
columnName: 'article_id',
type: 'integer',
foreignKey: true,
references: 'tag',
on: 'id',
onKey: 'id',
via: 'article'
},
tag: {
columnName: 'article',
type: 'integer',
foreignKey: true,
references: 'tag',
on: 'id',
onKey: 'id',
via: 'article'
}
}
}
find()
いわゆるWHERE
where()
を使う。find()
の引数に書いてもいい。
Article.find({name: 'hoge'}).then(function(articles){}).catch(function(error){});
Article.find().where({name: 'hoge'}).then(function(articles){}).catch(function(error){});
いわゆるJOIN
populate()
を使う。複数連結したい場合は続けて書く。
Article.find().populate('tag').populate('images').then(function(articles){}).catch(function(error){});
その他
ソートとページネーション
Article.find().sort('createdAt DESC').paginate({page: 1, limit: 5}).then(function(articles){}).catch(function(error){});
ハマりポイント
populateの条件文
こんな感じで書くが、微妙なポイントがあり、populateの第2引数に指定するwhere句は、カラム名
を指定する(通常のwhereはモデルに定義した要素名)。謎。
追記
waterline送ったプルリクエストが通ったので、カラム名でもモデルのattribute名でも指定できるようになった。waterline v0.10.25から。
Article.find().populate('images', {where: {'width': {'<=' : 480}}}}.then(function(articles){}).catch(function(error){});