LoginSignup
1
0

More than 5 years have passed since last update.

LoopBack4のリレーション

Last updated at Posted at 2018-11-04

Loopback4ではモデルを作ってAPIを公開するところまでは簡単な作業でできるが、モデル間の関係を表そうとするにはちょっと難しいオマジナイがいるようで、APIにどう公開するかも色々ありそうだった。

物品管理を例に、物品を表すItemモデルと、入れ物を表すContainerモデルを使って関係を表現してみる。(関係を表す矢印があるとしたら、Item=Source、Container=Targetになる)

18/11/4時点で、HasManyは正しく動かない(https://github.com/strongloop/loopback-next/issues/1944 )。すぐ使いたければ一時的にBelongsToでやった方が良さそう。

BelongsTo

物品を中心に「どこに入っているか」を知る場合、ItemのプロパティにItemが属するContainerの情報があればよい。このような関係はbelongsToの関係で表せる。

LooBack4のドキュメント(https://loopback.io/doc/en/lb4/BelongsTo-relation.html) によると、モデル・リポジトリ・コントローラそれぞれでやることがあるらしい。

ソース側のモデルに外部キー制約をかける

ソース側(Item)モデルのプロパティに外部キー制約をかけるには、外部キーのプロパティに@belongsTo( () => targetClass)デコレータを付ける。デフォルトでターゲットのidプロパティを主キーと扱うが、主キー名を変えていたら@belongsTo( () => targetClass, { keyTo: '主キー名'}で指定する。

もしかしたら現時点では未実装? 存在しないIDも追加できた。

item.model.ts
import { model, property, belongsTo } from '@loopback/repository';
import { CommonModel, Container } from '.';
// CommonModelでidプロパティを定義している
@model()
export class Item extends CommonModel {
    @belongsTo( () => Container )
    containerId: number;

    constructor(data?: Partial<Item>) {
        super(data);
    }
}

プロパティの型にtargetClass.prototype.idのように、対象の型を設定することで矛盾を防げるかと思ったが、やってみたらObject扱いになってしまったのでNG。

リポジトリにBelongsToAccessorを追加する

LoopBack4では、Itemが属するContainerをItemのIDで取り出すためのBelongsToAccessorを作る手段が用意されている。これにより、例えばitemRepository.container( itemId )のようにアクセスできるようになる。

外部キーItem.containerIdが分かれば、Containerリポジトリから外部キーを使ってContainerを取り出すことはできるから必須ではない。とはいえ、あった方が便利なんだと思う。

BelongsToAccessorを作るには、

  1. BelongsToAccessorとなるメンバを宣言する
    型はBelongsToAccessor<ターゲットの型, 検索に使うIDの型>で指定する。ここでは、ターゲットはContainerモデルで、検索に使うIDの型はItemモデルのIDの型としてtypeof Item.prototype.idを指定している。
  2. ターゲットのRepository.getter関数をDependency Injectionする
    @repository.getter('ターゲット側リポジトリ名') getter名: Getter<ターゲット側リポジトリ>をコンストラクタの引数に追加する。リポジトリをDependency Injectionしないのは、リポジトリ間でbelongsToとhasManyがあった際に循環参照になるのを防ぐための工夫らしい。(AngularはDIの循環参照をうまく解決していた気がするけど)
  3. BelongsToAccessorを定義する
    ファクトリ関数を使ってthis._createBelongsToAccessorFor( ソース側の外部キー名, Repository.getter関数 )のように定義する。ここでは、ソース側の外部キーはcontainerId、Repository.getter関数はcontainerRepositoryGetterを指定している。
item.repository.ts
import { DefaultCrudRepository, juggler, BelongsToAccessor, repository } from '@loopback/repository';
import { Item, Container } from '../models';
import { SqldbDataSource } from '../datasources';
import { ContainerRepository } from './container.repositorie';
import { inject, Getter } from '@loopback/core';

export class ItemRepository extends DefaultCrudRepository<Item,typeof Item.prototype.id> {
[1] public readonly container: BelongsToAccessor<Container,
                                                 typeof Item.prototype.id>;
    constructor(@inject('datasources.sqldb')
                dataSource: SqldbDataSource,
[2]             @repository.getter('ContainerRepository') 
                containerRepositoryGetter: Getter<ContainerRepository>) {
        super(Item, dataSource);
[3]     this.container = this._createBelongsToAccessorFor('containerId', containerRepositoryGetter);
    }
}

うっかり忘れがちだが、リポジトリ間で参照しあう場合は、importでは直接ファイルを指定した方が良い。import { SomeRepository } from '.'のようにすると、index.ts内のexportの順序次第で不可解なコンパイルエラーで悩まされることになる。

コントローラでBelongsToAccessorを使う

Itemが属するContainerをどのように利用者に提供するかはAPIの設計次第らしい。例えば、

  1. belongsTo用のエンドポイントを設ける
  2. Itemのエンドポイント/item/{id}に対して、/item/{id}/containerエンドポイントを設ける

いずれの場合も、Containerの取得に先ほど作成したBelongsToAccessorを使うので、例えば[2]の場合は以下のようになる。

item.repository.ts
    @get('/orders/{id}/container')
    async getContainer( @param.path.number('id') id: typeof Item.prototype.id ): Promise<Container> {
        // ここで先ほど作ったリポジトリのBelongsToAccessorを使って、Itemに対応するContainerを取得
        return await this.itemRepository.container( id ); 
    }

後で、「ItemのGet時に自動でContainerを取得して返す」を試そうとしたところ、応答にContainerを付けても消されてしまった。LoopBack4の思想的にそうするものではないのかもしれない。(LoopBack3では、リレーションごとにエンドポイントを設けることになっていたみたいなので)

HasMany

1対多の関係を表すので、試しにContainer hasMany Items という関係を表してみようとしたところ、以下のようになってしまった。
image.png
image.png

BelongsToと同じようになるのかと思ったら期待しない動きになってしまった。itemsプロパティに配列が直接格納されてしまい、Itemモデルのテーブルとは無関係になっている。

調べたところ、バグの様だった。この記事を書く5日前にIssueとして挙がっていた。(https://github.com/strongloop/loopback-next/issues/1944)
現状はLoopBack4自体が正式リリースではないから仕方なさそう。

1
0
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
1
0