モデル関係の構築
アプリケーションデータをモデル化する際、異なるデータモデル間の関係を確立する必要があることがよくあります。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」関係をレコード間で作成する
do { let team = Team(mantra: "Go Frontend!") let createdTeam = try await Amplify.API.mutate(request: .create(team)).get()
let member = Member( name: "Tim", team: createdTeam) // Directly pass in the team instance let createdMember = try await Amplify.API.mutate(request: .create(member))} catch { print("Create team or member failed", error)}「Has Many」関係をレコード間で更新する
do { let newTeam = Team(mantra: "Go Fullstack!") let createdNewTeam = try await Amplify.API.mutate(request: .create(newTeam)).get()
existingMember.setTeam(createdNewTeam) let updatedMember = try await Amplify.API.mutate(request: .update(existingMember)).get()} catch { print("Create team or update member failed", error)}「Has Many」関係をレコード間で削除する
参照フィールドが必須でない場合、関係値を null に設定することで、1 対多の関係を「削除」できます。
do { existingMember.setTeam(nil) let memberRemovedTeam = try await Amplify.API.mutate(request: .update(existingMember)).get()} catch { print("Failed to remove team from member", error)}「Has Many」関係を遅延読み込みする
do { let queriedTeam = try await Amplify.API.query( request: .get( Team.self, byIdentifier: team.identifier)).get()
guard let queriedTeam, let members = queriedTeam.members else { print("Missing team or members") return } try await members.fetch() print("Number of members: \(members.count)")} catch { print("Failed to fetch team or members", error)}「Has Many」関係を事前読み込みする
do { let queriedTeamWithMembers = try await Amplify.API.query( request: .get( Team.self, byIdentifier: team.identifier, includes: { team in [team.members]})) .get() guard let queriedTeamWithMembers, let members = queriedTeamWithMembers.members else { print("Missing team or members") return } print("Number of members: \(members.count)")} catch { print("Failed to fetch team with members", error)}「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」関係をレコード間で作成するには、最初に親アイテムを作成し、次に子アイテムを作成して親を割り当てます。
do { let customer = Customer(name: "Rene") let createdCustomer = try await Amplify.API.mutate(request: .create(customer)).get()
let cart = Cart( items: ["Tomato", "Ice", "Mint"], customer: createdCustomer) let createdCart = try await Amplify.API.mutate(request: .create(cart)).get()} catch { print("Create customer or cart failed", error)}「Has One」関係をレコード間で更新する
「Has One」関係をレコード間で更新するには、最初に子アイテムを取得し、次に親への参照を別の親に更新します。たとえば、Cart を別の Customer に再割り当てするには:
do { let newCustomer = Customer(name: "Rene") let newCustomerCreated = try await Amplify.API.mutate(request: .create(newCustomer)).get() existingCart.setCustomer(newCustomerCreated) let updatedCart = try await Amplify.API.mutate(request: .update(existingCart)).get()} catch { print("Create customer or cart failed", error)}「Has One」関係をレコード間で削除する
関係フィールドを nil に設定して、レコード間の「Has One」関係を削除できます。
do { existingCart.setCustomer(nil) let cartWithCustomerRemoved = try await Amplify.API.mutate(request: .update(existingCart)).get()} catch { print("Failed to remove customer from cart", error)}「Has One」関係で関連データを読み込む
do { guard let queriedCart = try await Amplify.API.query( request: .get( Cart.self, byIdentifier: existingCart.identifier)).get() else { print("Missing cart") return }
let customer = try await queriedCart.customer} catch { print("Failed to fetch cart or customer", error)}「多対多」関係をモデル化する
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());クライアント側では、次のコードで関連データを取得できます:
do { guard let queriedPost = try await Amplify.API.query( request: .get( Post.self, byIdentifier: post.identifier)).get() else { print("Missing post") return }
let loadedAuthor = try await queriedPost.author let loadedEditor = try await queriedPost.editor} catch { print("Failed to fetch post, author, or editor", error)}識別子でソートキーを持つモデルのモデル関係
データモデルが識別子でソートキーを使用する場合、関連データモデルにも参照フィールドを追加し、ソートキーフィールドを保存する必要があります:
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());