モデル関係の構築
アプリケーションデータをモデル化する際、異なるデータモデル間の関係を確立する必要があることがよくあります。Amplify Data では、データスキーマに 1 対多、1 対 1、および多対多の関係を作成できます。クライアント側では、Amplify Data により関連データの遅延読み込みまたは事前読み込みが可能です。
関係のタイプ
| 関係 | コード | 説明 | 例 |
|---|---|---|---|
| 1 対多 | a.hasMany(...) & a.belongsTo(...) | 2 つのモデル間に 1 対多の関係を作成します。 | Team は複数の Members を持ちます。Member は Team に属しています。 |
| 1 対 1 | a.hasOne(...) & a.belongsTo(...) | 2 つのモデル間に 1 対 1 の関係を作成します。 | Customer は 1 つの Cart を持ちます。Cart は 1 つの Customer に属しています。 |
| 多対多 | 結合テーブル上の 2 つの a.hasMany(...) & a.belongsTo(...) | 結合テーブルで関連モデル間に 2 つの 1 対多の関係を作成します。 | Post は複数の Tags を持ちます。Tag は複数の Posts を持ちます。 |
1 対多のモデル関係を構築する
hasMany() メソッドと belongsTo() メソッドを使用して、2 つのモデル間に 1 対多の関係を作成します。以下の例では、Team は複数の Members を持ち、Member はちょうど 1 つの Team に属しています。
- Member モデルで
teamIdという参照フィールドを作成します。この参照フィールドの型は Team の識別子の型と一致する必要があります。この場合、自動生成されるid: a.id().required()フィールドです。 teamIdフィールドを参照するteamという関係フィールドを追加します。これにより、Member モデルからチーム情報を照会できます。- Member モデルの
teamIdフィールドを参照するmembersという関係フィールドを追加します。
const schema = a.schema({ Member: a.model({ name: a.string().required(), // 1. Create a reference field teamId: a.id(), // 2. Create a belongsTo relationship with the reference field team: a.belongsTo('Team', 'teamId'), }),
Team: a.model({ mantra: a.string().required(), // 3. Create a hasMany relationship with the reference field // from the `Member`s model. members: a.hasMany('Member', 'teamId'), }),}).authorization((allow) => allow.publicApiKey());「Has Many」関係をレコード間で作成する
const { data: team } = await client.models.Team.create({ mantra: 'Go Frontend!',});
const { data: member } = await client.models.Member.create({ name: "Tim", teamId: team.id,});「Has Many」関係をレコード間で更新する
const { data: newTeam } = await client.models.Team.create({ mantra: 'Go Fullstack',});
await client.models.Member.update({ id: "MY_MEMBER_ID", teamId: newTeam.id,});「Has Many」関係をレコード間で削除する
参照フィールドが必須でない場合、関係値を null に設定することで、1 対多の関係を「削除」できます。
await client.models.Member.update({ id: "MY_MEMBER_ID", teamId: null,});「Has Many」関係を遅延読み込みする
const { data: team } = await client.models.Team.get({ id: "MY_TEAM_ID"});
const { data: members } = await team.members();
members.forEach(member => console.log(member.id));「Has Many」関係を事前読み込みする
const { data: teamWithMembers } = await client.models.Team.get( { id: "MY_TEAM_ID" }, { selectionSet: ["id", "members.*"] },);
teamWithMembers.members.forEach(member => console.log(member.id));「Has Many」関係で親レコード削除時の孤立した外部キーを処理する
// Get the IDs of the related members.const { data: teamWithMembers } = await client.models.Team.get( { id: teamId }, { selectionSet: ["id", "members.*"] },);
// Delete Teamawait client.models.Team.delete({ id: teamWithMembers.id });
// Delete all members in parallelawait Promise.all( teamWithMembers.members.map(member => client.models.Member.delete({ id: member.id }) ));「1 対 1」関係をモデル化する
hasOne() メソッドと belongsTo() メソッドを使用して、2 つのモデル間に 1 対 1 の関係を作成します。以下の例では、Customer は Cart を持ち、Cart は Customer に属しています。
- Cart モデルで
customerIdという参照フィールドを作成します。この参照フィールドの型は Customer の識別子の型と一致する必要があります。この場合、自動生成されるid: a.id().required()フィールドです。 customerIdフィールドを参照するcustomerという関係フィールドを追加します。これにより、Cart モデルから顧客情報を照会できます。- Cart モデルの
customerIdフィールドを参照するactiveCartという関係フィールドを追加します。
const schema = a.schema({ Cart: a.model({ items: a.string().required().array(), // 1. Create reference field customerId: a.id(), // 2. Create relationship field with the reference field customer: a.belongsTo('Customer', 'customerId'), }), Customer: a.model({ name: a.string(), // 3. Create relationship field with the reference field // from the Cart model activeCart: a.hasOne('Cart', 'customerId') }),}).authorization((allow) => allow.publicApiKey());「Has One」関係をレコード間で作成する
「has one」関係をレコード間で作成するには、最初に親アイテムを作成し、次に子アイテムを作成して親を割り当てます。
const { data: customer, errors } = await client.models.Customer.create({ name: "Rene",});
const { data: cart } = await client.models.Cart.create({ items: ["Tomato", "Ice", "Mint"], customerId: customer?.id,});「Has One」関係をレコード間で更新する
「Has One」関係をレコード間で更新するには、最初に子アイテムを取得し、次に親への参照を別の親に更新します。たとえば、Cart を別の Customer に再割り当てするには:
const { data: newCustomer } = await client.models.Customer.create({ name: 'Ian',});
await client.models.Cart.update({ id: cart.id, customerId: newCustomer?.id,});「Has One」関係をレコード間で削除する
関係フィールドを null に設定して、レコード間の「Has One」関係を削除できます。
await client.models.Cart.update({ id: project.id, customerId: null,});「Has One」関係を遅延読み込みする
const { data: cart } = await client.models.Cart.get({ id: "MY_CART_ID"});const { data: customer } = await cart.customer();「Has One」関係を事前読み込みする
const { data: cart } = await client.models.Cart.get( { id: "MY_CART_ID" }, { selectionSet: ['id', 'customer.*'] },);
console.log(cart.customer.id)「Has One」関係で親レコード削除時の孤立した外部キーを処理する
// Get the customer with their associated cartconst { data: customerWithCart } = await client.models.Customer.get( { id: customerId }, { selectionSet: ["id", "activeCart.*"] },);
// Delete Cart if existsawait client.models.Cart.delete({ id: customerWithCart.activeCart.id });
// Delete the customerawait client.models.Customer.delete({ id: customerWithCart.id });「多対多」関係をモデル化する
2 つのモデル間に多対多の関係を作成するには、「結合テーブル」として機能するモデルを作成する必要があります。この「結合テーブル」は、2 つの関連エンティティ間に 2 つの 1 対多の関係を含む必要があります。たとえば、Post が多くの Tags を持ち、Tag が多くの Posts を持つという関係をモデル化するには、これら 2 つのエンティティ間の関係を表す新しい PostTag モデルを作成する必要があります。
const schema = a.schema({ PostTag: a.model({ // 1. Create reference fields to both ends of // the many-to-many relationship postId: a.id().required(), tagId: a.id().required(), // 2. Create relationship fields to both ends of // the many-to-many relationship using their // respective reference fields post: a.belongsTo('Post', 'postId'), tag: a.belongsTo('Tag', 'tagId'), }), Post: a.model({ title: a.string(), content: a.string(), // 3. Add relationship field to the join model // with the reference of `postId` tags: a.hasMany('PostTag', 'postId'), }), Tag: a.model({ name: a.string(), // 4. Add relationship field to the join model // with the reference of `tagId` posts: a.hasMany('PostTag', 'tagId'), }),}).authorization((allow) => allow.publicApiKey());2 つのモデル間の複数の関係をモデル化する
関係は参照フィールドによって一意に定義されます。たとえば、Post は Person モデルとの author と editor の個別の関係を持つことができます。
const schema = a.schema({ Post: a.model({ title: a.string().required(), content: a.string().required(), authorId: a.id(), author: a.belongsTo('Person', 'authorId'), editorId: a.id(), editor: a.belongsTo('Person', 'editorId'), }), Person: a.model({ name: a.string(), editedPosts: a.hasMany('Post', 'editorId'), authoredPosts: a.hasMany('Post', 'authorId'), }),}).authorization((allow) => allow.publicApiKey());クライアント側では、次のコードで関連データを取得できます:
const client = generateClient<Schema>();
const { data: post } = await client.models.Post.get({ id: "SOME_POST_ID" });
const { data: author } = await post?.author();const { data: editor } = await post?.editor();識別子でソートキーを持つモデルのモデル関係
データモデルが識別子でソートキーを使用する場合、関連データモデルにも参照フィールドを追加し、ソートキーフィールドを保存する必要があります:
const schema = a.schema({ Post: a.model({ title: a.string().required(), content: a.string().required(), // Reference fields must correspond to identifier fields. authorName: a.string(), authorDoB: a.date(), // Must pass references in the same order as identifiers. author: a.belongsTo('Person', ['authorName', 'authorDoB']), }), Person: a.model({ name: a.string().required(), dateOfBirth: a.date().required(), // Must reference all reference fields corresponding to the // identifier of this model. authoredPosts: a.hasMany('Post', ['authorName', 'authorDoB']), }).identifier(['name', 'dateOfBirth']),}).authorization((allow) => allow.publicApiKey());関係を必須または任意にする
Amplify Data の関係は参照フィールドを使用して、関係が必須か任意かを判断します。参照フィールドを必須としてマークした場合、2 つのモデル間の関係を「削除」することはできません。代わりに、関連レコード全体を削除する必要があります。
const schema = a.schema({ Post: a.model({ title: a.string().required(), content: a.string().required(), // You must supply an author when creating the post // Author can't be set to `null`. authorId: a.id().required(), author: a.belongsTo('Person', 'authorId'), // You can optionally supply an editor when creating the post. // Editor can also be set to `null`. editorId: a.id(), editor: a.belongsTo('Person', 'editorId'), }), Person: a.model({ name: a.string(), editedPosts: a.hasMany('Post', 'editorId'), authoredPosts: a.hasMany('Post', 'authorId'), }),}).authorization((allow) => allow.publicApiKey());