Relational models
DataStore has the capability to handle relationships between Models, such as has one, has many, belongs to. In GraphQL this is done with the @hasOne
, @hasMany
and @index
directives as defined in the GraphQL Transformer documentation.
Updated schema
For the examples below with DataStore let's add a new model to the sample schema:
1enum PostStatus {2 ACTIVE3 INACTIVE4}5
6type Post @model @auth(rules: [{allow: public}]) {7 id: ID!8 title: String!9 rating: Int!10 status: PostStatus!11 # new field with @hasMany12 comments: [Comment] @hasMany13}14
15# new model16type Comment @model {17 id: ID!18 content: String19 post: Post @belongsTo20}
Saving relations
In order to save connected models, you will create an instance of the model you wish to connect and pass its ID to DataStore.save
:
1let postWithComments = Post(title: "My post with comments",2 rating: 5,3 status: .active)4
5let comment = Comment(content: "Loving Amplify DataStore", post: postWithComments)6
7Amplify.DataStore.save(postWithComments) { postResult in8 switch postResult {9 case .failure(let error):10 print("Error adding post - \(error.localizedDescription)")11 case .success:12 Amplify.DataStore.save(comment) { commentResult in13 switch commentResult {14 case .success:15 print("Comment saved!")16 case .failure(let error):17 print("Error adding comment - \(error.localizedDescription)")18 }19 }20 }21}
Querying relations
Models with one-to-many connections are lazy-loaded when accessing the connected property, so accessing a relation is as simple as:
1Amplify.DataStore.query(Post.self, byId: "123") {2 switch $0 {3 case .success(let post):4 if let postWithComments = post {5 if let comments = postWithComments.comments {6 for comment in comments {7 print(comment.content)8 }9 }10 } else {11 print("Post not found")12 }13 case .failure(let error):14 print("Post not found - \(error.localizedDescription)")15 }16}
The connected properties are of type List<M>
, where M
is the model type, and that type is a custom Swift Collection, which means that you can filter
, map
, etc:
1let excitedComments = postWithComments2 .comments?3 .compactMap { $0.content }4 .filter { $0.contains("Wow!") }
Deleting relations
When you delete a parent object in a one-to-many relationship, the children will also be removed from the DataStore and mutations for this deletion will be sent over the network. For example, the following operation would remove the Post with id 123 as well as any related comments:
1Amplify.DataStore.query(Post.self, byId: "123") {2 switch $0 {3 case .success(let postWithComments):4 // postWithComments might be nil, unwrap the optional appropriately5 Amplify.DataStore.delete(postWithComments!) { deleteResult in6 switch deleteResult {7 case .success:8 print("Post with id 123 deleted with success")9 case .failure(let error):10 print("Error deleting post and comments - \(error.localizedDescription)")11 }12 }13 case .failure(let error):14 print("Error fetching post with id 123 - \(error.localizedDescription)")15 }16}
However, in a many-to-many relationship the children are not removed and you must explicitly delete them.
Many-to-many
For many-to-many relationships, you can use the @manyToMany
directive and specify a relationName
. Under the hood, Amplify creates a join table and a one-to-many relationship from both models.
1enum PostStatus {2 ACTIVE3 INACTIVE4}5
6type Post @model {7 id: ID!8 title: String!9 rating: Int10 status: PostStatus11 editors: [User] @manyToMany(relationName: "PostEditor")12}13
14type User @model {15 id: ID!16 username: String!17 posts: [Post] @manyToMany(relationName: "PostEditor")18}
1let post = Post(title: "My post with comments",2 rating: 5,3 status: .active)4let editor = User(username: "Nadia")5
6Amplify.DataStore.save(post) { postResult in7 switch postResult {8 case .failure(let error):9 print("Error adding post - \(error.localizedDescription)")10 case .success:11 Amplify.DataStore.save(editor) { editorResult in12 switch editorResult {13 case .failure(let error):14 print("Error adding user - \(error.localizedDescription)")15 case .success:16 let postEditor = PostEditor(post: post, editor: editor)17 Amplify.DataStore.save(postEditor) { postEditorResult in18 switch postEditorResult {19 case .failure(let error):20 print("Error saving postEditor - \(error.localizedDescription)")21 case .success:22 print("Saved user, post and postEditor!")23 }24 }25 }26 }27 }28}