This is not the current version. View the latest documentation

イントロダクション

Realm JavaScriptはアプリケーションのモデル層を効率的に安全で迅速な方法で記述することができます。React NativeNode.jsで動作します。

下記はReact Nativeで利用する例です:

// モデルクラスとスキーマを定義します
class Car {}
Car.schema = {
  name: 'Car',
  properties: {
    make:  'string',
    model: 'string',
    miles: 'int',
  }
};
class Person {}
Person.schema = {
  name: 'Person',
  properties: {
    name:    {type: 'string'},
    cars:    {type: 'list', objectType: 'Car'},
    picture: {type: 'data', optional: true}, // オプショナル(NULL可)のプロパティ
  }
};

// モデルクラスを与えてデフォルトRealmを取得します
let realm = new Realm({schema: [Car, Person]});

// Realmオブジェクトを作成してローカルDBに保存します
realm.write(() => {
  let myCar = realm.create('Car', {
    make: 'Honda',
    model: 'Civic',
    miles: 1000,
  });
  myCar.miles += 20; // 保存済みの値を更新することもできます
});

// 'miles > 1000'に該当するCarオブジェクトを検索します
let cars = realm.objects('Car').filtered('miles > 1000');

// 上記の条件に該当するCarオブジェクトは1件です
cars.size // => 1

// もう一つ別のCarオブジェクトを保存します
realm.write(() => {
  let myCar = realm.create('Car', {
    make: 'Ford',
    model: 'Focus',
    miles: 2000,
  });

// 検索結果は自動的に最新の状態が映されます
cars.size // => 2

はじめに

インストール

Realm JavaScriptはnpmを利用してインストールすることができます。ソースコードはGitHubにあります。

Prerequisites

  • React Nativeアプリケーションの開発環境が整っている必要があります。React Nativeの開発環境のセットアップについては公式サイトの説明をご覧ください。
  • 実行環境としてiOS、およびAndroidをサポートしています。
  • React Native 0.20.0以降をサポートしています。
  • React Nativeのパッケージマネージャであるrnpmの最新版がグローバルにインストールされている必要があります。下記のコマンドを用いてインストールします。

    npm install -g rnpm

Installation

  • 新しくReact Nativeプロジェクトを作成します。

    react-native init <project-name>

  • 作成したプロジェクトのディレクトリに移動します(cd <project-name>)。さらにrealmを依存関係として追加します。

    npm install --save realm

  • 次に、react-nativeを使ってプロジェクトとrealmネイティブモジュールをリンクします。

    • React Nativeのバージョンが0.31.0 以上 の場合:

      react-native link realm
    • React Nativeのバージョンが0.31.0 未満 の場合:

      rnpm link realm

Android環境で利用する場合の注意: バージョンによっては、rnpmが正しくない設定を生成することがあります。Gradleを正しく更新している(android/settings.gradleandroid/app/build.gradle)にもかかわらず、Realmモジュールの追加に失敗することがあります。その場合は、まずrnpm linkコマンドでRealmモジュールが正しく追加されるかどうかを確認し、失敗する場合は、下記の手順を用いて手作業にてライブラリをリンクしてください。

  1. android/settings.gradleファイルに下記の2行を追加します。

    include ':realm'
    project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
  2. android/app/build.gradledependenciesに下記のようにcompileの指定を追加します。

    dependencies {
        compile project(':realm')
    }
  3. MainApplication.javaimport文とパッケージをリンクする指定を追加します。

    import io.realm.react.RealmReactPackage; // add this import
    
    public class MainApplication extends Application implements ReactApplication {
        @Override
        protected List<ReactPackage> getPackages() {
            return Arrays.<ReactPackage>asList(
                new MainReactPackage(),
                new RealmReactPackage() // add this line
            );
        }
    }

ここまでで、Realmを使用する準備が整いました。下記の定義をindex.ios.jsまたはindex.android.jsファイルのclass <project-name>に記述して、Realmが正しくセットアップされているか試してみてください。

const Realm = require('realm');

class <project-name> extends Component {
 render() {
   let realm = new Realm({
     schema: [{name: 'Dog', properties: {name: 'string'}}]
   });

   realm.write(() => {
     realm.create('Dog', {name: 'Rex'});
   });

   return (
     <View style={styles.container}>
       <Text style={styles.welcome}>
         Count of Dogs in Realm: {realm.objects('Dog').length}
       </Text>
     </View>
   );
 }
}

デバイスまたはシミュレータを使って実行できます。

ここで説明している内容はRealm Node.js SDKのDeveloper Editionのインストール方法です。Professional EditionまたはEnterprise Editionを利用する場合は、メールに記述されているインストール方法をご覧ください。

Realm Node.jsのインストールはNode Package Managerを利用します。

npm install --save realm

インストールが完了すれば、アプリケーション内でrequire('realm')と記述するだけで利用できます。

'use strict';

var Realm = require('realm');

var realm = new Realm({
  schema: [{name: 'Dog', properties: {name: 'string'}}]
});

realm.write(() => {
  realm.create('Dog', {name: 'Rex'});
});

Install examples

GitHubリポジトリにはソースコードに加え、サンプルコードも公開されています。

git clone https://github.com/realm/realm-js.git

クローンしたディレクトリに移動し、サブモジュールを更新します。

cd realm-js
git submodule update --init --recursive

Android環境で実行する場合は、NDKのインストールとANDROID_NDK環境変数を設定する必要があります。

export ANDROID_NDK=/usr/local/Cellar/android-ndk/r10e

React Nativeのサンプルはexamplesディレクトリにあります。各ディレクトリでnpm installを実行してください。

ヘルプ

  • 使い方に困ったときは、StackOverflowで#realmタグを付けて質問してください。私たちは毎日StackOverflowをチェックしています。
  • さらに複雑な問題に対する質問は、こちらの Slackチャットにて聞いてください。(質問は日本語で構いません)
  • 問題を発見した場合はGitHubのIssuesに報告してください。できる限り、ご使用のRealmのバージョン、エラーメッセージやログ、スアックトレースやRealmのデータファイル、問題を再現可能なプロジェクトなどを添えてください。
  • 機能のリクエストもGitHubのIssuesで教えてください。どのような機能が欲しいのか、また、どうしてその機能が必要なのかできるだけ具体的に教えてください。

もしクラッシュレポートツール(CrashlyticsやHockeyAppなど)を利用しているなら、ログコレクターを有効にしてください。Realmのログにはユーザーデータ以外のデバッグに有用なメタデータを含んでいます。それは私たちが問題を調査するときに非常に役に立ちます。

モデル

Realmのデータモデルは、スキーマの情報をRealmインスタンスの初期化時に渡すことによって定義されます。スキーマの情報とはモデルオブジェクトの名前、およびプロパティの名前や型などを示す一連の属性です。型は基本的な型に加えて、1対1の関連を示すobjectTypeや1対多の関連を示すリスト型が指定できます。またoptional(NULL可)やdefault(デフォルト値)についてもここで指定します。

var Realm = require('realm');

const CarSchema = {
  name: 'Car',
  properties: {
    make:  'string',
    model: 'string',
    miles: {type: 'int', default: 0},
  }
};
const PersonSchema = {
  name: 'Person',
  properties: {
    name:     'string',
    birthday: 'date',
    cars:     {type: 'list', objectType: 'Car'},
    picture:  {type: 'data', optional: true}, // オプショナル(NULL可)のプロパティ
  }
};

// CarとPersonのモデルクラスを指定してRealmを初期化します
let realm = new Realm({schema: [CarSchema, PersonSchema]});

既存のクラスを継承してモデルクラスを定義する場合は、スキーマをコンストラクタ内で定義して、コンストラクタをRealmの初期化時に渡します。

class Person {
  get ageSeconds() {
    return Math.floor((Date.now() - this.birthday.getTime()));
  }
  get age() {
    return ageSeconds() / 31557600000;
  }
}

Person.schema = PersonSchema;

// 注: `Person`クラスのコンストラクタをRealmに渡します
let realm = new Realm({schema: [CarSchema, Person]});

モデルクラスの定義ができていれば、オブジェクトをRealmに保存したり、検索することができます。

realm.write(() => {
  let car = realm.create('Car', {
    make: 'Honda',
    model: 'Civic',
    miles: 750,
  });

  // モデルに定義したすべてのプロパティを読み書きできます
  console.log('Car type is ' + car.make + ' ' + car.model);
  car.miles = 1500;
});

対応しているデータ型

Realmでは、次に示すデータ型を基本的なデータ型としてサポートしています: boolintfloatdoublestringdataおよびdate

  • bool型のプロパティは、JavaScriptにおけるBoolean型にマッピングされます。
  • intfloatおよびdouble型のプロパティは、JavaScriptにおけるNumber型にマッピングされます。内部的にはintおよびdouble型は64ビットの値として保存されます。一方float型は32ビットの値として保存されます。
  • string型のプロパティはString型にマッピングされます。
  • data型のプロパティはArrayBuffer型にマッピングされます。
  • date型のプロパティはDate型にマッピングされます。

基本的なデータ型をプロパティに指定する場合は、省略記法が使えます。プロパティの属性を含むオブジェクトを渡す代わりに、型名を示す文字列を渡します。

const CarSchema = {
  name: 'Car',
  properties: {
    // 下記のtypeプロパティの定義はどちらも同じ意味です
    make:   {type: 'string'},
    model: 'string',
  }
}

オブジェクト型のプロパティ

1対1の関連として、オブジェクト型のプロパティを指定するには、型にスキーマに定義した別のオブジェクトの名前(name)を指定します。

const PersonSchema = {
  name: 'Person',
  properties: {
    // 下記のtypeプロパティの定義はどちらも同じ意味です
    car: {type: 'Car'},
    van: 'Car',
  }
};

オブジェクト型のプロパティを使用する場合は、Realmを初期化する際に、指定したすべてのオブジェクト型がスキーマに定義されている必要があります。

// PersonSchema内で'Car'型のプロパティとしてCarSchemaが使われているので、CarSchemaも渡す必要があります。
let realm = new Realm({schema: [CarSchema, PersonSchema]});

オブジェクト型のプロパティにアクセスする場合は、標準の文法を用いて、ネストしたプロパティとしてアクセスします。

realm.write(() => {
  var nameString = person.car.name;
  person.car.miles = 1100;

  // JSON記法を用いて新しくCar型のプロパティを作成して代入します。
  person.van = {make: 'Ford', model: 'Transit'};

  // 同じCar型のプロパティであるcarとvanに同じインスタンスを設定します。
  person.car = person.van;
});

リスト型のプロパティ

1対多の関連として、リスト型のプロパティを指定するには、list型としてスキーマに指定し、同時に格納する要素をobjectTypeに指定します。

const PersonSchema = {
  name: 'Person',
  properties: {
    cars: {type: 'list', objectType: 'Car'},
  }
}

リスト型のプロパティにアクセスするとListオブジェクトが返ります。Listオブジェクトは通常のJavaScriptの配列オブジェクトとほとんど同じメソッドを持っています。大きく異なる点は、Listオブジェクトに対する変更はすべて自動的に永続化されるという点です。さらに、Listオブジェクトは取得したオブジェクトが保持しています。そのため、Listオブジェクトをプログラマが自分で生成することはできません。別のオブジェクトのプロパティとしてアクセスし、取得します。

let carList = person.cars;

// CarオブジェクトをListに追加します
realm.write(() => {
  carList.push({make: 'Honda', model: 'Accord', miles: 100});
  carList.push({make: 'Toyota', model: 'Prius', miles: 200});
});

let secondCar = carList[1].model;  // 添字を使って各要素にアクセスします

オプショナル(NULL可)プロパティ

各プロパティはoptional属性を用いて、オプショナル(NULL可)、または非オプショナル(NULL不可)として定義できます。

const PersonSchema = {
  name: 'Person',
  properties: {
    name:     {type: 'string'},               // 必須(非オプショナル・NULL不可)プロパティ
    birthday: {type: 'date', optional: true}, // オプショナル(NULL可)プロパティ

    // オブジェクト型のプロパティは常にオプショナル(NULL可)です
    car:      {type: 'Car'},
  }
};

let realm = new Realm({schema: [PersonSchema, CarSchema]});

realm.write(() => {
  // オプショナル(NULL可)プロパティは生成時にはnullまたはundefinedを設定できます
  let charlie = realm.create('Person', {
    name: 'Charlie',
    birthday: new Date(1995, 11, 25),
    car: null,
  });

  // オプショナル(NULL可)プロパティは`null`、`undefined`、
  // または通常の値のいずれかを設定できます
  charlie.birthday = undefined;
  charlie.car = {make: 'Honda', model: 'Accord', miles: 10000};
});

上記に示す通り、オブジェクト型のプロパティは明示的に指定しなくても常にオプショナルとして扱われます。一方、List型のプロパティはオプショナルとして定義することはできず、nullをセットすることはできません。List型のプロパティを空にする際は、プロパティに空の配列をセットします。

デフォルト値

各プロパティはdefault属性を用いて、デフォルト値を定義できます。デフォルト値を設定したいプロパティは、オブジェクトの生成時にそのプロパティはundefinedのままにしておきます。

const CarSchema = {
  name: 'Car',
  properties: {
    make:  {type: 'string'},
    model: {type: 'string'},
    drive: {type: 'string', default: 'fwd'},
    miles: {type: 'int',    default: 0}
  }
};

realm.write(() => {
  // `miles`プロパティは何も指定していないのでデフォルト値の`0`が設定されます。
  // また`drive`プロパティはここで指定しているので、デフォルト値は使用されず指定した値で上書きされます。
  realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'});
});

インデックス付きプロパティ

プロパティの定義中でindexed属性をtrueに設定すると、そのプロパティにはインデックスが付加されます。 intstring、およびbool型のプロパティはインデックスに対応しています。

You can add an indexed designator to a property definition to cause that property to be indexed. This is supported for int, string, and bool property types:

var BookSchema = {
  name: 'Book',
  properties: {
    name: { type: 'string', indexed: true },
    price: 'float'
  }
};

プロパティをインデックスに登録することは同値性を比較するクエリの速度を大幅に向上します。その代わりにオブジェクトを作成する速度は少し遅くなります。

プライマリキー

string型およびint型のプロパティについてはprimaryKey属性を用いてプライマリキーとして指定できます。プラオマリキーが定義されていると、オブジェクトの検索と更新を効率的に行えることに加え、値が重複していないことを保証できます。プライマリキーが設定されたオブジェクトは、Realmに保存した後でプライマリキーの値を変更することはできなくなります。

const PersonSchema = {
  name: 'Person',
  primaryKey: 'id',
  properties: {
    id:   'int',    // プライマリキー
    name: 'string'
  }
};

プライマリキーとして指定したプロパティは自動的にインデックスが付加されます。

書き込み

Realmへのオブジェクトの追加、変更、削除は、トランザクションの内部で行う必要があります。

トランザクションのオーバーヘッドは大きいのでコード中のトランザクションは、できる限り少なくなるように設計してください。

オブジェクトの生成

これまでに説明したように、オブジェクトの生成にはcreateメソッドを使用します。

let realm = new Realm({schema: [CarSchema]});

realm.write(() => {
  realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'});
});

ネストしたオブジェクト

オブジェクト型のプロパティを持つオブジェクトは、各オブジェクトのプロパティの値をJSONを用いて再帰的に子のプロパティも含めて一度に生成できます。

let realm = new Realm({schema: [PersonSchema, CarSchema]});

realm.write(() => {
  realm.create('Person', {
    name: 'Joe',
    // ネストしたオブジェクトを1度に再帰的に生成できます
    car: {make: 'Honda', model: 'Accord', drive: 'awd'},
  });
});

オブジェクトの更新

プロパティへの代入

オブジェクトを更新するには、トランザクションの中でプロパティに値を設定します。

realm.write(() => {
  car.miles = 1100;
});

プライマリキーを使ってオブジェクトを作成・更新する

モデルにプライマリキーを指定しているなら、オブジェクトがすでに存在する場合は更新、存在しない場合は新しく追加というように、追加または更新を一度に行うことができます。この機能を利用するにはcreateメソッドの3つ目の引数にtrueを渡します。

realm.write(() => {
  // Bookオブジェクトを生成して保存します
  realm.create('Book', {primaryId: 1, title: 'Recipes', price: 35});

  // 上で保存したBookオブジェクトのPriceプロパティをプライマリキーを指定して更新します
  realm.create('Book', {primaryId: 1, price: 55}, true);
});

上記の例では、最初に保存されたBookオブジェクトはプライマリキーとしてprimaryIdプロパティを持ち、プライマリキーは1です。次の行で同じプライマリキー1を持つオブジェクトを渡し、3つ目の引数をtrueに指定します。そのため、新しくオブジェクトが作成されるのではなく、既存のオブジェクトのpriceプロパティが更新されます。nameプロパティは渡しているオブジェクトに含まれていないので、更新されず元の値が維持されます。

オブジェクトの削除

オブジェクトを削除するにはトランザクションの中でdeleteメソッドを使用します。

realm.write(() => {
  // Bookオブジェクトを作成し、保存します
  let book = realm.create('Book', {primaryId: 1, title: 'Recipes', price: 35});

  // Bookオブジェクトを削除します
  realm.delete(book);

  // `Results`、`List`、またはJavaScriptの`Array`を渡すと
  // 1度に複数のオブジェクトを削除できます
  let allBooks = realm.objects('Book');
  realm.delete(allBooks); // すべてのBookオブジェクトを削除します
});

クエリ

Realmのクエリは、どれか1つのオブジェクト型を指定して保存されているオブジェクトをRealmから取得します。検索条件を指定して結果をフィルタしたり、並べ替えることもできます。すべてのクエリと検索結果のプロパティアクセスは自動的に遅延されます。実際のデータはオブジェクトとプロパティにアクセスしたときにのみ取得されます。このことにより、大量のデータでも効率よく扱うことができます。

クエリを実行するとResultsオブジェクトが返ります。Resultsは検索結果を表します。Resultsオブジェクトの内容を変更することはできません。

オブジェクトを検索するもっとも基本的なメソッドは、Realmobjectsメソッドです。引数で与えられた型のオブジェクトをすべて取得します。

let dogs = realm.objects('Dog'); // Realmに保存されているすべてのDogオブジェクトを取得します

検索条件を指定する

filteredメソッドにクエリ文字列で検索条件を渡すことで、Resultsオブジェクトに含まれるオブジェクトをフィルタすることができます。

下記の例では、先のDogオブジェクトをすべて取得する例に少し手を加えて、colorプロパティが”tan”かつnameが”B”から始まるオブジェクトを取得します。

let dogs = realm.objects('Dog');
let tanDogs = dogs.filtered('color = "tan" AND name BEGINSWITH "B"');

現在はNSPredicateの文法のうちの次に示す一部分のみをサポートしています。基本的な比較演算子である==!=>>=<<=は数値型のプロパティに対して使用できます。==BEGINSWITH、ENDSWITHCONTAINSは文字列に対して使用できます。文字列の比較では、==[c]BEGINSWITH[c]などのように、演算子に[c]を付加することによって大文字小文字を区別しない比較を行うことができます。関連のプロパティに対して検索条件を使用する場合は、クエリ中でcar.color == 'blue'`のようにキーパスを用いて指定します。

並べ替え

1つまたは複数のプロパティを指定してResultsを並べ替えることができます。下記の例では、milesプロパティの昇順で並べ替えます。

let hondas = realm.objects('Car').filtered('make = "Honda"');

// ホンダ製(make = "Honda")かつ、走行距離(miles)の昇順
let sortedHondas = hondas.sorted('miles');

クエリの実行結果(Results)の順序はソートしなければ保証されません。パフォーマンス上の都合により、オブジェクトの挿入順は保持されません。

検索結果(Results)の自動更新(ライブアップデート)

Resultsは常に最新の状態に自動的に更新されます。このため、同じ検索条件なら繰り返し検索を実行して、結果を取得し直す必要はありません。Resultsは常に現在の最新の状態を反映します。

let hondas = realm.objects('Car').filtered('make = "Honda"');
// hondas.length == 0

realm.write(() => {
  realm.create('Car', {make: 'Honda', model: 'RSX'});
});
// hondas.length == 1

この仕組みはすべてのResultsインスタンスに(検索条件や、並べ替えの有無にかかわらず)適用されます。

Resultsが持つこの性質によって、Realmは効率的で高速な処理を実現しています。さらにアプリケーションのコードをシンプルかつリアクティブにすることを可能にします。例えば、クエリの検索結果を表示するビューに対しては、Resultsを保持して直接データの表示に使うことで、アクセスするたびに再検索することなく、常に最新のデータを表示することができます。

通知を用いてRealmのデータが更新され、UIをアップデートする必要があることを検知できます。その場合も、Resultsを取得し直す必要はありません。

取得データの数を制限

Realm以外のほとんどのデータベースには検索結果を「ページネーション(ページング)」する仕組みが備わっています(例えばSQLiteの’LIMIT’句によるものなどです)。この仕組みはディスクの過剰な読み込み、あるいは一度に大量のデータをメモリに読み込むことを避けるために使われます。

Realmのクエリは遅延実行されるので、このような「ページネーション」の仕組みはまったく必要ありません。なぜなら、Realmはクエリの実行結果の要素に対して、実際にアクセスしたときだけオブジェクトを読み込むからです。

UIや実装の都合により、クエリの実行結果の一部分だけが必要だったとします。そのときは、単にResultsオブジェクトを用いて、必要な要素にだけアクセスすれば良いのです。

let cars = realm.objects('Car');

// データを5件に制限したい場合は、
// 単に最初から5番目までのオブジェクトにアクセスします
let firstCars = cars.slice(0, 5);

Realmについて

複数のRealmを使い分ける

複数のRealmファイルを別の場所に保存して使い分けることができると便利です。例えば、事前に用意した組み込み済みデータを、メインのデータファイルとは別に読み込み専用のRealmとして利用するなどです。

Realmオブジェクトの初期化時にpathを指定することで、Realmファイルの保存場所を指定できます。pathの指定はアプリケーションのドキュメントディレクトリからの相対パスになります。

// Realmをデフォルト値とは別の保存先を指定して取得します
let realmAtAnotherPath = new Realm({
  path: 'anotherRealm.realm',
  schema: [CarSchema]
});

デフォルトRealmの保存先

このドキュメントにおけるこれまでのコード例では、path引数を指定していなかったことにお気づきでしょう。path引数を指定しない場合は、デフォルトの保存先が使われます。デフォルトの保存先を知る、または変更するにはグローバルプロパティに定義されているRealm.defaultPathを使用します。

スキーマバージョン

Realmの初期化時に指定できる最後の1つのプロパティはschemaVersionです。指定しなかった場合は、デフォルトの値として0が使われます。既存のデータからスキーマを変更した場合は、必ずschemaVersionを初期化時に指定しなければなりません。もしスキーマが変更されているにもかかわらず、schemaVersionを指定しなかった場合は例外が発生します。

const PersonSchema = {
  name: 'Person',
  properties: {
    name: 'string'
  }
};

// schemaVersionのデフォルトは0です
let realm = new Realm({schema: [PersonSchema]});

const UpdatedPersonSchema = {
  // スキーマの`name`プロパティが同じなので`Person`オブジェクトのスキーマが
  // 更新されたことになります
  name: 'Person',
  properties: {
    name: 'string',
    dog:  'Dog'     // 新しいプロパティを追加
  }
};

// 下記の記述はスキーマが更新されているにもかかわらず、
// スキーマバージョンを指定していないので例外が発生します。
let realm = new Realm({schema: [UpdatedPersonSchema]});

// 下記の記述ではスキーマの更新が成功し、新しいスキーマのRealmを取得します
let realm = new Realm({schema: [UpdatedPersonSchema], schemaVersion: 1});

Realmを開く前に、スキーマバージョンがいくつであるかを知るには、下記のようにRealm.schemaVersionメソッドを使用します。

let currentVersion = Realm.schemaVersion(Realm.defaultPath);

マイグレーション

データベースを使ってる場合、時間が経つにつれ、データモデルは変更されていくものです。例えば、以下のPersonモデルについて考えてみてください。

var PersonSchema = {
  name: 'Person',
  properties: {
    firstName: 'string',
    lastName: 'string',
    age: 'int'
  }
}

ここで、firstNamelastNameを一つにして、nameプロパティが必要になったとします。 そこで以下のような単純な変更をインターフェースに加えることにします。

var PersonSchema = {
  name: 'Person',
  properties: {
    name: 'string',
    age: 'int'
  }
}

ここでのポイントは、もし以前に前のデータモデルでのデータが保存されている場合、新しく定義し直したデータモデルとディスクに保存されている古いデータモデルとの間に不整合が生じてしまいます。 マイグレーションを実行せずに新しいスキーマを用いてRealmを使おうとすると、例外が発生します。

マイグレーションを実行する

スキーマバージョンとマイグレーション処理(migration function)(データを移行しなくてよいなら不要です)を更新してマイグレーション処理を定義します。 マイグレーション処理では古いデータ構造から新しいデータ構造に変換するための処理を記述します。 マイグレーション処理を記述すると、より新しいスキーマバージョンを指定してRealmを開こうとした際に適用されます。

schemaVersionだけを更新して、マイグレーション処理を記述しなかった場合は、オートマイグレーションによりデータベースに自動的に新しいプロパティが追加され、古いプロパティは自動的に削除されます。 スキーマを更新する際に、古いプロパティを更新したり、新しいプロパティにデータを移行する必要がある場合は、マイグレーション処理を書く必要があります。

たとえば、上記のPersonクラスのマイグレーションについて考えてみましょう。 新しく追加したnameプロパティには、古いfirstNamelastNameプロパティを結合した値を設定するとします。w

var realm = new Realm({
  schema: [PersonSchema],
  schemaVersion: 1,
  migration: function(oldRealm, newRealm) {
    // スキーマバージョンを1に更新する際に1度だけ実行されます
    if (oldRealm.schemaVersion < 1) {
      var oldObjects = oldRealm.objects('Person');
      var newObjects = newRealm.objects('Person');

      // 保存されているすべてのオブジェクトをループして新しいnameプロパティに値を設定します
      for (var i = 0; i < oldObjects.length; i++) {
        newObjects[i].name = oldObjects[i].firstName + ' ' + oldObjects[i].lastName;
      }
    }
  }
});

var fullName = realm.objects('Person')[0].name;

一度マイグレーション処理が適用されると、その後は通常どおりにRealmとRealmオブジェクトが使用できます。

複数世代のマイグレーション

上記で示したマイグレーションのパターンでは、複数世代にわたるマイグレーションを実行した際に問題が起こることがあります。それはユーザーがプロパティのマイグレーションが必要なアプリのアップデートをスキップした場合に起こります。その場合、古いマイグレーションのコードを修正する必要に迫られるかもしれません。

この問題は、すべてのマイグレーションを古いものから順番に実行していくことで避けることができます。そうすることで、確実にすべてのマイグレーションが実行されて最新のデータ構造にアップデートされます。下記に示すパターンに従うことで、1度書いたマイグレーションのコードは2度と触る必要はありません。ただ、古いスキーマとマイグレーションのコードを将来にわたってもずっと保持し続ける必要があります。

下記の例を参考にしてください。

var schemas = [
  { schema: schema1, schemaVersion: 1, migration: migrationFunction1 },
  { schema: schema2, schemaVersion: 2, migration: migrationFunction2 },
  ...
]

// the first schema to update to is the current schema version
// since the first schema in our array is at
var nextSchemaIndex = Realm.schemaVersion(Realm.defaultPath);
while (nextSchemaIndex < schemas.length) {
  var migratedRealm = new Realm(schemas[nextSchemaIndex++]);
  migratedRealm.close();
}

// open the Realm with the latest schema
var realm = new Realm(schemas[schemas.length-1]);

通知

RealmResultsList型のオブジェクトではaddListenerメソッドを使って通知のコールバックを登録できます。オブジェクトに変更があったときは毎回この変更通知コールバックが呼ばれます。

通知は「Realmに対する通知」と「コレクションに対する通知」の2種類があります。「Realmに対する通知」は書き込みトランザクションがコミットされたときに呼ばれる単純な通知の仕組みです。「コレクションに対する通知」はより洗練された通知の仕組みで、追加・削除・変更といった変更内容を細かく受け取れます。

さらに、Professional EditionとEnterprise Editionにおいてはイベントハンドリング通知が利用できます。詳しくは”The Realm Mobile Platform”のドキュメントをご覧ください。

Realmに対する通知

Realmインスタンスは書き込みトランザクションがコミットされたときは常に通知を送信します。通知にコールバックを登録するには下記のようにします。

// Realmの変更通知を監視します。
realm.addListener('change', () => {
  // UIを更新します
  ...
});

// 変更通知の監視を解除します
realm.removeAllListeners();

コレクションに対する通知

コレクションに対する通知には細かい変更内容の情報が含まれています。変更内容の情報はインデックスの配列を用いて通知されます。インデックスの配列には、1つ前に通知を受けたときから、追加、削除、および変更されたオブジェクトのインデックスが格納されています。

コレクションに対する通知は、非同期で通知されます。初回の通知はクエリが実行完了された時点で呼ばれます。そのあとは、コレクションに格納されているオブジェクトに変更、削除、または追加が発生するトランザクションがコミットされるたびに通知されます。

通知に対するコールバックは2つの引数を受け取ります。1つ目は変更があったコレクションオブジェクト自身です。2つ目はchangesオブジェクトです。このオブジェクトは変更があったインデックスの情報をdeletionsinsertionsmodificationsという変数で保持しています。

最初の2つ、deletionsinsertions変数はコレクションに対してオブジェクトを追加・削除するたびに変更された部分のインデックスを記録しています。 コレクションがResults型の場合は、検索条件に関する値が変更されて、検索条件に新しくマッチするオブジェクトが増えたとき、あるいはマッチしなくなってオブジェクトが減ったときに変更として扱われます。List型の場合は、関連にオブジェクトが追加されたときと、関連からオブジェクトが削除されたときにも変更として扱われます。

modificationsはオブジェクトのプロパティが変更されたときにインデックスが含まれます。それは1対1や1対多の関連が変更された場合も当てはまります。ただし、逆方向の関連の変更の場合は含まれません。

class Dog {}
Dog.schema = {
  name: 'Dog',
  properties: {
    name:  'string',
    age: 'int',
  }
};
class Person {}
Person.schema = {
  name: 'Person',
  properties: {
    name:    {type: 'string'},
    dogs:    {type: 'list', objectType: 'Dog'},
  }
};

上記のようなモデルクラスが定義されているとき、dogsの所有者であるPersonオブジェクトの変更を監視するとします。その場合は、検索条件にマッチするPersonオブジェクトに下記の変更が加えられた場合に、通知が届きます。

  • Personオブジェクトのnameプロパティを変更したとき
  • Personオブジェクトのdogsプロパティに要素を追加または削除したとき
  • Personオブジェクトと関連づけがあるDogオブジェクトのageプロパティを変更したとき

このようなきめ細やかな通知の仕組みにより、変更があった際にただすべてを再読み込みするだけでなく、より分かりやすいアニメーションや表示を伴ったUIの更新を行うことができます。

// Observe Collection Notifications
realm.objects('Dog').filtered('age < 2').addListener((puppies, changes) => {

  // Update UI in response to inserted objects
  changes.insertions.forEach((index) => {
    let insertedDog = puppies[index];
    ...
  });

  // Update UI in response to modified objects
  changes.modifications.forEach((index) => {
    let modifiedDog = puppies[index];
    ...
  });

  // Update UI in response to deleted objects
  changes.deletions.forEach((index) => {
    // Deleted objects cannot be accessed directly
    // Support for accessing deleted objects coming soon...
    ...
  });


});

// Unregister all listeners
realm.removeAllListeners();

同期

Realm Mobile PlatformはRealm Mobile Databaseを拡張し、ネットワークを通じてデバイス間の自動的なデータ同期を実現します。同期されたRealmに対応するために、いくつかの新しい型とクラスが導入されました。下記以降でRealm Mobile Databaseに新しく同期のために追加されたクラスを説明します。

Userクラス

Realmユーザー(Realm.Sync.User)はRealm Object Serverの中心となるクラスです。Userクラスはユーザー名+パスワードによる認証に加え、SNSを使う方法などいくつかのサードパーティの認証方式をサポートしています。

ユーザーの作成、およびログインは次の2つの値が必要です。

  • 接続先のRealm ServerのURL
  • ユーザーを認証するための認証情報。認証情報は認証方式により変わります。(例: ユーザー名+パスワード、アクセストークンなど)

認証

ユーザーログインには認証が必要です。どのような認証プロバイダがサポートされているかについては、Realm Object Serverの認証についてのドキュメント をご覧ください。

ユーザー認証に使用するアカウント情報を表すオブジェクトは次に示すさまざまな方法で作成できます。

  • ユーザー名/パスワードの組み合わせ。
  • 組み込みのサードパーティ認証サービスによって認証されたアクセストークン。
  • カスタム認証プロバイダによって認証されたトークン(カスタム認証をご覧ください)。

ユーザー名とパスワードによる認証はRealm Object Server内に完全に閉じています。ユーザーに関するコントロールをすべて自分自身で行うことができます。その他の認証サービスを利用する場合は、アプリケーション内で外部のサービスに接続し、認証トークンを取得する必要があります。

下記にさまざまな認証プロバイダにおけるアカウント情報を表すオブジェクトの作成方法を示します。

ユーザー名/パスワード
Realm.Sync.User.login('http://my.realm-auth-server.com:9080', 'username', '[email protected]$w0rd', (error, user) => { /* ... */ });

上記のメソッドを用いてログインするためには、あらかじめユーザー登録が必要です。ユーザー登録はWebの管理者用のダッシュボードから、もしくはregisterメソッドを用います。

Realm.Sync.User.register('http://my.realm-auth-server.com:9080', 'username', '[email protected]$w0rd', (error, user) => { /* ... */ });
Google
const googleAccessToken = 'acc3ssT0ken...';
Realm.Sync.User.registerWithProvider('http://my.realm-auth-server.com:9080', 'google', googleAccessToken, (error, user) => { /* ... */ });
Facebook
const fbAccessToken = 'acc3ssT0ken...';
Realm.Sync.User.registerWithProvider('http://my.realm-auth-server.com:9080', 'facebook', fbAccessToken, (error, user) => { /* ... */ });
##### カスタム認証

```js
// The user token provided by your authentication server
const accessToken = 'acc3ssT0ken...';

const user = Realm.Sync.User.registerWithProvider(
  'http://my.realm-auth-server.com:9080',
  'fooauth',
  accessToken,
  (error, user) => { /* ... */ }
);

注意: JavaScript SDKでは現在は追加のログイン情報を送ることはサポートしていません。単一のアクセストークン以外に送りたいデータがある場合は、accessToken引数にJSONを渡し、受け取った側でデコードして使用してください。

ログアウト

同期されたRealmからログアウトするのは下記のメソッドを呼ぶだけです。

user.logout();

ログアウトすると同期は停止します。ログアウトしたユーザーは同期されたRealmファイルを開くことはできなくなります。

ユーザーオブジェクトを使用する

Realm Mobile Platformでは1つのアプリケーション内で同時に複数のユーザーを利用することができます。例えばメールのクライアントアプリでは複数のアカウントを切り替えて使用できます。そのような動作を実現するために、複数のユーザーを好きなときに同時に有効にできます。認証済みのユーザーオブジェクトをすべて取得するにはRealm.Sync.User.allプロパティを利用します。

Realm URLはチルダ(~)を含むことができ、各ユーザーのディレクトリを表します。Realm URL中のチルダは自動的にユーザーIDに置き換えられます。この仕組みは、ユーザーごとに別のRealm URLを指定する場合に非常に便利です。

同期されたRealmファイル保存される場所はフレームワークによって管理されています。保存場所は必要に応じて変更できます。

Realm.Sync.User.login(/* ... */, (error, user) => {
  if (!error) {
    var realm = new Realm({
      sync: {
        user: user,
        url: 'realm://object-server-url:9080/~/my-realm',
      },
      schema: [/* ... */]
    });

    realm.write(() => {
      /* ... */
    })
  }
})

Realm.Sync.User.currentを用いてログイン中のユーザーを取得できます。ログイン中のユーザーが存在しない、またはすべてのユーザーがログアウト済みである場合はこのプロパティはundefinedを返します。複数のユーザーがログイン中の場合は例外が発生します。

let user = Realm.Sync.User.current;

複数のユーザーがログインしている場合は、Realm.Sync.User.allプロパティを用いてログイン済みの全ユーザーのコレクションオブジェクトを取得できます。ログイン中のユーザーが存在しなければ、空のコレクションを返します。

let users = Realm.Sync.User.all;

for(const key in users) {
  const user = users[key];

  // do something with the user.
})

同期されたRealmを使用する

Userオブジェクトと接続先のRealm Object Serverを示すURLを使用して同期されたRealmを開いた後は、通常のRealmと同様に使用できます。

realm.write(() => {
  realm.create('MyObject', jsonData);
});

var objects = realm.object('MyObject');

同期セッション

同期されたRealmとRealm Object Serverとの接続はSessionオブジェクトとして表されます。 セッションオブジェクトはrealm.syncSessionメソッドを用いて取得します。

セッションの状態を知るにはstateプロパティを使います。このプロパティからセッションがアクティブ、サーバーと未接続、またはエラー状態のいずれかであることがわかります。

アクセスコントロール

Realm Mobile Platformでは、どのユーザーが同期されたRealmにアクセスできるのかを柔軟にコントロールできる仕組みがあります。 例えば、1つのRealmを複数のユーザーが共同で編集するようなアプリを作ることもできます。 また、あるユーザーのみが編集できるファイルを公開し、他のユーザーは読み取り専用で閲覧だけを可能にするといったことも可能です。

Realmのアクセス権限には3種類のレベルが存在します。

  • mayReadは読み取り権限が与えられていることを示します。
  • mayWriteは書き込み権限が与えられていることを示します。
  • mayManageはそのRealmに対するアクセス権限を変更できることを示します。

アクセス権限を明示的に与えなかった場合は、デフォルトの動作ではRealmファイルの作成者だけがそのファイルにアクセスできます。 唯一の例外はAdminユーザーです。Adminユーザーは常にすべてのRealmファイルに対してすべてのアクセス権限を持ちます。

書き込み権限のみという状態(例: mayReadをセットせずにmayWriteだけをセットした場合など)は現在サポートしていません。

アクセス権限の概念についてより詳しく知るには、Realm Object Serverドキュメントのアクセスコントロールセクションをご覧ください。

Management Realm

アクセス権限を変更はManagement Realmに変更内容を書き込むことで行います。 Management Realmは一般の同期されたRealmとまったく同じものです。しかし、Realm Object ServerはManagement Realmの変更を監視するように設計されています。 特定のRealmファイルに対するアクセス権限を変更するには、アクセス権変更オブジェクトをManagement Realmに追加します。すると、その変更がRealmファイルに対して適用されます。

特定のユーザーに対するManagement Realmオブジェクトを取得するには、user.openManagementRealm()メソッドを使用します。

const managementRealm = user.openManagementRealm();

アクセス権限を変更する

Realmファイルに対してアクセス権限を変更するにはアクセス権変更オブジェクトManagement Realmに書き込みます。現在は、下記に示すを2つの方法をサポートしています。

PermissionChange

PermissionChangeオブジェクトは同期されたRealmのアクセス権を直接変更することができます。アクセス権を付与したいユーザーのIDが分かっている場合や、全ユーザーにアクセス権を付与したい場合にはこちらの方法が適しています。

managementRealm.write(() => {
  managementRealm.create('PermissionChange', {
    id: generateUniqueId(),   // implement something that creates a unique id.
    createdAt: new Date(),
    updatedAt: new Date(),
    userId: '...',
    realmUrl: '...',
  });
});

特定のユーザーが作成したすべてのRealmファイルのアクセス権限を変更するには、realmURL引数に*を指定します。 すべてのユーザーに対してアクセス権限を変更するには、userID引数に*を指定します。

mayReadmayWritemayManageプロパティに値を指定しなかった場合は、もしくはnullを指定した場合はreadwritemanageの状態は変更されません。このことは、例えばあるユーザーにread権限を付与し、かつ、そのユーザーがもともとwrite権限を持っていた場合はそのままにしたい、というときなどに活用できます。

managementRealm.write(() => {
  for(const userId in users) {
    managementRealm.create('PermissionChange', {
      id: generateUniqueId(),
      createdAt: new Date(),
      updatedAt: new Date(),
      userId: userId,
      realmUrl: realmUrl,
      mayRead: true
    });
  });
});

上記のようにすると、usersに含まれるすべてのユーザーにread権限が付与されます。各ユーザーがもともとwrite権限もしくはmanage権限を持っていた場合はそのままになり、失われることはありません。

PermissionOffer/PermissionResponse

PermissionOfferPermissionOfferResponseはペアで使用し、あるユーザーがファイルの共有を別のユーザー(複数の場合もあります)に招待し、招待を受けたユーザーが共有を承認するというワークフローをサポートします。

managementRealm.write(() => {
  let expirationDate = new Date();
  expirationDate.setDate(expirationDate.getDate() + 7); // Expires in a week.

  managementRealm.create('PermissionOffer', {
    id: generateUniqueId(),
    createdAt: new Date(),
    updatedAt: new Date(),
    userId: userId,
    realmUrl: realmUrl,
    mayRead: true,
    mayWrite: true,
    mayManage: false,
    expiresAt: expirationDate
  });
});

/* Wait for the offer to be processed */

var token = permissionOffer.token;

/* Send token to the other user */

PermissionChangeオブジェクトと同じように、アクセス権はreadwritemanageの3種類をrealmUrlに対して指定できます。 expiresAtはトークンの有効期限を示します。expiresAtを指定しない、もしくはnullを渡した場合は、有効期限は無期限になります。有効期限とはトークンの使用期限であり、すでにトークンを使用したユーザーのアクセス権が無くなるわけではありません。

トークン(token)を受け取ったユーザーはトークンを使用して共有を開始できます。

const managementRealm = anotherUser.openManagementRealm();
let offerResponse;

managementRealm.write(() =>
{
    offerResponse = managementRealm.create('PermissionOfferResponse', {
      id: generateUniqueId(),
      createdAt: new Date(),
      token: token
    });
});

/* Wait for the offer to be processed */

const realmUrl = offerResponse.realmUrl;

// Now we can open the shared realm:

var realm = new Realm({
  sync: {
    user: anotherUser,
    url: realmUrl,
  },
  schema: [/* ... */]
});

PermissionOfferによるアクセス権限の変更は既存の状態に追加する形で行われます。つまりすでにwrite権限のあるユーザーがread権限を取得したとしてもwrite権限が失われることはありません。

共有はPermissionOfferオブジェクトをマネジメントRealmから削除することで中止できます。またはexpiresAtプロパティを過去に設定します。しかし新しくそのファイルを共有しようとするユーザーは受け付けなくなりますが、すでにアクセス権を獲得したユーザーのアクセス権が無くなるわけではありません。

Object Serverがパーミッションオブジェクトの操作を完了した際には、オブジェクトのstatusCodeプロパティとstatusMessageプロパティに結果を示す値がセットされます。

イベントハンドリング

This feature is limited to our Professional and Enterprise editions. Learn more about them and start a free trial.

Realm Object ServerのEnterprise EditionとProfessional Editionだけで使える機能としてイベントハンドリングがあります。サーバーサイドのNode.js SDKのグローバルイベントリスナーAPIによって提供され、すべてのRealmの変更を監視できます。すべてのRealmの変更、または特定のパターンに一致するRealmを監視できます。例えば、/~/settingsというRealmにユーザーごとの設定を保存するような設計になっているとすると、そのRealmを監視してユーザーの設定が変更されたときに特定のアクションを実行できます。

変更がサーバーに同期されたときはいつでも、通知が呼ばれ、任意のサーバーサイドの処理を実行できます。通知には変更されたRealmを示すパスと、オブジェクトのきめ細やかな変更内容が含まれます。変更内容を示すオブジェクトはクラスごとに追加・削除・変更されたオブジェクトのインデックスが含まれます。

イベントハンドラの作成

イベントハンドリングを使用するには小さなNode.jsアプリケーションを作成することになります。

ディレクトリを作成し、package.jsonという名前のファイルをそこに作成します。このJSONファイルには外部ライブラリなどの依存関係を記述し、Node.jsとnpmが参照します。

npm initコマンドを使うと、対話的なインターフェースでpackage.jsonを作成できます。下記のテンプレートを用いて、テキストエディタを使って編集しても構いません。

{
    "name": "MyApp",
    "version": "0.0.1",
    "main": "index.js",
    "author": "Your Name",
    "description": "My Cool Realm App",
    "dependencies": {
        "realm": "file:realm-1.0.0-professional.tgz"
    }
}

ダウンロードしたRealm Mobile Platformのアーカイブファイルをfile:属性の依存関係として指定します。アーカイブファイルはpackage.jsonと同じディレクトリに置いてください。(package.jsonに指定しているファイル名と、実際のファイル名は同じでなければなりません。)

他に依存関係が必要な場合はdependenciesセクションに記述します。

package.jsonファイルを正しく記述できたら、下記のコマンドを実行します。

npm install

必要なモジュールと依存関係がダウンロード、展開されます。

イベントハンドラはObject Serverに対して管理者権限のアクセスを必要とします。そのため、Object Serverの管理者トークンを取得する必要があります。Linux環境では管理者トークンは下記の場所にあります。

cat /etc/realm/admin_token.base64

macOS環境では管理者トークンはrealm-object-serverフォルダの下記の場所にあります。

cd path-to/realm-mobile-platform
cat realm-object-server/admin_token.base64

サンプルの index.jsファイルは次のようになります。この例では、/~/privateにあるユーザー固有のRealmファイルの変更を監視します。Realm内の変更された Couponオブジェクトを探し、未検証ならばそのクーポンコードを検証し、結果をCouponオブジェクトのisValidプロパティに書き込みます。

var Realm = require('realm');

// Insert the Realm admin token here
//   Linux:  cat /etc/realm/admin_token.base64
//   macOS:  cat realm-object-server/admin_token.base64
var ADMIN_TOKEN = 'ADMIN_TOKEN';

// the URL to the Realm Object Server
var SERVER_URL = 'realm://127.0.0.1:9080';

// The regular expression you provide restricts the observed Realm files to only the subset you
// are actually interested in. This is done in a separate step to avoid the cost
// of computing the fine-grained change set if it's not necessary.
var NOTIFIER_PATH = '/^\/([0-9a-f]+)\/private$/';

// The handleChange callback is called for every observed Realm file whenever it
// has changes. It is called with a change event which contains the path, the Realm,
// a version of the Realm from before the change, and indexes indication all objects
// which were added, deleted, or modified in this change
function handleChange(changeEvent) {
  // Extract the user ID from the virtual path, assuming that we're using
  // a filter which only subscribes us to updates of user-scoped Realms.
  var matches = changeEvent.path.match(/^\/([0-9a-f]+)\/private$/);
  var userId = matches[1];

  var realm = changeEvent.realm;
  var coupons = realm.objects('Coupon');
  var couponIndexes = changeEvent.changes.Coupon.insertions;

  for (var couponIndex in couponIndexes) {
    var coupon = coupons[couponIndex];
    if (coupon.isValid !== undefined) {
      var isValid = verifyCouponForUser(coupon, userId);
      // Attention: Writes here will trigger a subsequent notification.
      // Take care that this doesn't cause infinite changes!
      realm.write(function() {
        coupon.isValid = isValid;
      });
    }
  }
}

// create the admin user
var adminUser = Realm.Sync.User.adminUser(ADMIN_TOKEN);

// register the event handler callback
Realm.Sync.addListener(SERVER_URL, adminUser, NOTIFIER_PATH, 'change', handleChange);

console.log('Listening for Realm changes');

他のサービスと連携する

イベントハンドリングを用いた他のサービスとの連携の例としてはScanner App](/docs/tutorials/scanner/)のチュートリアルがありますので参考にしてください(IBM WatsonのBluemixと連携しています)。

データコネクタ

This feature is limited to our Enterprise editions. Learn more.

Realm Object ServerのEnterprise Editionには、Object Serverのすべての操作とデータにアクセスできる低レベルのNode.jsベースのアダプタAPIが用意されています。これは、同期されたRealmがPostgreSQLなどの既存のデータベースと連携するために使用します。Realmは外部データベースとリアルタイムに同期できます。クライアントアプリケーションは、Realm Mobile Database APIを使用して、リアルタイムのネイティブオブジェクトを扱うメリットを得ることができます。

アダプタAPIは、上記のイベントハンドラAPIと非常によく似た方法で作成されます。サーバファイルを配置するディレクトリを作成し、npmの依存関係を記述したpackage.jsonをエディタなどを用いて作成するか、npm initを使って対話的に作成します。そして小さなNode.jsアプリケーションを作成します。Realm Mobile Platform Enterprise Editionをfile属性の依存関係として指定する必要があります。アーカイブは package.jsonファイルと同じディレクトリにおく必要があります。(package.jsonで指定されたファイル名と実際のファイル名は同じでなければなりません。)

{
    "name": "MyApp",
    "version": "0.0.1",
    "main": "index.js",
    "author": "Your Name",
    "description": "My Cool Realm App",
    "dependencies": {
        "realm": "file:realm-1.0.0-enterprise.tgz"
    }
}

必要な依存関係を正しく記述できたら、npm installを実行します。必要なモジュールと依存関係がダウンロード、展開されます。

イベントハンドラAPIと同様に、Object Serverに対して管理者権限のアクセスを必要とします。そのため、Object Serverの管理者トークンを取得する必要があります。Linux環境では管理者トークンは下記の場所にあります。

cat /etc/realm/admin_token.base64

macOS環境では管理者トークンはrealm-object-serverフォルダの下記の場所にあります。

cd path-to/realm-mobile-platform
cat realm-object-server/admin_token.base64

アダプタAPIを使用するには、作成したNode.jsアプリケーションはトランスレータとして機能し、Object Serverから命令を受け取り、外部データベースのAPIを呼び出して読み書きします。サンプルアプリケーションは次のようになります。

var Realm = require('realm');

var adapterConfig = {
  // Insert the Realm admin token here
  //   Linux:  cat /etc/realm/admin_token.base64
  //   macOS:  cat realm-object-server/admin_token.base64
  admin_token: 'ADMIN_TOKEN',

  // the URL to the Realm Object Server
  server_url: 'realms://127.0.0.1:9080',

  // local path for the Adapter API file
  local_path: './adapter',

  // regular expression to limit which Realms will be observed
  realm_path_regex: '/^\/([0-9a-f]+)\/private$/'
};

class CustomAdapter {
  constructor(config) {
    this.adapter = new Realm.Sync.Adapter(
      config.local_path,
      config.server_url,
      Realm.Sync.User.adminUser(config.admin_token),
      config.realm_path_regex,

      // This callback is called any time a new transaction is available for
      // processing for the given path. The argument is the path to the Realm
      // for which changes are available. This will be called for all Realms
      // which match realm_path_regex.
      (realm_path) => {
        var current_instructions = this.adapter.current(realm_path);
        while (current_instructions) {
          // if defined, process the current array of instructions
          this.process_instructions(current_instructions);

          // call advance to progress to the next transaction
          this.adapter.advance(realm_path);
          current_instructions = this.adapter.current(realm_path);
        }
      }
    )
  }

  // This method is passed the list of instructions returned from
  // Adapter.current(path)
  process_instructions(instructions) {
    instructions.forEach((instruction) => {
        // perform an operation for each type of instruction
        switch (instruction.type) {
          case 'INSERT':
            insert_object(instruction.object_type, instruction.identity, instruction.values);
            break;
          case 'DELETE':
            delete_object(instruction.object_type, instruction.identity);
            break;
          // ... add handlers for all other relevant instruction types
          default:
            break;
        }
      })
  }
}

Adapter.currentによって返される命令オブジェクトはtypeプロパティを持ちます。type`プロパティは次に示すいずれかの文字列です。その他に、命令を処理するために必要なデータを保持している2つ以上のプロパティも合わせて設定されています。

  • INSERT: insert a new object
    • object_type: type of the object being inserted
    • identity: primary key value or row index for the object
    • values: map of property names and property values for the object to insert
  • SET: set property values for an existing object
    • object_type: type of the object
    • identity: primary key value or row index for the object
    • values: map of property names and property values to update for the object
  • DELETE: delete an exising object
    • object_type: type of the object
    • identity: primary key value or row index for the object
  • CLEAR: delete all objects of a given type
    • object_type: type of the object
  • LIST_SET: set the object at a given list index to an object
    • object_type: type of the object
    • identity: primary key for the object
    • property: property name for the list property to mutate
    • list_index: list index to set
    • object_identity: primary key or row number of the object being set
  • LIST_INSERT: insert an object in the list at the given index
    • object_type: type of the object
    • identity: primary key for the object
    • property: property name for the list property to mutate
    • list_index: list index at which to insert
    • object_identity: primary key or row number of the object to insert
  • LIST_ERASE: erase an object in the list at the given index: this removes the object
  • from the list but the object will still exist in the Realm
    • object_type: type of the object
    • identity: primary key for the object
    • property: property name for the list property to mutate
    • list_index: list index which should be erased
  • LIST_CLEAR: clear a list removing all objects: objects are not deleted from the Realm
    • object_type: type of the object
    • identity: primary key for the object
    • property: property name for the list property to clear
  • ADD_TYPE: add a new type
    • object_type: name of the type
    • primary_key: name of primary key property for this type
    • properties: Property map as described in Realm.ObjectSchema
  • ADD_PROPERTIES: add properties to an existing type
    • object_type: name of the type
    • properties: Property map as described in Realm.ObjectSchema
  • CHANGE_IDENTITY: change the row index for an existing object: not called for objects with primary keys
    • object_type: type fo the object
    • identity: old row value for the object
    • new_identity: new row value for the object

すべての命令タイプと渡されるデータの詳細については、Realm.Sync.AdapterクラスのAPIリファレンスを参照してください。

PostgreSQLのデータコネクタは既にRealmによって実装されており組み込みのデータコネクタとして提供されています。現在もMongoDBやMicrosoft SQL Serverを含む多くのデータベースに対応するための開発が行われています。どのデータコネクタも、アプリケーション固有のニーズに合わせてカスタマイズできます。Enterprise Editionを使用されている方は、担当者に詳細をお問い合わせください。

データアクセス

This feature is limited to our Professional and Enterprise editions. Learn more about them and start a free trial.

Realm Object ServerのEnterprise EditionとProfessional Editionでは、すべてのサーバーサイドに同期されているRealmにアクセス、変更できる機能が提供されています。この機能を利用するには、サーバーを起動したときに作成される管理者トークンが必要です。管理者トークンを用いると、すべてのRealmのデータにアクセスできます。

Linux環境では管理者トークンは下記の場所にあります。

cat /etc/realm/admin_token.base64

macOS環境では管理者トークンはrealm-object-serverフォルダの下記の場所にあります。

cat realm-object-server/admin_token.base64

管理者トークンを使ってRealm.Sync.Userオブジェクトを作成し、Realmコンストラクタに渡すことで、サーバーサイドのすべてのRealmを開くことができます。

// Open a Realm using the admin user
var adminToken = '3x4mpl3T0k3n…';
var adminUser = Realm.Sync.User.adminUser(adminToken);
var realm = new Realm({
  sync: {
    user: admin_user,
    url: 'realm://object-server-url:9080/my-realm',
  },
  schema: [{...}
  }]
});

管理者ユーザーを使用する際にはRealm URLに~(チルダ)を 含めない ように注意してください。~は通常のユーザー単位の認証を使用している場合のみユーザーIDに自動的に置換されます。管理者ユーザーはユーザーIDを持っていません。通常のユーザー単位の認証についてはUserクラスセクションをご覧ください。

React Native ListView

ListResultsのインスタンスをListViewのデータソースとして利用する場合は、realm/react-nativeモジュールにて提供されているListViewListView.DataSourceを利用することを強く推奨します。

import { ListView } from 'realm/react-native';

realm/react-nativeモジュールが提供するListViewのAPIは完全にReact.ListViewと同じです。使い方はReact.ListViewのドキュメントをご覧ください。

暗号化

Please take note of the Export Compliance section of our LICENSE, as it places restrictions against the usage of Realm if you are located in countries with an export restriction or embargo from the United States.

Realmでは64バイトの暗号化キーを用いてAES-256とSHA-2暗号化方式でデータベースファイルを暗号化する機能を提供しています。

var key = new Int8Array(64);  // pupulate with a secure key
var realm = new Realm({schema: [CarObject], encryptionKey: key});

// Use the Realm as normal
var dogs = realm.objects('Car');

この機能を使用すると、ディスクに保存されるデータが透過的にAES-256で必要に応じて暗号/複合化され、SHA-2 HMACによって検証されます。

暗号化したRealmファイルのインスタンスを作成するには同じ暗号化キーが必要になります。

暗号化したRealmを使う場合、わずかに(10%未満)パフォーマンスが下がります。

トラブルシューティング

Realmコンストラクタが見つからない

Realmコンストラクタが見つからないというエラーが原因でアプリがクラッシュする場合は、次の手順で解決します。

まず、react-native link realmを実行してください。

問題が解決しない場合でAndroidアプリを開発しているなら、さらに下記を実行してください。

MainApplication.javaファイルに次のように記述します。 java import io.realm.react.RealmReactPackage;

そして、RealmReactPackageをパッケージのリストに追加します。

protected List getPackages() {
    return Arrays.asList(
        new MainReactPackage(),
        new RealmReactPackage() // add this line
    );
}

次の2行をsettings.gradleに追加します。

include ':realm'
project(':realm').projectDir = new File(settingsDir, '../node_modules/realm/android')

問題が解決しない場合でiOSアプリを開発しているなら、下記を実行してください。 1. すべてのシミュレータを終了しデバイスの接続を切る。 2. ターミナルからパッケージマネージャの実行を停止する。(またはターミナルを再起動する) 3. アプリケーションのディレクトリにあるiosフォルダをFinderから開く。 4. buildフォルダに移動する(注意: Atomエディタにbuildフォルダが表示されない場合は、右クリックメニューの「Finderで開く」を選択してください。) 5. buildフォルダの内容をすべて削除する。(ゴミ箱に移動する) 6. react-native run-iosを実行し際ビルドする。

クラッシュレポート

私たちは開発者の方にクラッシュレポーターを使用していただくことを推奨しています。Realmの操作のうちの多くは(ディスクI/Oを伴う操作などと同様に)実行時に失敗する可能性があります。そのため、クラッシュレポートを収集することは、何が原因で問題が発生したのかを特定し、エラー処理の改善や不具合の修正に有効です。

多くの商用のクラッシュレポーターはログを収集するオプションを提供しています。この機能を有効にすることを強く推奨します。Realm例外が発生した時や、致命的な状態に陥った際にはメタデータの情報をログに出力しており(そこにユーザーデータは一切含まれていません)、それらのログは我々が問題を調査する際に役に立ちます。

Realmの問題や不具合を報告するには

Realmについて問題を発見した際は、GitHubで問題を報告するか、help@realm.ioにEメール、またはSlackで報告してください。その際には、こちらで問題を再現できるように、できるだけ多くの情報をあわせて教えてください。

下記に示す情報は問題を解決するために非常に役に立ちます。

  1. あなたが実際にやりたいこと・目的。
  2. 期待している結果。
  3. 実際に発生した結果。
  4. 問題の再現方法。
  5. 問題を再現、または理解できるサンプルコード (そのままビルドして実行できるプロジェクトだと理想的です)
  6. Realmのバージョン
  7. クラッシュログやスタックトレース。上述のクラッシュレポートの項目も参考にしてください。