Node.js向けORM Objection.jsを使ってみた(2) - Model定義について

## 概要

Modelを定義する際は、Objection.jsのModelクラスを継承します。

```javascript
const { Model } = require('objection');

class User extends Model {
  static get tableName() { return 'users'; }
  static get jsonSchema () {
    return {
      required: ['name', 'email'],
      properties: {
        id: { type: 'integer' },
        name: { type: 'string' },
        email: { type: 'string' }
      }
    };
  }

  static get relationMappings() {
    return {
      projects: {
        relation: Model.ManyToManyRelation,
        modelClass: Project,
        join: {
          from: 'users.id',
          through: {
            from: 'projectMembers.userId',
            to: 'projectMembers.projectId'
          },
          to: 'projects.id'
        }
      },
      tasks: {
        relation: Model.HasManyRelation,
        modelClass: Task,
        join: {
          from: 'users.id',
          to: 'tasks.assignedTo'
        }
      }
    };
  }
}

class Project extends Model {
  static get tableName() { return 'projects'; }
  static get jsonSchema () {
    return {
      required: ['name'],
      properties: {
        id: { type: 'integer' },
        name: { type: 'string' },
        ownerId: { type: 'integer' }
      }
    };
  }

  static get relationMappings() {
    return {
      members: {
        relation: Model.ManyToManyRelation,
        modelClass: User,
        join: {
          from: 'projects.id',
          through: {
            // projectMembersはjoinテーブル
            from: 'projectMembers.projectId',
            to: 'projectMembers.userId'
          },
          to: 'users.id'
        }
      },
      owner: {
        relation: Model.HasOneRelation,
        modelClass: User,
        join: {
          from: 'projects.ownerId',
          to: 'users.id'
        }
      },
      tasks: {
        relation: Model.HasManyRelation, 
        modelClass: Task,
        join: {
          from: 'projects.id',
          to: 'tasks.projectId'
        }
      }
    };
  }
}

class Task extends Model {
  static get tableName() { return 'tasks'; }
  static get jsonSchema() {
    return {
      required: ['name', 'projectId'],
      properties: {
        id: { type: 'integer' },
        name: { type: 'string' },
        description: { type: 'string' },
        assignedTo: { type: 'integer' },
        projectId: { type: 'integer' },
        deadline: { type: 'string' },
        createdAt: { type: 'string' },
        updatedAt: { type: 'string' }
      }
    };
  }

  static get relationMappings() {
    return {
      project: {
        relation: Model.BelongsToOneRelation,
        modelClass: Project,
        join: {
          from: 'tasks.projectId',
          to: 'projects.id'
        }
      },
      user: {
        relation: Model.BelongsToOneRelation,
        modelClass: User,
        join: {
          from: 'tasks.assignedTo',
          to: 'users.id'
        }
      }
    };
  }
}
```

## staticプロパティ

Modelクラスには、validationや対応するテーブルの定義などに関するstaticプロパティやメソッドが定義されており、
Modelのサブクラスでoverrideすることによって、クエリ発行時の挙動などを変更できます。

### `tableName`

必須

Modelに対応するテーブル名を文字列で設定します。

### `idColumn`

省略可能(デフォルトは`"id"`)

プライマリーキーの名前を文字列で指定します。

複合主キーの場合は、各キーの名前を格納した配列を指定します。

### `jsonSchema`

省略可能

[JSONSchema](http://json-schema.org)に基づいて、バリデーションルールを定義します。

省略した場合は、バリデーションは実行されません。

### `relationMappings`

省略可能

他のモデルとの関連を定義します。

Eager loadingやGraph inserts等の機能を使用する際に必要になります。

### BelongsToOne

relationプロパティに、Model.BelongsToOneRelationを設定します。

```javascript
class Task extends Model {
  ...
  static get relationMappings() {
    return {
      // 関連名。Eager loading等を使用した際に、インスタンスのプロパティ名として使用されます。
      project: {
        relation: Model.BelongsToOneRelation,
        // 参照先Modelのコンストラクタ または 参照先Modelをexportしているモジュールへの絶対パス
        modelClass: Project,
        join: {
          // 参照元テーブル.参照元カラム
          from: 'tasks.projectId',
          // 参照先テーブル.参照先カラム
          to: 'projects.id'
        }
      }
    };
  }
  ...
}
```

#### HasOne

```javascript
class Project extends Model {
  ...
  static get relationMappings() {
    return {
      // 関連名。Eager loading等を使用した際に、インスタンスのプロパティ名として使用されます。
      owner: {
        relation: Model.HasOneRelation,
        // 参照先Modelのコンストラクタ または 参照先Modelをexportしているモジュールへの絶対パス
        modelClass: User,
        join: {
          // 参照元テーブル.参照元カラム
          from: 'projects.ownerId',
          // 参照先テーブル.参照先カラム
          to: 'users.id'
        }
      }
    };
  }
  ...
}
```

#### HasMany

```javascript
class User extends Model {
  ...
  static get relationMappings() {
    return {
      // 関連名。Eager loading等を使用した際に、インスタンスのプロパティ名として使用されます。
      tasks: {
        relation: Model.HasManyRelation,
        // 参照先Modelのコンストラクタ または 参照先Modelをexportしているモジュールへの絶対パス
        modelClass: Task,
        join: {
          // 参照元テーブル.参照元カラム
          from: 'users.id',
          // 参照先テーブル.参照先カラム
          to: 'tasks.assignedTo'
        }
      }
    };
  }
  ...
}
```

#### ManyToMany

relationプロパティに、Model.ManyToManyRelationを設定します。

また、joinテーブルに関する定義を、join.throughプロパティに設定します。

```javascript
class Project extends Model {
  ...
  static get relationMappings() {
    return {
      // 関連名。Eager loading等を使用した際に、インスタンスのプロパティ名として使用されます。
      projects: {
        relation: Model.ManyToManyRelation,
        // 参照先Modelのコンストラクタ または 参照先Modelをexportしているモジュールへの絶対パス
        modelClass: Project,
        join: {
          // 参照元テーブル.参照元カラム
          from: 'users.id',
          // joinテーブルに関する定義
          through: {
            from: 'projectMembers.userId',
            to: 'projectMembers.projectId'
          },
          // 参照先テーブル.参照先カラム
          to: 'projects.id'
        }
      }
    }
  }
  ...
}
```

## 参考

https://vincit.github.io/objection.js/#models