Database Basics

First, let's talk about databases and ORMs.

The database is just a collection of information organized in some way. To interact with data, each database has its own set of commands. For SQL databases, it is SQL, that looks like:

        SELECT ID, NAME FROM USERS
        WHERE ID = 3
      
In this example, we get ID and name of a user with ID = 3

Since not every developer familiar with database syntax, ORMs were introduced. ORM stands for Object-Oriental Mapping and helps us to manipulate the data with the programming language of choice instead of database command. If we wanted to find a user with ID = 3 with ORM, we would use syntax that could look like this:

        user = User.where("id = 3")
      
ORM will automatically translate it into database syntax for us.

There are lots of ways to work with databases in iOS:

These are just most popular examples, and you can find much more. However, it is important to choose something stable, because working with data is one of the most important tasks of the app, and even loss of one record can break the whole app.

Even though Core Data is stable, documented, and developed by Apple, we are not going to use it in this tutorial. Core Data is a very complex framework - it is hard to understand for a beginner, it needs lots of additional and difficult code to make it work, documentation is outdated and is not always correct.

Here at Mova, we always try to use Realm when possible. It is stable, the code is very easy to read and understand, and it is also very fast.

Tables

There are different kinds of databases: relational DB, key-value data stores, etc. Realm is a relational DB - it uses tables to store data. The table has columns and rows (records). Here is the simple example of how does table in a relational DB looks like. In this example we use "Company" table:

This table has next properties (or columns): The table has six records. Even though the current example has all values set for each property, it is fine to leave some properties empty. If we had this table available in the app, we would work with it like in example below:
        companies = Company.where("name = 'Company 1'")
        # => array of companies where name = 'Company 1'

        company = company.first
        company.name
        # => "Company 1"

        company.code
        # => "c1"
      

Schemas

Database Schemas are structures that describe tables with its properties and property types in a Database. The database won't store any data if it has no tables, and table won't store any data if it does not know which properties of which type can be stored.

In a given example with Companies, schema has only 1 table with next properties:

It is important to set correct data types for each property. The database will need to know how much space to allocate for each property.

Realm schemas are written in Objective-C. Their code is very easy to understand if you are familiar with Objective-C. Otherwise, you can check comments next to each line. In Objective-C comments start with //. Creating a schema is very easy, but has some Objective-C specific. For example to create a class in Ruby, we just use

        class SomeClass
          # code
        end
      
However in Objective-C first we create some kind of description (header) file, which has a list of all properties and methods of the class. This description is called Interface. Its file extension should always be .h All method definitions are located in another (implementation) file. Extension of implementation file is .m.

However, it is also possible to keep both Interface and Implementation in implementation (.m) file. But you will still need to have Interface at the beginning of the file.

Let's see how an empty class will look like. Let's use one file for simplicity:

        
        // describe MainClass' properties:
        @interface MainClass

        @property NSDate   *created_at;
        @property NSString *name;

        @end

        // describe OtherClass' properties:
        @interface OtherClass

        @property NSDate    *created_at;
        @property NSInteger *index;

        @end

        // Implementation part:

        // create MainClass class
        @implementation MainClass
        @end

        // create OtherClass class
        @implementation OtherClass
        @end
      
If we could write schemas in Ruby, it would look like:
        class SomeClass
          attr_accessor :created_at, :index
        end

        class OtherClass
          attr_accessot :created_at, :index
        end
      
The biggest difference here is that Ruby is a dynamic language, and we don't set variable's type.

To create a valid schema class, we need to include Realm first, and then base all classes on a RLMObject:

        // include Realm:
        #import <Realm/Realm.h>


        // MainClass is a subclass of RLMObject:
        @interface MainClass : RLMObject

        @property NSDate   *created_at;
        @property NSString *name;

        @end

        // OtherClass is a subclass of RLMObject:
        @interface OtherClass : RLMObject

        @property NSDate    *created_at;
        @property NSInteger *index;

        @end

        // And implementation:

        // create MainClass class
        @implementation MainClass
        @end

        // create OtherClass class
        @implementation OtherClass
        @end
      
This is it! Realm will create a MainClass table and OtherClass table. MainClass table will have two properties: created_at with Date type, and name with a String type. OtherClass will have two properties as well: created_at with Date type, and index with an Integer type.

Available types

Realm can use next classes for its properties:

You can read more about models at Realm's docs or RLMObject Class Reference

Relationships

Sometimes you need to connect one object to another. In these cases, we will use relationships. For example, we have Child and Parent classes. A Child can have only one father and only one mother. However, parents can have multiple children. It means that Child will have two properties of Parent class: father and mother. On the other hand, Parent will have one property children, of an Array type.

        #import <Realm/Realm.h>

        // Class Child will use Class Parent BEFORE it has been declared.
        // We need to make Objective-C compiler understand that this is not a
        // mistake by using next command:
        @class Parent;

        @interface Child : RLMObject

        @property BOOL          likes_math;
        @property NSInteger     favourite_number;
        @property NSString     *name;
        @property NSString     *sex;
        @property NSDate       *birthday;

        // Child has one mother, and one father:
        @property Parent *mother;
        @property Parent *father;

        @end

        // Declare a custom type, array of Child objects:
        RLM_ARRAY_TYPE(Child)

        @interface Parent : RLMObject

        @property NSString     *job;
        @property NSString     *name;
        @property NSString     *sex;
        @property NSDate       *birthday;
        @property BOOL          likes_ruby;
        @property NSInteger     apps_in_appstore;

        // Parent can have many children. We create an
        // array of Child objects with syntax like this:
        @property RLMArray  *children;

        @end
      
More on relationships can be found at Realm's docs: Relationships

Relationships can be used in code like this:

        son = Child.new
        son.name = "John"

        daughter = Child.new
        daughter.name = "Lily"

        father = Parent.new
        father.name = "Jack"

        # create `father` relation:
        son.father = father
        daughter.father = father

        # create `children` relation:
        father.children << son
        father.children << daughter

        # in a future you can always get parent of children relation:

        parent = Parent.where("name = 'Jack'").first
        parent.children
        # => returns array of parent's children, in our case John and Lily

        lily = Child.where("name = 'Lily'").first
        lily.name
        # => "Lily"

        jack = lily.parent
        # => Parent object linked to Lily

        jack.name
        # => "Jack"
      

Migrations

Schema changes are inevitable. Property names can be changed or new ones added, new classes created or removed, etc. If you just add your changes to the schema.m file, it will not work, at least because the app won't know what to do with new or deleted properties. For example, you had a schema Person, that has last_name and first_name:

        #import <Realm/Realm.h>

        @interface Person : RLMObject

        @property NSString     *first_name;
        @property NSString     *last_name;

        @end
      
Later you decided to merge them into name property. Your DB already has some Person records, and it will need to know what to do with deleted columns, and how to populate new name column.

To change DB schema, we are going to use migrations. This is a small part of the code that lets us tell the app how to update DB with older schema to a newer DB schema. Using motion-realm gem your migration code will look like this:

        # versions start from 0. Next version is 1.
        RLMRealmConfiguration.migrate_to_version(1) do |migration, old_version|

          # Tell the app what to do with updated Person objects:
          migration.enumerate "Person" do |old_object, new_object|
            # user has not updated his app to the 1st schema version yet:
            if old_version < 1
              new_object.name = old_object.first_name + " " + old_object.last_name
            end
          end
        end
      
This is it! During migration app will just merge first_name and last_name values into a new name field.

It is important to have schema version checks for all schema version you had: some users update their apps more often, some less. Imagine user X updated the app in time, and his schema has been updated, but user Y installed the app and did not update it for three months. DB schema version on his device is 0, and your latest update already has 2nd schema version. In this case, his DB needs to be updated step by step, from 0 to 1st, from 1st to 2nd. If your 2nd schema version adds age property, migration could look like this:

        # versions start from 0. Next version is 1.
        RLMRealmConfiguration.migrate_to_version(1) do |migration, old_version|

          # Tell the app what to do with updated Person objects:
          migration.enumerate "Person" do |old_object, new_object|
            # user has not updated his app to the 1st schema version yet:
            if old_version < 1
              new_object.name = old_object.first_name + " " + old_object.last_name
            end

            if old_version < 2
              new_object.age = 18
            end
          end
        end
      
Remember to have schema version checks for all your available schema versions. Also don't use nested if/elif/end statements - they will skip migration to latest version:
        # versions start from 0. Next version is 1.
        RLMRealmConfiguration.migrate_to_version(1) do |migration, old_version|

          # Tell the app what to do with updated Person objects:
          migration.enumerate "Person" do |old_object, new_object|

            # imagine old schema version is 0. It will be migrated to the
            # first version only:
            if old_version < 1
              new_object.name = old_object.first_name + " " + old_object.last_name
            elsif old_version < 2
              new_object.age = 18
            end
          end
        end
      

Usage

We know how to create schemas and how to update them. Let's figure out how can we create, read and delete objects.

First we will need to create a model for each class. In our case with Child and Parent classes, we would create two new files: /app/models/child.rb and /app/models/parent.rb With next content:

        # child.rb:
        class Child
          include MotionRealm
        end

        # parent.rb
        class Parent
          include MotionRealm
        end
      

To create instance of a Child or Parent class, simply use next command:

        child = Child.new
        parent = Parent.new
      
Remember that objects are not persisted yet! They exist only in memory, and they will be gone next time the user opens the app. You can save them in two ways:
        child.save

        # or in a block:
        RLMRealm.write do |realm|

          # add object to the database:
          realm << parent
        end
      
There is no difference between first and second way. In fact, save method just runs the same block, the developer just does not see it (you can find it in motion-realm sources if curious).

To retrieve data from the database you can use where method on a class:

        children = Child.where("name = 'John'")
        # => array of children with name 'John'

        # if you want to get all objects:
        all_children = Child.all

        # or just first child:
        first_child = Child.first
        # or
        first_child = Child.all.first
        last_child = Child.last

        # if you need to specify multiple parameters:
        children = Child.where("name BEGINSWITH 'J' AND age > 10")


        
      
Alternative way is to use NSPredicate:
        predicate = NSPredicate.predicateWithFormat "name == %@", "some cool name"
        cool_kids = Child.with_predicate predicate

        predicate = NSPredicate.predicateWithFormat "name CONTAINS %@ AND age < %@", "cool", 16
        kids = Child.with_predicate predicate
        
      
Apple has lots of info on predicates. Or you can check Realm's NSPredicate Cheatsheet

If you want to delete an object, it can be done in two ways:

        child.delete

        # or in a block:
        RLMRealm.write do |realm|

          # remove from database:
          realm.delete parent
        end

        # if you want to delete all objects of a class:
        Parent.delete_all

        # or to clear the whole DB:
        RLMRealm.write do |realm|
          realm.delete_all
        end
      
Just like with saving, delete method is just a shorthand for an RLMRealm.write block.

Notifications

Sometimes you will want to be notified when data inside your Realm changes. Each time write transaction is committed, Realm sends a notification. With motion-realm gem you can subscribe to them like this:

        @notification_token = realm.add_notification do |notification, realm|
          # some your actions here:
          p "Just updated Database!"
        end

        # don't forget to remove it when you no longer need it:
        realm.remove_notification @notification_token
      

Summary

In this chapter, we learned how to set up Realm database, and how to use it. Realm schemas may look complicated at first sight, but they are very easy. To create a schema, we need to create an Interface based on RLMObject, and to add all properties that we are going to use in the app. Later we will also need to create a Ruby class with the same name, and include MotionRealm module. You can learn more about Realm at their official page. To find out more about motion-realm, visit its Github page.

Book Index | Next