The Realm Data Model
At the heart of the Realm Platform is the Realm Database, an open source, embedded database library optimized for mobile use. If you’ve used a data store like SQLite or Core Data, at first glance the Realm Database may appear to be an object-relational mapper (ORM) backed with a lightweight relational database. This is not, however, what Realm is. Instead, Realm uses a “data container” model. Your data objects are stored in a Realm as objects. This gives Realm several significant advantages:
- Realms store native objects: The Realm Database has bindings for many popular languages for mobile app development, including Swift, Java, Objective-C, C#, and JavaScript (using React Native). The objects you store in a Realm are the objects you work with in the rest of your code.
- Realms are zero-copy: data is not copied in and out of the database to be accessed; you’re working with the objects directly.
- Realms implement the live objects pattern: if you have an instance of an object stored in a Realm and something else in your application updates that object, your instance will reflect those changes.
- Realms are cross-platform: as long as you don’t store platform-specific objects in a Realm, the data can be synced across operating systems. (In fact, the actual Realm data files can be copied between platforms.)
- Realms are offline-first: Your application works the same way with a Realm whether or not it’s synchronized via the Realm Object Server, and whether or not the device is online at any given moment. When connectivity is available, changes will be synchronized transparently.
- Lastly, Realms are ACID-compliant.
The concepts being discussed are cross-platform; simple examples will be given in Swift. Consult the documentation section for your preferred binding for examples in your language.
What is a Realm?
A Realm is an instance of a Realm 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.
The hypothetical chat application above 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 applicaion 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
In JavaScript, the schema for your model must be passed into the constructor as part of the configuration object. See Models in the JavaScript documentation for details.
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
(The configuration object may specify other values, depending on your language, and is usually used for migrations when those are necessary. As noted above, the configuration object also includes the model schema in JavaScript.) If you don’t provide a configuration object, you’ll open the default Realm, which is a local Realm specific to that application.
Opening a synchronized Realm, therefore, might look like this. For this example, we’ll assume the Realm is named "settings"
.
// create a configuration object
let realmUrl = URL(string: "realms://example.com:9000/~/settings")!
let realmUser = SyncCredentials.usernamePassword(username: username, password: password)
let config = Realm.Configuration(user: realmUser, realmURL: realmUrl)
// open the Realm with the configuration object
let settingsRealm = try! Realm(configuration: config)
Opening a local or in-memory Realm is even simpler—it doesn’t need a URL or user argument–and opening the default Realm is just one line:
let defaultRealm = try! Realm()
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. 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
); theuser-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 ~
.)
Note: the realms://
prefix is analogous to https://
, e.g., the “s” indicates the use of SSL encryption. A Realm URL beginning with realm://
is unencrypted.
Permissions
Realms managed by the Realm Object Server have access permissions that control whether it’s public, private, or shared. Permissions are set on each Realm using three boolean flags:
- The
mayRead
flag indicates a user can read from the Realm. - The
mayWrite
flag indicates a user can write to the Realm. - The
mayManage
flag indicates a user can change permissions on the Realm for other users.
The permission flags can be set on a default basis and a per-user basis. When a user requests access for a Realm, first the Object Server checks to see if there are per-user permissions set for that user on that Realm. If there are no per-user permissions set for that user, the default permissions for the Realm are used. For example, a Realm might have mayRead
set true by default, with individual users being granted mayWrite
permissions.
By default, a Realm is private: the owner has all permissions on it, and no other user has any permissions for it. Other users must be explicitly granted access. (Admin users, though, are always granted all permissions to all Realms on the Object Server.)
For more details about permissions, see:
- Authorization for the Realm Object Server
- Access Control for your language SDK:
Models and Schema
To store an object in a traditional relational database, the object’s class (say, User
) corresponds to a table (users
), 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 owner
property 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! In Java, all properties are nullable by default, and required properties must be given the @Required
notation. JavaScript marks property types and default values in a different fashion.
Relations
In relational databases, relations between tables are defined with primary keys and foreign keys. If one or more Dog
s 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:
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
dynamic var breed: String? = nil
dynamic var owner: Person?
}
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
This is similar in other languages. Here’s the Java implementation of Dog
:
public class Dog extends Realm Object {
@Required
private String name = "";
@Required
private Integer age = 0;
private String breed;
private Person owner;
}
To-Many Relationships
Let’s show the matching Person
class for Dog
:
class Person: Object {
let name = ""
let dogs = List<Dog>()
}
A list in Realm contains one or more Realm objects. To add Fido to Bob’s list of dogs:
bob.dogs.append(fido)
Again, this is similar in other languages; 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;
(Note that in Java, RealmList
properties are always considered required, so they don’t need the @Required
notation. JavaScript defines list properties in a different fashion. As always, consult the documentation and API reference for your language SDKs for specifics.)
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 owner
property. 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 language bindings 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.owner
will point to bob
.
Currently, Objective-C, Swift, and Xamarin provide linking objects.
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:
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:
Migrations
Since data models in Realm are defined as standard classes, making model changes is very easy. Suppose you had a Person
model which contained these properties:
class Person: Object {
dynamic var firstName = ""
dynamic var lastName = ""
dynamic var age = 0
}
And you wished to combine the firstName
and lastName
properties into a single name
property. The model change is simple:
class Person: Object {
dynamic var name = ""
dynamic var age = 0
}
However, now the schema in the Realm file doesn’t match your model—when you try to use the existing Realm, errors will happen. To fix this, you’ll need to perform a migration—essentially, call a small bit of code in your application which can detect the old version of the Realm schema and upgrade it on disk.
Migrations With Local Realms
The specifics of how to perform a migration vary from language to language, but the basics are always similar:
- When you open the Realm, a schema version and migration function are passed to the constructor in the configuration object.
- If the existing schema version is equal to the schema version passed to the constructor, things proceed as normal (no migration is performed).
- If the existing schema version is less than the schema version passed to the constructor, the migration function is called.
In Swift, a migration function added to the configuration object might look like this.
let config = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in
let firstName = oldObject!["firstName"] as! String
let lastName = oldObject!["lastName"] as! String
newObject!["name"] = "\(firstName) \(lastName)"
}
}
})
If no version is specified for a schema, it defaults to 0
.
Migrations With Synced Realms
If a Realm is synced, the rules for performing migrations are a little different.
- Additive changes, such as adding a class or adding a property to an existing class, are applied automatically.
- Removing a property from a schema will not delete the field from the database, but rather instruct Realm to ignore that property. New objects will continue be created with those properties, but they will be set to
null
. Non-nullable fields will be set to appropriate zero/empty values:0
for numeric fields, an empty string for string properties, and so on. - Custom migration functions cannot be invoked on synced Realm migrations, and supplying one will throw an exception.
- Destructive changes—that is, changes to a Realm schema that require changes to be made to code that interacts with that Realm—are not directly supported. This includes changes to property types that keep the same name (e.g., changing from a nullable string to a non-nullable string), changing a primary key, or changing a field from optional to required (or vice-versa).
If you can’t use a custom migration function, how do you make schema changes like the previous example on a synced Realm? There are two ways to do it:
- On the client side, you can write a notification handler that performs the changes.
- On the server side, you can write a Node.js function that performs them.
Neither of these will allow you to apply destructive changes to an existing Realm. Instead of doing that, create a new synced Realm with the new schema, then create a function which listens for changes on the old Realm and copies values to the new one. This can happen on either the client or server side.
More about Migrations
To see examples and more details in your preferred language binding, consult the language-specific documentation for migrations: