7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

sails.jsのアソシエーション周りまとめ

Last updated at Posted at 2015-06-08

最近Sails.jsでプロジェクトを一つ回してみたのでそのまとめ。アソシエーション編。
Sails.js、というかそこで使われているO/R Mapperのwaterlineでアソシエーションをどう記述するか。

とりあえず、findだけまとめる。createとかは後でまとめます。

アソシエーション別

One to Many

「記事に複数の画像が紐付いている」という場合。aritclesarticle_imagesという2つのテーブルがあり、article_imagesarticle_idarticleを参照しているというケース。

モデル定義

Article.js
module.exports = {
  tableName: 'articles',

  attributes: {
    title: {
      type: 'string',
      required: true
    },
    contents: {
      type: 'string',
      required: true
    },

    // collection: JOIN先のモデル名(テーブル名ではない)
    // via: JOIN先モデルで定義した要素名
    images: {
      collection: 'ArticleImage',
      via: 'article'
    }
  }
}
ArticleImage.js
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の定義。「記事に複数のタグがひも付き、同じタグが紐付いた記事が複数ある」場合。

モデル定義

Article.js
module.exports = {
  tableName: 'articles',

  attributes: {
    title: {
      type: 'string',
      required: true
    },
    contents: {
      type: 'string',
      required: true
    },

    // collection: JOIN先のモデル
    tags: {
      collection: 'Tag'
    }
  }
}
Tag.js
moduel.exports = {
  tableName: 'tags',

  attributes: {
    name: {
      type: 'string',
      required: true,
      unique: true
    },

    // JOIN先のモデル
    articles: {
      collection: 'Article'
    }
  }
}

重要なのは交差テーブル

ArticleTag.js
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が立ってるのでそのうち直るはず。

ArticleTag.js
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){});
7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?