How to Create a Many-to-Many Relationship in Realm JS React Native
While building my app ThinkBack I decided to use Realm as my underlying data-store. ThinkBack allows users capture notes and categorize them using hashtags. A note can have many hashtags and a hashtag can have many notes. This is the basis of a many-to-many relationship.
Realm’s documentation on model relationships shows an example using a car and manufacturer model. In their example a car has a relationship with a manufacturer, and a manufacturer has a one-to-many relationship with a car. This example creates a “to-many” relationship from manufacturer to car simply by adding a property to the manufacturer model that references a collection of cars.
By default a relationship is one-sided, for example a manufacturer has a link to cars, but cars don’t link back to the manufacturer. In order for a car to know its manufacturer Realm leverages the concept of an inverse relationship. An inverse relationship is simply a link from one object back to another, and is defined by setting the “type” of the linking property as a linkingObjects
. This relationship must be explicitly defined otherwise a car will be ignorant to the fact it belongs to a manufacturer. The downside to this is that if you want to load the manufacturer associated with a car you need to issue a separate query in Realm rather than simply dot-notationining into it (e.g. myCar.manufacturer.name
)
Below is the example from the documentation. Line 11 defines the “to-many” relationship from manufacturer to car, and line 27-31 define the inverse relationship from car back to manufacturer.
class ManufacturerInverse extends Realm.Object {
_id!: BSON.ObjectId;
name!: string;
cars!: Realm.List<CarInverse>;
static schema: Realm.ObjectSchema = {
name: 'ManufacturerInverse',
properties: {
_id: 'objectId',
name: 'string',
// A manufacturer's related CarInverse objects
cars: 'CarInverse[]',
},
};
}
class CarInverse extends Realm.Object {
_id!: BSON.ObjectId;
model!: string;
manufacturer!: Realm.List<ManufacturerInverse>;
miles?: number;
static schema: Realm.ObjectSchema = {
name: 'CarInverse',
properties: {
_id: 'objectId',
model: 'string',
miles: 'int?',
// A car's related ManufacturerInverse objects
manufacturer: {
type: 'linkingObjects',
objectType: 'ManufacturerInverse',
property: 'cars',
},
},
};
}
Creating a many-to-many relationship between Hashtag and Note
Creating a many-to-many relationship between Hashtags and Notes is exactly the same as the relationship between Cars and Manufacturers.
Note Model
Line 30 defines a “to-many” relationship between a Note and Hashtag. This is equivalent to the manufacturer-to-car relationship in the example above. When a note
is queried in Realm the hashtags
object will point to N number of hashtags that reference this note. This is handy if you want to load all hashtags for a given note without issuing a separate query (e.g. myNote.hashtags.forEach(...)
).
import uuid from 'react-native-uuid';
import Hashtag from './Hashtag';
export interface NoteProps {
id?: string;
content: string;
hashtags: Hashtag[];
}
export default class Note {
public id: string;
public content: string;
public hashtags: Hashtag[];
constructor({
id = uuid.v4().toString(),
content,
hashtags,
}: NoteProps) {
this.id = id;
this.content = content;
this.hashtags = hashtags;
}
static schema: Realm.ObjectSchema = {
name: 'Note',
properties: {
id: 'string',
content: 'string',
hashtags: `Hashtag[]`,
},
primaryKey: 'id',
};
}
Hashtag Model
Line 26 defines a linking object property notes
which links back to the hashtags
property of the Notes model. This inverse relationship effectively creates a collection of notes that have a reference to this particular hashtag. When a hashtag is queried in Realm the notes
object will point to N number of notes that reference this hashtag. This is handy if you want to load all notes for a given hashtag without issuing a separate query (e.g. myHashtag.notes.forEach(...)
).
import uuid from 'react-native-uuid';
import Note from './Note';
interface HashtagProps {
id?: string;
name: string;
}
export default class Hashtag {
public id: string;
public name: string;
public notes: Note[];
constructor({ id = uuid.v4().toString(), name }: HashtagProps) {
this.id = id;
this.name = name;
this.notes = [];
}
static schema: Realm.ObjectSchema = {
name: 'Hashtag',
properties: {
id: 'string',
name: 'string',
// all notes who have this hashtag added in the "hashtags" property.
notes: { type: 'linkingObjects', objectType: 'Note', property: 'hashtags' },
},
primaryKey: 'id',
};
}
With this relationship in place you can now load a note or a hashtag and have a direct reference to the other side of the relationship:
const note = realm.objectForPrimaryKey<Note>('Note', id);
// load all hashtags associated with the given note and extract their names.
const hashtagNames = note.hashtags.map(h => h.name)
Hi, I’m Sam.
I’m a programmer and a DIYer. When I’m not finding things to build I enjoy cooking, hiking, camping and traveling the world with my best friend. Say Hello!