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:
enum PostStatus { ACTIVE INACTIVE}
type Post @model { id: ID! title: String! rating: Int! status: PostStatus! # New field with @hasMany comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"])}
# New modeltype Comment @model { id: ID! postID: ID! @index(name: "byPost") post: Post! @belongsTo(fields: ["postID"]) content: String!}
Generate the models for the updated schema using the Amplify CLI.
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
:
Post post = Post.builder() .title("My Post with comments") .rating(10) .status(PostStatus.ACTIVE) .build();
Comment comment = Comment.builder() .post(post) // Directly pass in the post instance .content("Loving Amplify DataStore!") .build();
Amplify.DataStore.save(post, savedPost -> { Log.i("MyAmplifyApp", "Post saved."); Amplify.DataStore.save(comment, savedComment -> Log.i("MyAmplifyApp", "Comment saved."), failure -> Log.e("MyAmplifyApp", "Comment not saved.", failure) ); }, failure -> Log.e("MyAmplifyApp", "Post not saved.", failure));
val post = Post.builder() .title("My Post with comments") .rating(10) .status(PostStatus.ACTIVE) .build()
val comment = Comment.builder() .post(post) // Directly pass in the post instance .content("Loving Amplify DataStore!") .build()
Amplify.DataStore.save(post, { Log.i("MyAmplifyApp", "Post saved") Amplify.DataStore.save(comment, { Log.i("MyAmplifyApp", "Comment saved") }, { Log.e("MyAmplifyApp", "Comment not saved", it) } ) }, { Log.e("MyAmplifyApp", "Post not saved", it) })
val post = Post.builder() .title("My Post with comments") .rating(10) .status(PostStatus.ACTIVE) .build()
val comment = Comment.builder() .post(post) // Directly pass in the post instance .content("Loving Amplify DataStore!") .build()
try { Amplify.DataStore.save(post) Log.i("MyAmplifyApp", "Post saved.")
Amplify.DataStore.save(comment) Log.i("MyAmplifyApp", "Comment saved.")
} catch (error: DataStoreException) { Log.e("MyAmplifyApp", "Save failed", error)}
Post post = Post.builder() .title("My Post with comments") .rating(10) .status(PostStatus.ACTIVE) .build();
Comment comment = Comment.builder() .post(post) // Directly pass in the post instance .content("Loving Amplify DataStore!") .build();
RxAmplify.DataStore.save(post)) .andThen(RxAmplify.DataStore.save(comment)) .subscribe( () -> Log.i("MyAmplifyApp", "Saved Post and Comment."), failure -> Log.e("MyAmplifyApp", "Failed to save at least one item.", failure) );
Querying relations
Amplify.DataStore.query(Comment.class, Post.STATUS.eq(PostStatus.ACTIVE), matches -> { while (matches.hasNext()) { Comment comment = matches.next(); Log.i("MyAmplifyApp", "Content: " + comment.getContent()); } }, failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
Amplify.DataStore.query(Comment::class.java, Post.STATUS.eq(PostStatus.ACTIVE), { matches -> while (matches.hasNext()) { val comment = matches.next() Log.i("MyAmplifyApp", "Content: ${comment.content}") } }, { Log.e("MyAmplifyApp", "Query failed", it) })
Amplify.DataStore .query(Comment::class, Where.matches(Post.STATUS.eq(PostStatus.ACTIVE))) .catch { Log.e("MyAmplifyApp", "Query failed", it) } .collect { Log.i("MyAmplifyApp", "Content: ${it.content}") }
RxAmplify.DataStore.query(Comment.class, Post.STATUS.eq(PostStatus.ACTIVE)) .subscribe( comment -> Log.i("MyAmplifyApp", "Content: " + comment.getContent()), failure -> Log.e("MyAmplifyApp", "Query failed.", failure) );
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:
QueryOptions queryOptions = null;try { queryOptions = Where.identifier(Post.class, "123");} catch (AmplifyException e) { Log.e("MyAmplifyApp", "Failed to construct QueryOptions with provided values for Where.identifier", e);}
if (queryOptions != null) { Amplify.DataStore.query(Post.class, queryOptions, match -> { if (match.hasNext()) { Post post = match.next(); Amplify.DataStore.delete(post, deleted -> Log.i("MyAmplifyApp", "Post deleted."), failure -> Log.e("MyAmplifyApp", "Delete failed.", failure) ); } }, failure -> Log.e("MyAmplifyApp", "Query failed.", failure) );}
Amplify.DataStore.query(Post::class.java, Where.identifier(Post::class.java, "123"), { if (it.hasNext()) { val post = it.next() Amplify.DataStore.delete(post, { Log.i("MyAmplifyApp", "Post deleted") }, { Log.e("MyAmplifyApp", "Delete failed") } ) } }, { Log.e("MyAmplifyApp", "Query failed", it) })
Amplify.DataStore.query(Post::class, Where.identifier(Post::class.java, "123")) .catch { Log.e("MyAmplifyApp", "Query failed", it) } .onEach { Amplify.DataStore.delete(it) } .catch { Log.e("MyAmplifyApp", "Delete failed", it) } .collect { Log.i("MyAmplifyApp", "Post deleted") }
QueryOptions queryOptions = null;try { queryOptions = Where.identifier(Post.class, "123");} catch (AmplifyException e) { Log.e("MyAmplifyApp", "Failed to construct QueryOptions with provided values for Where.identifier", e);}
if (queryOptions != null) { RxAmplify.DataStore.query(Post.class, queryOptions) .flatMapCompletable(RxAmplify.DataStore::delete) .subscribe( () -> Log.i("MyAmplifyApp", "Post deleted."), failure -> Log.e("MyAmplifyApp", "Delete failed.", failure) );}
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.
enum PostStatus { ACTIVE INACTIVE}
type Post @model { id: ID! title: String! rating: Int status: PostStatus editors: [User] @manyToMany(relationName: "PostEditor")}
type User @model { id: ID! username: String! posts: [Post] @manyToMany(relationName: "PostEditor")}
Post post = Post.builder() .title("My First Post") .build();
User editor = User.builder() .username("Nadia") .build();
PostEditor postEditor = PostEditor.builder() .post(post) .user(editor) .build();
Amplify.DataStore.save(post, savedPost -> { Log.i("MyAmplifyApp", "Post saved."); Amplify.DataStore.save(editor, savedEditor -> { Log.i("MyAmplifyApp", "Editor saved."); Amplify.DataStore.save(postEditor, saved -> Log.i("MyAmplifyApp", "PostEditor saved."), failure -> Log.e("MyAmplifyApp", "PostEditor not saved.", failure) ); }, failure -> Log.e("MyAmplifyApp", "Editor not saved.", failure) ); }, failure -> Log.e("MyAmplifyApp", "Post not saved.", failure));
val post = Post.builder() .title("My First Post") .build()
val editor = User.builder() .username("Nadia") .build()
val postEditor = PostEditor.builder() .post(post) .user(editor) .build()
Amplify.DataStore.save(post, { Log.i("MyAmplifyApp", "Post saved") Amplify.DataStore.save(editor, { Log.i("MyAmplifyApp", "Editor saved") Amplify.DataStore.save(postEditor, { Log.i("MyAmplifyApp", "PostEditor saved") }, { Log.e("MyAmplifyApp", "PostEditor not saved", it) } ) }, { Log.e("MyAmplifyApp", "Editor not saved", it) } ) }, { Log.e("MyAmplifyApp", "Post not saved", it) })
val post = Post.builder() .title("My First Post") .build()
val editor = User.builder() .username("Nadia") .build()
val postEditor = PostEditor.builder() .post(post) .user(editor) .build()
try { Amplify.DataStore.save(post) Log.i("MyAmplifyApp", "Post saved.")
Amplify.DataStore.save(editor) Log.i("MyAmplifyApp", "Editor saved.")
Amplify.DataStore.save(postEditor) Log.i("MyAmplifyApp", "PostEditor saved.")
} catch (error: DataStoreException) { Log.e("MyAmplifyApp", "Save failed", error)}
Post post = Post.builder() .title("My First Post") .build();
User editor = User.builder() .username("Nadia") .build();
PostEditor postEditor = PostEditor.builder() .post(post) .user(editor) .build();
Completable.mergeArray( RxAmplify.DataStore.save(post), RxAmplify.DataStore.save(editor)).andThen( RxAmplify.DataStore.save(postEditor)).subscribe( () -> Log.i("MyAmplifyApp", "Post, Editor, and PostEditor saved."), failure -> Log.e("MyAmplifyApp", "Item not saved.", failure));