Many to many Relationship
Introduction
At the core of many backends, you will find the ability to store data. Using Parse, you can store data objects establishing relations between them. Data relations standardize how each data object is related or associated with other ones. That can give you extra power when building and running complex queries. There are three main relation types:
one-to-many
, where one object can be related to many other objects;one-to-one
, establishing direct relations between two objects and only them;many-to-many
, which can create many complex relations between many objects.
In this guide, we will focus on many-to-many relations. These relations are common in applications containing authored content, such as blogs and news feeds, because authors and category tags can be interchangeable and optimized for quick querying. Data storage backends usually will demand explicit declarations of these associations and even require that you create, on your own, relational tables for managing their joining. Parse makes it automatically for you, removing any possibility of errors while building your data relations and speeding up your application modeling process.
There are two ways to create a many-to-many relation in Parse. The first uses the Parse.Object
relations, which is the fastest in creation and query time. The second is using arrays which can lead to slow query times depending on their size. Because of this performance issue, we will use only relation examples from now on.
In this guide, you will implement a React Native book registration application that contains the three main kinds of data associations. You will learn how to create and query many-to-many data relations using your Back4App and React Native.
At any time, you can access this project via our GitHub repositories to checkout the styles and complete code.
Prerequisites
To complete this tutorial, you will need:
- A React Native App created and connected to Back4App.
- If you want to run this guide’s example project, you should set up the
react-native-paper
library.
Goal
To perform and demonstrate many-to-many database relations in React Native using Parse in a realistic scenario.
Step 1 - Understanding the Book class
Since in this guide we will be using a book registration application example, you need to first understand how the object relations are laid out in this database. The main object class that you’ll be using is the Book
class, which will store each book entry in the registration. These are the other four object classes:
Publisher
: book publisher name, one-to-many relation withBook
;Genre
: book genre, one-to-many relation withBook
. Note that for this example we will consider that a book can only have one genre;Author
: book author, many-to-many relation withBook
, since a book can have more than one author and an author can have more than one book as well;ISDB
: book ISDB identifying number, one-to-one relation withBook
, since this number is unique for each book.
Here is a visual representation of these database tables:
For simplicity’s, we will assume that each object class has only a string type name
attribute (title
for the Book
), apart from any additional relational attribute.
Step 2 - Creating many-to-many relations
Before going into this step we recommend you to clone and run the React Native Library app exmaple (JavaScript Example Repository, TypeScript Example Repository). This application has two main screens: one responsible for listing the registered books and the other for creating new books. In the book registration form, there are direct links to the other related objects, so when a user picks one or more author options, the form fields will hold the complete Parse.Object
instances as an array. Note that in the following example there are two authors selected:
Let’s take a look at the book creation method that is called when submitting this form:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
const createBook = async function () {
try {
// These values come from state variables linked to
// the screen form fields, retrieving the user choices
// as a complete Parse.Object, when applicable;
const bookTitleValue = bookTitle;
const bookISBDValue = bookISBD;
// For example, bookPublisher holds the value from
// RadioButton.Group field with its options being every
// Publisher parse object instance saved on server, which is
// queried on screen load via useEffect
const bookPublisherObject = bookPublisher;
const bookGenreObject = bookGenre;
// bookAuthors can be an array of Parse.Objects, since the book
// may have more than one Author
const bookAuthorsObjects = bookAuthors;
// Creates a new parse object instance
let Book = new Parse.Object('Book');
// Set data to parse object
// Simple title field
Book.set('title', bookTitleValue);
// 1:1 relation, need to check for uniqueness of value before creating a new ISBD object
let isbdQuery = new Parse.Query('ISBD');
isbdQuery.equalTo('name', bookISBDValue);
let isbdQueryResult = await isbdQuery.first();
if (isbdQueryResult !== null && isbdQueryResult !== undefined) {
// If first returns a valid object instance, it means that there
// is at least one instance of ISBD with the informed value
Alert.alert(
'Error!',
'There is already an ISBD instance with this value!',
);
return false;
} else {
// Create a new ISBD object instance to create a one-to-one relation on saving
let ISBD = new Parse.Object('ISBD');
ISBD.set('name', bookISBDValue);
ISBD = await ISBD.save();
// Set the new object to the new book object ISBD field
Book.set('isbd', ISBD);
}
// One-to-many relations can be set in two ways:
// add direct object to field (Parse will convert to pointer on save)
Book.set('publisher', bookPublisherObject);
// or add pointer to field
Book.set('genre', bookGenreObject.toPointer());
// many-to-many relation
// Create a new relation so data can be added
let authorsRelation = Book.relation('authors');
// bookAuthorsObjects is an array of Parse.Objects,
// you can add to relation by adding the whole array or object by object
authorsRelation.add(bookAuthorsObjects);
// After setting the values, save it on the server
try {
await Book.save();
// Success
Alert.alert('Success!');
navigation.goBack();
return true;
} catch (error) {
// Error can be caused by lack of Internet connection
Alert.alert('Error!', error.message);
return false;
}
} catch (error) {
// Error can be caused by lack of value selection
Alert.alert(
'Error!',
'Make sure to select valid choices in Publisher, Genre and Author fields!',
);
return false;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
const createBook = async function (): Promise<boolean> {
try {
// These values come from state variables linked to
// the screen form fields, retrieving the user choices
// as a complete Parse.Object, when applicable;
const bookTitleValue: string = bookTitle;
const bookISBDValue: string = bookISBD;
// For example, bookPublisher holds the value from
// RadioButton.Group field with its options being every
// Publisher parse object instance saved on server, which is
// queried on screen load via useEffect
const bookPublisherObject: Parse.Object = bookPublisher;
const bookGenreObject: Parse.Object = bookGenre;
// bookAuthors can be an array of Parse.Objects, since the book
// may have more than one Author
const bookAuthorsObjects: [Parse.Object] = bookAuthors;
// Creates a new parse object instance
let Book: Parse.Object = new Parse.Object('Book');
// Set data to parse object
// Simple title field
Book.set('title', bookTitleValue);
// 1:1 relation, need to check for uniqueness of value before creating a new ISBD object
let isbdQuery: Parse.Query = new Parse.Query('ISBD');
isbdQuery.equalTo('name', bookISBDValue);
let isbdQueryResult: Parse.Object = await isbdQuery.first();
if (isbdQueryResult !== null && isbdQueryResult !== undefined) {
// If first returns a valid object instance, it means that there
// is at least one instance of ISBD with the informed value
Alert.alert(
'Error!',
'There is already an ISBD instance with this value!',
);
return false;
} else {
// Create a new ISBD object instance to create a one-to-one relation on saving
let ISBD: Parse.Object = new Parse.Object('ISBD');
ISBD.set('name', bookISBDValue);
ISBD = await ISBD.save();
// Set the new object to the new book object ISBD field
Book.set('isbd', ISBD);
}
// One-to-many relations can be set in two ways:
// add direct object to field (Parse will convert to pointer on save)
Book.set('publisher', bookPublisherObject);
// or add pointer to field
Book.set('genre', bookGenreObject.toPointer());
// many-to-many relation
// Create a new relation so data can be added
let authorsRelation = Book.relation('authors');
// bookAuthorsObjects is an array of Parse.Objects,
// you can add to relation by adding the whole array or object by object
authorsRelation.add(bookAuthorsObjects);
// After setting the values, save it on the server
try {
await Book.save();
// Success
Alert.alert('Success!');
navigation.goBack();
return true;
} catch (error) {
// Error can be caused by lack of Internet connection
Alert.alert('Error!', error.message);
return false;
}
} catch (error) {
// Error can be caused by lack of value selection
Alert.alert(
'Error!',
'Make sure to select valid choices in Publisher, Genre and Author fields!',
);
return false;
}
};
Look how the bookAuthorsObjects
are set to the new book Parse.Object
instance. To create a many-to-many relation, you first need to create a new Parse.Object.relation
and then add the related objects to it either one by one or by passing an array of Parse.Object
using the Parse.Object.relation.add
method. Parse will create a relation type column and also a relational table on your database. Parse will also create a link for easy access to this new table in the field column in the dashboard.
Be aware that you can also manage objects and add new ones to your relation through the dashboard once it is created, so remember this shortcut when developing your application.
Step 3 - Querying many-to-many relations
Retrieving results from many-to-many related objects queries need some additional steps comparing to regular ones. Take a look at the query function in the book registers list screen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const queryBooks = async function () {
// These values come from state variables linked to
// the screen query RadioButton.Group fields, with its options being every
// parse object instance saved on server from the referred class, which is
// queried on screen load via useEffect; these variables retrievie the user choices
// as a complete Parse.Object;
const queryPublisherValue = queryPublisher;
const queryGenreValue = queryGenre;
const queryAuthorValue = queryAuthor;
const queryIsbdValue = queryIsbd;
// Reading parse objects is done by using Parse.Query
const parseQuery = new Parse.Query('Book');
// One-to-many queries
if (queryPublisherValue !== '') {
parseQuery.equalTo('publisher', queryPublisherValue);
}
if (queryGenreValue !== '') {
parseQuery.equalTo('genre', queryGenreValue);
}
// One-to-one query
if (queryIsbdValue !== '') {
parseQuery.equalTo('isbd', queryIsbdValue);
}
// Many-to-many query
// In this case, we need to retrieve books related to the chosen author
if (queryAuthorValue !== '') {
parseQuery.equalTo('authors', queryAuthorValue);
}
try {
let books = await parseQuery.find();
// Many-to-many objects retrieval
// In this example we need to get every related author Parse.Object
// and add it to our query result objects
for (let book of books) {
// This query is done by creating a relation and querying it
let bookAuthorsRelation = book.relation('authors');
book.authorsObjects = await bookAuthorsRelation.query().find();
}
setQueriedBooks(books);
return true;
} catch (error) {
// Error can be caused by lack of Internet connection
Alert.alert('Error!', error.message);
return false;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const queryBooks = async function (): Promise<boolean> {
// These values come from state variables linked to
// the screen query RadioButton.Group fields, with its options being every
// parse object instance saved on server from the referred class, which is
// queried on screen load via useEffect; these variables retrievie the user choices
// as a complete Parse.Object;
const queryPublisherValue: Parse.Object = queryPublisher;
const queryGenreValue: Parse.Object = queryGenre;
const queryAuthorValue: Parse.Object = queryAuthor;
const queryIsbdValue: Parse.Object = queryIsbd;
// Reading parse objects is done by using Parse.Query
const parseQuery: Parse.Query = new Parse.Query('Book');
// One-to-many queries
if (queryPublisherValue !== '') {
parseQuery.equalTo('publisher', queryPublisherValue);
}
if (queryGenreValue !== '') {
parseQuery.equalTo('genre', queryGenreValue);
}
// One-to-one query
if (queryIsbdValue !== '') {
parseQuery.equalTo('isbd', queryIsbdValue);
}
// Many-to-many query
// In this case, we need to retrieve books related to the chosen author
if (queryAuthorValue !== '') {
parseQuery.equalTo('authors', queryAuthorValue);
}
try {
let books: [Parse.Object] = await parseQuery.find();
// Many-to-many objects retrieval
// In this example we need to get every related author Parse.Object
// and add it to our query result objects
for (let book of books) {
// This query is done by creating a relation and querying it
let bookAuthorsRelation = book.relation('authors');
book.authorsObjects = await bookAuthorsRelation.query().find();
}
setQueriedBooks(books);
return true;
} catch (error) {
// Error can be caused by lack of Internet connection
Alert.alert('Error!', error.message);
return false;
}
};
In this case, to query any books related to a specific author, you need to perform a Parse.Query.equalTo
method passing the Parse.Object
instance as the parameter. After querying, Parse will not store Parse.Object
instances from many-to-many relational fields, only a reference to the related class name, such as {"__type": "Relation", "className": "Author"}
. To retrieve and show data from these object instances, you need to create a relation and query it again, storing the results in an object array of your own. After that, you may iterate over the object array and use the Parse.Object.get
method to retrieve the author’s name. Here is how the list screen looks like by using these procedures:
Conclusion
At the end of this guide, you learned how to create and query many-to-many relations in Parse on React Native. In the next guide, we will show you how to do one-to-one queries and relations.