リレーションシップ

モデルを定義したら、`belongsTo` と `hasMany` ヘルパーを使用して、モデル間のリレーションシップを定義できます。各ヘルパーは、モデルにいくつかの動的メソッドを追加します。

belongsTo

1対1のリレーションシップを定義するには、`belongsTo` ヘルパーをインポートし、別のモデルを指すモデルに新しいプロパティを定義します。

import { createServer, Model, belongsTo } from "miragejs"

createServer({
  models: {
    blogPost: Model.extend({
      author: belongsTo(),
    }),

    author: Model,
  },
})

これは、`Author` モデルへの `belongsTo` リレーションシップを定義します。

`belongsTo` ヘルパーは、モデルにいくつかの新しいプロパティとメソッドを追加します。

この場合、`BlogPost` モデルは `authorId` プロパティと、関連付けられた `author` モデルを操作するためないくつかのメソッドを獲得します。

blogPost.authorId // 1
blogPost.authorId = 2 // updates the relationship
blogPost.author // Author instance
blogPost.author = anotherAuthor
blogPost.newAuthor(attrs) // new unsaved author
blogPost.createAuthor(attrs) // new saved author (updates blogPost.authorId in memory only)

`createAuthor` メソッドは新しい作成者を 作成し、すぐに `db` に保存しますが、ブログ投稿の外部キーは*このインスタンスでのみ*更新され、データベースにすぐに永続化されるわけではありません。そのため、`blogPost.authorId` はメモリ内で更新されますが、`db` から `blogPost` を再度フェッチした場合、リレーションシップは永続化されません。

新しい外部キーを永続化するには、新しい作成者を作成した後に `blogPost.save()` を呼び出す必要があります。

hasMany

多対多のリレーションシップを定義するには、hasMany ヘルパーを使用します。

import { createServer, Model, hasMany } from "miragejs"

createServer({
  models: {
    blogPost: Model.extend({
      comments: hasMany(),
    }),

    comment: Model,
  },
})

このヘルパーは、blogPost モデルに commentIds プロパティと、関連付けられた comments コレクションを操作するためメソッドを追加します。

blogPost.commentIds // [1, 2, 3]
blogPost.commentIds = [2, 3] // updates the relationship
blogPost.comments // array of related comments
blogPost.comments = [comment1, comment2] // updates the relationship
blogPost.newComment(attrs) // new unsaved comment
blogPost.createComment(attrs) // new saved comment (comment.blogPostId is set)

1対1

1対1のリレーションシップは、2つのモデルで belongsTo ヘルパーを使用して定義できます。

import { createServer, Model, belongsTo } from "miragejs"

createServer({
  models: {
    supplier: Model.extend({
      account: belongsTo(),
    }),

    account: Model.extend({
      supplier: belongsTo(),
    }),
  },
})

デフォルトでは、Mirage はこれら2つのリレーションシップを互いに逆としてマークし、変更に合わせて同期を保つことができます。たとえば、supplierA.accountaccountB であった場合、accountB.suppliersupplierA を指します。また、supplierA.accountnull に設定された場合、リレーションシップの同期を維持するために、accountB.supplier 側も null に設定されます。

1対多

1対多のリレーションシップは、一方のモデルで belongsTo ヘルパーを使用し、逆のモデルで hasMany ヘルパーを使用して定義できます。

import { createServer, Model, belongsTo, hasMany } from "miragejs"

createServer({
  models: {
    user: Model.extend({
      comments: hasMany(),
    }),

    comment: Model.extend({
      user: belongsTo(),
    }),
  },
})

Mirage はこれらを互いに逆としてマークし、変更に合わせて同期を保ちます。

多対多

多対多のリレーションシップは、2つのモデルで hasMany ヘルパーを使用して定義できます。

import { createServer, Model, hasMany } from "miragejs"

createServer({
  models: {
    blogPost: Model.extend({
      tags: hasMany(),
    }),

    tag: Model.extend({
      blogPosts: hasMany(),
    }),
  },
})

Mirage はこれらを互いに逆としてマークし、変更に合わせて同期を保ちます。

アソシエーションオプション

belongsTo および hasMany リレーションシップ定義をカスタマイズするために、以下のオプションを使用できます。

modelName

関連付けのモデルが関連付け自体とは異なる名前を持つ場合、関連付けに modelName を指定できます。

例えば、

import { createServer, Model, belongsTo, hasMany } from "miragejs"

createServer({
  models: {
    user: Model,

    annotation: Model,

    blogPost: Model.extend({
      author: belongsTo("user"),
      comments: hasMany("annotation"),
    }),
  },
})

上記のように、authorcomment という名前のすべてのメソッドを追加しますが、実際のリレーションシップには UserAnnotation モデルを使用します。

inverse

多くの場合、リレーションシップは互いに逆になることがあります。

たとえば、次の2つのモデルがあるとします。

import { createServer, Model, belongsTo, hasMany } from "miragejs"

createServer({
  models: {
    blogPost: Model.extend({
      comments: hasMany(),
    }),

    comment: Model.extend({
      blogPost: belongsTo(),
    }),
  },
})

この場合、blogPost.comments は Comment モデルのコレクションを指し、それらの Comment モデルのそれぞれは、元の投稿を指す comment.blogPost リレーションシップを持ちます。

Mirage は多くの場合、2つの異なるモデルの2つのリレーションシップが互いに逆であると推測できますが、明示的にする必要がある場合があります。これは通常、モデルに同じモデルタイプを指す2つのリレーションシップがある場合に発生します。

たとえば、次のスキーマがあるとします。

import { createServer, Model, belongsTo, hasMany } from "miragejs"

createServer({
  models: {
    user: Model.extend({
      blogPosts: hasMany(),
    }),

    blogPost: Model.extend({
      author: belongsTo("user"),
      reviewer: belongsTo("user"),
    }),
  },
})

この場合、Mirage はどのリレーションシップ(blogPost.author または blogPost.reviewer)を user.blogPosts コレクションと同期させるべきかわかりません。そのため、inverse オプションを使用して、どちらが逆であるかを指定できます。

import { createServer, Model, belongsTo, hasMany } from "miragejs"

createServer({
  models: {
    user: Model.extend({
      blogPosts: hasMany(),
    }),

    blogPost: Model.extend({
      author: belongsTo("user", { inverse: "blogPosts" }),
      reviewer: belongsTo("user", { inverse: null }),
    }),
  },
})

これで、ブログ投稿が user.blogPosts に追加されると、その投稿の author が正しく更新されます。

polymorphic

オプションとして { polymorphic: true } を渡すことで、関連付けが多態関連付けであるかどうかを指定できます。

たとえば、BlogPostPicture モデルがあり、どちらも Comment を持つことができるとします。モデル定義は次のようになります。

import { createServer, Model, belongsTo, hasMany } from "miragejs"

createServer({
  models: {
    blogPost: Model.extend({
      comments: hasMany(),
    }),

    picture: Model.extend({
      comments: hasMany(),
    }),

    comment: Model.extend({
      commentable: belongsTo({ polymorphic: true }),
    }),
  },
})

コメントされるモデルは BlogPost または Picture のいずれかである可能性があるため、Comment モデルに commentable 多態リレーションシップを与えます。そのリレーションシップにどのタイプのモデルが存在できるかについての検証が行われないため、commentable にはタイプがありません。

多態関連付けは、外部キーと build/create メソッドのメソッドシグネチャがわずかに異なります。

let comment = server.schema.comments.create({ text: "foo" })

comment.buildCommentable("blog-post", { title: "Lorem Ipsum" })
comment.createCommentable("blog-post", { title: "Lorem Ipsum" })

// getter
comment.commentableId // { id: 1, type: 'blog-post' }

// setter
comment.commentableId = { id: 2, type: "picture" }

Has-many リレーションシップも多態的である可能性があります。

import { createServer, Model, belongsTo, hasMany } from "miragejs"

createServer({
  models: {
    car: Model,

    watch: Model

    user: Model.extend({
      things: hasMany({ polymorphic: true })
    }),
  },
})
let user = server.schema.users.create({ name: "Sam" });

user.buildThing('car', { attrs });
user.createThing('watch', { attrs });

// getter
user.thingIds; // [ { id: 1, type: 'car' }, { id: 3, type: 'watch' }, ... ]

// setter
user.thingIds = [ { id: 2, type: 'watch' }, ... ];

使用可能なすべての ORM メソッドについては、スキーマ、モデル、およびコレクションの API ドキュメントを参照してください。

これらのガイドではシリアライザーについても説明します。シリアライザーでは、モデルとコレクションのシリアル化された形式を本番 API に一致するようにカスタマイズする方法を学習します。

次に、モデル定義を活用してリレーショナルデータのグラフを簡単に作成できるファクトリを見てみましょう。