Bookshelfでpolymorphic association

BackboneベースのORM [Bokshelf](http://bookshelfjs.org/)の[polymorphic association](http://en.wikipedia.org/wiki/Polymorphic_association)です。

```bash
$ mkdir bookshelf-polymorphic-association
$ cd  bookshelf-polymorphic-association
$ vi package.json
```

package.json:

```json
{
  "name": "bookshelf-polymorphic-association",
  "version": "0.0.0",
  "main": "index.js",
  "dependencies": {
    "bookshelf": "~0.6.1",
    "knex": "git+https://github.com/tgriesser/knex.git",
    "sqlite3": "~2.1.19"
  }
}
```

モジュールのインストール:

```bash
$ npm install
```

## テーブルの作成

Bookshelfが利用している[knex](http://knexjs.org/)がマイグレーションをサポートしているのでこれを使います。

### 設定ファイルの作成

```bash
$ mkdir db
$ vi db/config.js
```

db/config.js:


```js
module.exports = {
  directory: './db/migrations',
  database: {
    client: 'sqlite3',
    connection: {
      filename: './db/bookshelf-polymorphic-association.db'
    }
  }
};
```

### マイグレーション作成

```bash
$ ./node_modules/.bin/knex migrate:make create -c ./db/config.js 
$ vi 20131130140239_create.js
```

20131130140239_create.js:


```js
exports.up = function(knex, Promise) {
  return Promise.all([
    knex.schema.createTable('employees', function(t) {
      t.increments('id').primary();
      t.string('name');
    }),

    knex.schema.createTable('products', function(t) {
      t.increments('id').primary();
      t.string('name');
    }),

    knex.schema.createTable('pictures', function(t) {
      t.increments('id').primary();
      t.string('name');
      t.integer('imageable_id').notNull();
      t.string('imageable_type').notNull();
    })
  ]);
};

exports.down = function(knex, Promise) {
  return Promise.all([
    knex.schema.dropTable('employees'),

    knex.schema.dropTable('products'),

    knex.schema.dropTable('pictures')
  ]);
};
```

### マイグレーション実行

```bash
$ ./node_modules/.bin/knex migrate:latest -c ./db/config.js
```

## 本題

index.js:

```js
var config = require('./db/config').database,
    Bookshelf = require('bookshelf').initialize(config);

var Employee = Bookshelf.Model.extend({
  tableName: 'employees',
  pictures: function() {
    return this.morphMany(Picture, 'imageable');
  }
});

var Product = Bookshelf.Model.extend({
  tableName: 'products',
  pictures: function() {
    return this.morphMany(Picture, 'imageable');
  }
});

var Picture = Bookshelf.Model.extend({
  tableName: 'pictures',
  imageable: function() {
    return this.morphTo('imageable', Employee, Product);
  }
});

// Employeeを作成
new Employee({ name: 'hoge' }).save()

  // employeeのPictureを作成
  .then(function(employee) {
    var picture = new Picture({
      name: 'employee\'s picture',
      imageable_id: employee.id,
      imageable_type: 'employees'
    });

    return [picture.save(), employee];
  })

  // employeeをPicture毎フェッチしてみる
  .spread(function(picture, employee) {
    return employee.fetch({ withRelated: ['pictures'] });
  })
  .tap(function(employee) {
    console.log(employee.toJSON());
  })

  // 今度はProductを作成
  .then(function() {
    return new Product({ name: 'piyo' }).save();
  })

  // productのPictureを作成
  .then(function(product) {
    var picture = new Picture({
      name: 'product\'s picture',
      imageable_id: product.id,
      imageable_type: 'products'
    });

    return [picture.save(), product];
  })

  // productをPicture毎フェッチしてみる
  .spread(function(picture, product) {
    return product.fetch({ withRelated: ['pictures'] });
  })
  .tap(function(employee) {
    console.log(employee.toJSON());
  })

  .then(function() {
    process.exit();
  });
```

### 実行

```bash
$ node index.js
```