Realm Data Model

Background

This document will explain a number of common concepts which are important for building an application which uses Realm for persistence and data synchronization. The concepts being discussed are cross-platform; simple examples will be given in Swift. Consult the documentation section for your preferred SDK for examples in your language. While this document may mention local Realms, it aims to explain important concepts with a focal point on working with synchronized Realms.

What is a Realm?

A Realm is an instance of a Realm Mobile Database container. Realms can be local, synchronized, or in-memory. In practice, your application works with any kind of Realm the same way. In-memory Realms have no persistence mechanism, and are meant for temporary storage. A synchronized Realm uses the Realm Object Server to transparently synchronize its contents with other devices. While your application continues working with a synchronized Realm as if it's a local file, the data in that Realm might be updated by any device with write access to that Realm--so the Realm could represent a channel in a chat application, for instance, being updated by any user talking in that channel. Or, it could be a shopping cart, accessible only to devices owned by you.

If you're used to working with other kinds of databases, here are some things that a Realm is not:

  • A Realm is not a single application-wide database. While an application generally only uses one SQL database, an application often uses multiples Realms to organize data more efficiently, or to "silo" data for access control purposes.

  • A Realm is not a table. Tables typically only store one kind of information: user records, email messages, and so on. But a Realm can contain multiple kinds of objects.

  • A Realm is not a schemaless document store. Because object properties are analogous to key/value pairs, it's easy to think of a Realm as a document store, but objects in a Realm have defined schemas that support giving values defaults or marking them as required or optional.

For example, a hypothetical chat application might use one synchronized Realm for public chats, another synchronized Realm storing user data, yet another synchronized Realm for a "master channel list" that's read-only to non-administrative users, and a local Realm for persisted settings on that device. Or a multi-user application on the same device could store each user's private data in a user-specific Realm. Realms are lightweight, and your application can be using several at one time. (On mobile platforms, there are some resource constraints, but up to a dozen open at once should be no issue.)

Opening a Realm

When you open a Realm, you pass the constructor a configuration object that defines how to access it. The configuration object specifies where the Realm database is located:

  • a path on the device's local file system

  • a URL to a Realm Object Server, with appropriate access credentials (user/password, authentication token)

  • an identifier for an in-memory Realm

  • an optional identifier for the sync type (if left unspecified, the Realm will use query-based sync)

Opening a synchronized Realm, therefore, might look like this. For this example, we'll assume the Realm is named "settings".

// Create the configuration
let syncServerURL = URL(string: "realms://myinstance.cloud.realm.io/~/settings")!
let config = user.configuration(realmURL: syncServerURL);
// Open the remote Realm
let realm = try! Realm(configuration: config)
// Any changes made to this Realm will be synced across all devices!

Realm URLs

Synchronized Realms may be public, private, or shared. They're all accessed the same way---on a low level, there's no difference between them at all. The difference between them is access controls, which users can read and write to them. The URL format may also look a little different:

  • A public Realm can be accessed by all users. Public realms are owned by the admin user on the Realm Object Server, and are read-only to non-admins by default. These Realms have URLs of the form realms://server/realm-name.

  • A private Realm is created and owned by a user, and by default only that user has read and write permissions for it. Private Realms have URLs of the form realms://server/user-id/realm-name.

  • A shared Realm is a private Realm whose owner has granted other users read (and possibly write) access---for instance, a shopping list shared by multiple family members. It has the same URL format as a private Realm (realms://server/user-id/realm-name); the user-id segment of the path is the ID of the owning user. Sharing users all have their own local copies of the Realm, but there's only one "master" copy synced through the Object Server.

Very often in private Realm URLs, you'll see a tilde (~) in place of the user ID; this is a shorthand for "fill in the current user's ID." This makes it easier for application developers to refer to private Realms in code: you can simply refer to a private settings Realm, for example, with realms://server/~/settings.

You can think of Realm URLs as matching a file system: public Realms live at the top-level "root" directory, with user-owned Realms in subdirectories underneath. (The tilde was chosen to match the Unix style of referring to a user's home directory with ~.)

To learn more about opening and working with Realms, head over to our documentation on setting up Realms.

Permissions

Query-based Sync is not recommended. For applications using Realm Sync, we recommend Full Sync. Learn more about our plans for the future of Realm Sync here.

Realms managed by the Realm Object Server have access permissions that control various levels of access control for Realm users. The access control options available to the developer will depend on which type of sync has been chosen. Traditional full-sync is most performant but only allows permissions to be set at the individual Realm level. Query-based sync is more flexible and allows permissions to be set at the Realm, Class, and Object level, but this added complexity naturally comes with decreased performance. Query-based sync is not recommended for production.

To learn more about the types of permissions, consult the in depth documentation.

Models and Schema

To store an object in a traditional relational database, the object's class (say, Dog) corresponds to a table (dogs), with each object instance being mapped to a table row and the object's properties mapping to table columns. In Realm, though, your code works with the actual objects.

class Dog: Object {
dynamic var name = ""
dynamic var age = 0
dynamic var breed: String? = nil
dynamic var owner: Person?
}

Our Dog object has four properties, two of which are required and have default values (name, with a default of the empty string, and age, with a default of 0). The breed property is an optional string, and the ownerproperty is an optional Person object. (We'll get to that.) Optional properties are sometimes called nullable properties by Realm, meaning that their values can be set to nil (or null, depending on your language). Optional properties don't have to be set on objects to be stored in a Realm. Required properties, like name and age, cannot be set to nil.Check your language SDK for the proper syntax for required and optional properties!

Persistence and Live Objects

There's a few things to keep in mind when working with Realms and objects in Realms.

  • Once an object has been added to a Realm (e.g., using realm.add or your language's equivalent), modifying that object in your code modifies it in the Realm, too. You don't need to call an update method or re-add it to the Realm to persist your changes. They'll be updated correctly.

  • This is true for synchronized Realms, too---there's nothing you need to do to "push" changes up to the Object Server. Modify the object, and when your device has network access, changes will be propagated up to the server and to any other Realm clients that are synchronizing with those Realms.

Relations

In relational databases, relations between tables are defined with primary keys and foreign keys. If one or more Dogs can be owned by one Person, then the Dog model will have a foreign key field that contains the primary key of the Person who owns them. This can be described as a "has-many" relationship: a Person has-many Dogs. The inverse relationship, Dog belongs-to Person, isn't explicitly defined in the database, although some ORMs implement it.

Realm has similar relationship concepts, declared with properties in your model schema.

To-One Relations

Let's revisit the Dog model above:

Swift
Java
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
dynamic var breed: String? = nil
dynamic var owner: Person?
}
public class Dog extends Realm Object {
@Required
private String name = "";
@Required
private Integer age = 0;
private String breed;
private Person owner;
}

The owner property is the Object subclass you want to establish a relationship with. This is all you need to define a "to-one" relationship (which could be either one-to-one or many-to-one). Now, you can define a relationship between a Dog and a Person:

let bob = Person()
let fido = Dog()
fido.owner = bob

To-Many Relationships

Let's show the matching Person class for Dog:

Swift
Java
class Person: Object {
let name = ""
let dogs = List<Dog>()
}

In Java, you use RealmList as the property type (to distinguish them from native Java lists):

public class Person extends RealmObject {
@Required
private String name;
private RealmList<Dog> dogs;

A list in Realm contains one or more Realm objects. To add Fido to Bob's list of dogs:

bob.dogs.append(fido)

Inverse Relationships

You'll note that defining the Person has-many Dogs relationship didn't automatically create a Dog belongs-to Person relationship; both sides of the relationship need to be set explicitly. Adding a Dog to a Person's dogs list doesn't automatically set the dog's ownerproperty. It's important to define both sides of this relationship: while it makes it easier for your code to traverse relationships, it's also necessary for Realm's notification system to work properly.

Some Realm SDKs provide "linking objects" properties, which return all objects that link to a given object from a specific property. To define Dog this way, our model could be:

class Dog: Object {
dynamic var name = ""
dynamic var age = 0
dynamic var breed: String? = nil
let owners = LinkingObjects(fromType: Person.self, property: "dogs")
}

Now, when we execute bob.dogs.append(fido), then fido.ownerwill point to bob.

Primary Keys

While Realm doesn't have foreign keys, it does support primary key properties on Realm objects. Declaring one of the properties on a model class to be a primary key enforces uniqueness: only one object of that class with the same primary key can be added to a Realm. Primary keys are also implicit indexes: querying an object on its primary key is extremely efficient.

For details about how to specify a primary key, consult your language SDK's documentation:

Primary keys also let Realm perform an "upsert" operation: if an object is added to a Realm with a new primary key, it will be inserted, but if the key exists, adding the object with the update flag will merge the object properties. That is, properties in the newly-added version of the object will overwrite the values of that property on the server, but any other properties on the server's copy of the object will retain their values, and the newly merged object will be downloaded to the client. Suppose you have a preference object called Settings, similar to a previous example:

class Settings: object {
dynamic var id = 0
dynamic var showToolbar = true
dynamic var linesShown = 5
}
// open Realm with an existing configuration object
let settingsRealm = try! Realm(configuration: config)

Suppose every Settings object uses the user ID as a unique primary key on the Realm Object Server. To create or update a single property on a user's Settings:

try! realm.write {
realm.create(Settings.self, value: ["showToolbar": false], update: true)
}

Note that you should use the create() method to create the new object here. Why? If the object being added didn't specify showToolbar it would inherit the default value of false, but Realm needs to know whether this is being explicitly set and should override a value of true on the server. By using the create method, we make this choice explicit.

Indexes

Adding an index to a property significantly speeds up some queries. If you're frequently making an equality comparison on a property---that is, retrieving an object with an exact match, like an email address---adding an index may be a good idea. Indexes also speed up exact matches with "contains" operators (e.g., name IN {'Bob', 'Agatha', 'Fred'}).

Consult your language SDK for information on how to set indexes:

Schema Changes

Synchronized Realms only support additive changes. This is to ensure that older clients can continue to sync with newer clients. As a result, synced schema migrations tend to be simpler, but they do work differently than schema migrations for local-only Realms. Head over to the syncing data documentation for more specific details.

Not what you were looking for? Leave Feedback