モデル関係の構築
アプリケーションデータをモデル化する際、異なるデータモデル間の関係を確立する必要があることがよくあります。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」関係をレコード間で作成する
val team = Team.builder() .mantra("Go Frontend!") .build()
Amplify.API.mutate(ModelMutation.create(team), { Log.i("MyAmplifyApp", "Added team with id: ${it.data.id}") val member = Member.builder() .name("Tim") .team(it.data) .build()
Amplify.API.mutate(ModelMutation.create(member), { Log.i("MyAmplifyApp", "Added Member with id: ${it.data.id}")}, { Log.e("MyAmplifyApp", "Create failed", it)}, ) }, { Log.e("MyAmplifyApp", "Create failed", it) })「Has Many」関係をレコード間で更新する
val newTeam = Team.builder() .mantra("Go Fullstack!") .build()
Amplify.API.mutate(ModelMutation.create(newTeam), { Log.i("MyAmplifyApp", "Added team with id: ${it.data.id}")
val updatingMember = existingMember.copyOfBuilder().team(it.data).build()
Amplify.API.mutate(ModelMutation.update(updatingMember), { Log.i("MyAmplifyApp", "Updated Member with id: ${it.data.id}")}, { Log.e("MyAmplifyApp", "Create failed", it)}, ) }, { Log.e("MyAmplifyApp", "Create failed", it) })「Has Many」関係をレコード間で削除する
参照フィールドが必須でない場合、関係値を null に設定することで、1 対多の関係を「削除」できます。
val memberWithRemovedTeam = existingMember.copyOfBuilder().team(null).build()
Amplify.API.mutate(ModelMutation.update(memberWithRemovedTeam), { Log.i("MyAmplifyApp", "Updated Member with id: ${it.data.id}")}, { Log.e("MyAmplifyApp", "Create failed", it)},)「Has Many」関係を遅延読み込みする
Amplify.API.query( ModelQuery.get(Team::class.java, Team.TeamIdentifier("YOUR_TEAM_ID")), { suspend { try { val members = when (val membersModelList = it.data.members) { is LoadedModelList -> { // Eager loading loads the 1st page only. membersModelList.items }
is LazyModelList -> { var page = membersModelList.fetchPage() var loadedMembers = mutableListOf(page.items) // initial page of members // loop through all pages to fetch the full list of members while (page.hasNextPage) { val nextToken = page.nextToken page = membersModelList.fetchPage(nextToken) // add the page of members to the members variable loadedMembers += page.items } loadedMembers } } Log.i("MyAmplifyApp", "members: $members") } catch (error: ApiException) { Log.e("MyAmplifyApp", "Failed to fetch members", error) } } }, { Log.e("MyAmplifyApp", "Failed to fetch team")})「Has Many」関係を事前読み込みする
Amplify.API.query( ModelQuery.get<Team, TeamPath>( Team::class.java, Team.TeamIdentifier("YOUR_TEAM_ID") ) { teamPath -> includes(teamPath.members) }, { val members = (it.data.members as? LoadedModelList<Member>)?.items }, { Log.e("MyAmplifyApp", "Failed to fetch team")})「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」関係をレコード間で作成するには、最初に親アイテムを作成し、次に子アイテムを作成して親を割り当てます。
val customer = Customer.builder() .name("Rene") .build()
Amplify.API.mutate(ModelMutation.create(customer), { Log.i("MyAmplifyApp", "Added customer with id: ${it.data.id}") val cart = Cart.builder() .items(listOf("Tomato", "Ice", "Mint")) .customer(customer) .build()
Amplify.API.mutate(ModelMutation.create(cart), { Log.i("MyAmplifyApp", "Added Cart with id: ${it.data.id}")}, { Log.e("MyAmplifyApp", "Create failed", it)}, ) }, { Log.e("MyAmplifyApp", "Create failed", it) })「Has One」関係をレコード間で更新する
「Has One」関係をレコード間で更新するには、最初に子アイテムを取得し、次に親への参照を別の親に更新します。たとえば、Cart を別の Customer に再割り当てするには:
val newCustomer = Customer.builder() .mantra("Ian") .build()
Amplify.API.mutate(ModelMutation.create(newCustomer), { Log.i("MyAmplifyApp", "Added customer with id: ${it.data.id}")
val updatingCart = existingCart.copyOfBuilder().customer(it.data).build()
Amplify.API.mutate(ModelMutation.update(updatingCart), { Log.i("MyAmplifyApp", "Updated cart with id: ${it.data.id}")}, { Log.e("MyAmplifyApp", "Create failed", it)}, ) }, { Log.e("MyAmplifyApp", "Create failed", it) })「Has One」関係をレコード間で削除する
関係フィールドを null に設定して、レコード間の「Has One」関係を削除できます。
val cartWithRemovedCustomer = existingCart.copyOfBuilder().customer(null).build()
Amplify.API.mutate(ModelMutation.update(cartWithRemovedCustomer), { Log.i("MyAmplifyApp", "Updated cart with id: ${it.data.id}")}, { Log.e("MyAmplifyApp", "Create failed", it)},)「Has One」関係を遅延読み込みする
Amplify.API.query( ModelQuery.get(Team::class.java, Team.TeamIdentifier("YOUR_TEAM_ID")), { suspend { try { val customer = when (val customerReference = cart.customer) { is LoadedModelReference -> { customerReference.value }
is LazyModelReference -> { customerReference.fetchModel() } } Log.i("MyAmplifyApp", "customer: $customer") } catch (error: ApiException) { Log.e("MyAmplifyApp", "Failed to fetch customer", error) } } }, { Log.e("MyAmplifyApp", "Failed to get team")})「Has One」関係を事前読み込みする
val cart = Amplify.API.query( ModelQuery.get<Cart, CartPath>( Cart::class.java, Cart.CartIdentifier("YOUR_CART_ID") ) { cartPath -> includes(cartPath.customer) },{ val customer = (cart.customer as? LoadedModelReference)?.value },{ Log.e("MyAmplifyApp", "Failed to fetch cart", it) })「多対多」関係をモデル化する
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());