This is not the current version. View the latest documentation
イントロダクション
Realm JavaScriptはアプリケーションのモデル層を効率的に安全で迅速な方法で記述することができます。React NativeとNode.jsで動作します。
下記はRealmを利用するコードの簡単なサンプルです。
const Realm = require('realm');
// モデルとモデルのプロパティを定義します
// Define your models and their properties
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可)のプロパティ
}
};
Realm.open({schema: [CarSchema, PersonSchema]})
.then(realm => {
// Realmオブジェクトを作成してローカルDBに保存します
realm.write(() => {
const myCar = realm.create('Car', {
make: 'Honda',
model: 'Civic',
miles: 1000,
});
myCar.miles += 20; // 保存済みの値を更新することもできます
});
// 'miles > 1000'に該当するCarオブジェクトを検索します
const cars = realm.objects('Car').filtered('miles > 1000');
// 上記の条件に該当するCarオブジェクトは1件です
cars.length // => 1
// もう一つ別のCarオブジェクトを保存します
realm.write(() => {
const myCar = realm.create('Car', {
make: 'Ford',
model: 'Focus',
miles: 2000,
});
});
// 検索結果は自動的に最新の状態が反映されます
cars.length // => 2
});
Realmをサーバーサイド(Node.js)で利用する場合は、Realm Object Serverのドキュメントも合わせてご覧ください。
はじめに
インストール
Realm JavaScriptはnpmを利用してインストールすることができます。ソースコードはGitHubにあります。
Prerequisites
- React Nativeアプリケーションの開発環境が整っている必要があります。React Nativeの開発環境のセットアップについては公式サイトの説明をご覧ください。
- 実行環境としてiOS、およびAndroidをサポートしています。
- React Native 0.31.0以降をサポートしています。 ```
Installation
-
新しくReact Nativeプロジェクトを作成します。
react-native init <project-name>
-
作成したプロジェクトのディレクトリに移動します(
cd <project-name>
)。さらにrealm
を依存関係として追加します。npm install --save realm
-
次に、
react-native
を使ってプロジェクトとrealm
ネイティブモジュールをリンクします。react-native link realm
Android環境で利用する場合の注意: バージョンによっては、rnpm
が正しくない設定を生成することがあります。Gradleを正しく更新している(android/settings.gradle
、 android/app/build.gradle
)にもかかわらず、Realmモジュールの追加に失敗することがあります。その場合は、まずreact-native link
コマンドでRealmモジュールが正しく追加されるかどうかを確認し、失敗する場合は、下記の手順を用いて手作業にてライブラリをリンクしてください。
-
android/settings.gradle
ファイルに下記の2行を追加します。include ':realm' project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
-
android/app/build.gradle
のdependencies
に下記のようにcompile
の指定を追加します。dependencies { compile project(':realm') }
-
MainApplication.java
にimport
文とパッケージをリンクする指定を追加します。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 {
constructor(props) {
super(props);
this.state = { realm: null };
}
componentWillMount() {
Realm.open({
schema: [{name: 'Dog', properties: {name: 'string'}}]
}).then(realm => {
realm.write(() => {
realm.create('Dog', {name: 'Rex'});
});
this.setState({ realm });
});
}
render() {
const info = this.state.realm
? 'Number of dogs in this Realm: ' + this.state.realm.objects('Dog').length
: 'Loading...';
return (
<View style={styles.container}>
<Text style={styles.welcome}>
{info}
</Text>
</View>
);
}
}
デバイスまたはシミュレータを使って実行できます。
ここで説明している内容はRealm Node.js SDKのDeveloper Editionのインストール方法です。Professional EditionまたはEnterprise Editionを利用する場合は、メールに記述されているインストール方法をご覧ください。
Realm Node.jsのインストールはNode Package Managerを利用します。
npm install --save realm
インストールが完了すれば、アプリケーション内でrequire('realm')
と記述するだけで利用できます。
var Realm = require('realm');
サンプルコード
realm-jsのGitHubリポジトリにてサンプルコードが公開されています。
Android環境で実行する場合は、NDKのインストールとANDROID_NDK環境変数を設定する必要があります。
export ANDROID_NDK=/usr/local/Cellar/android-ndk/r10e
ヘルプ
- 使い方に困ったときは、StackOverflowで#realmタグを付けて質問してください。私たちは毎日StackOverflowをチェックしています。
- さらに複雑な問題に対する質問は、こちらの Slackチャットにて聞いてください。(質問は日本語で構いません)
- 問題を発見した場合はGitHubのIssuesに報告してください。できる限り、ご使用のRealmのバージョン、エラーメッセージやログ、スアックトレースやRealmのデータファイル、問題を再現可能なプロジェクトなどを添えてください。
- 機能のリクエストもGitHubのIssuesで教えてください。どのような機能が欲しいのか、また、どうしてその機能が必要なのかできるだけ具体的に教えてください。
もしクラッシュレポートツール(CrashlyticsやHockeyAppなど)を利用しているなら、ログコレクターを有効にしてください。Realmのログにはユーザーデータ以外のデバッグに有用なメタデータを含んでいます。それは私たちが問題を調査するときに非常に役に立ちます。
モデル
Realmのデータモデルは、スキーマの情報をRealmインスタンスの初期化時に渡すことによって定義されます。スキーマの情報とはモデルオブジェクトの名前、およびプロパティの名前や型などを示す一連の属性です。型は基本的な型に加えて、1対1の関連を示すobjectType
や1対多の関連を示すリスト型が指定できます。またoptional
(NULL可)やdefault
(デフォルト値)についてもここで指定します。
const 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}, // optional property
}
};
// Initialize a Realm with Car and Person models
Realm.open({schema: [CarSchema, PersonSchema]})
.then(realm => {
// ... use the realm instance to read and modify data
})
クラス
現時点では、クラスを用いたモデル定義はReact Nativeのみ利用できます。Node環境では利用できません。
ES2015から導入されたクラス構文やクラス継承を利用してモデルを定義するには、コンストラクタでスキーマを定義します。
class Person {
get fullName() {
return this.firstName + ' ' + this.lastName;
}
}
Person.schema = {
name: 'Person',
properties: {
firstName: {type: 'string'},
lastName: {type: 'string'}
}
};
定義したクラスはRealmファイルを開く際のコンフィギュレーションオブジェクトのschema
プロパティにそのまま渡せます。
Realm.open({schema: [Person]})
.then( /* ... */ );
下記のようにしてクラスのプロパティにアクセスできます。
realm.write(() => {
const john = realm.create('Person', {
firstName: 'John',
lastName: 'Smith'
});
john.lastName = 'Peterson';
console.log(john.fullName); // -> 'John Peterson'
});
対応しているデータ型
Realmでは、次に示すデータ型を基本的なデータ型としてサポートしています: bool
、int
、float
、double
、string
、data
およびdate
。
bool
型のプロパティは、JavaScriptにおけるBoolean
型にマッピングされます。int
、float
および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の関連
1対1の関連として、オブジェクト型のプロパティを指定するには、型にスキーマに定義した別のオブジェクトの名前(name
)を指定します。
const PersonSchema = {
name: 'Person',
properties: {
// 下記のtypeプロパティの定義はどちらも同じ意味です
car: {type: 'Car'},
van: 'Car',
}
};
オブジェクト型のプロパティを使用する場合は、Realmを初期化する際に、指定したすべてのオブジェクト型がスキーマに定義されている必要があります。
// PersonSchema内で'Car'型のプロパティとしてCarSchemaが使われているので、CarSchemaも渡す必要があります。
Realm.open({schema: [CarSchema, PersonSchema]})
.then(/* ... */);
オブジェクト型のプロパティにアクセスする場合は、標準の文法を用いて、ネストしたプロパティとしてアクセスします。
realm.write(() => {
const nameString = person.car.name;
person.car.miles = 1100;
// JSON記法を用いて新しくCar型のプロパティを作成して代入します。
person.van = {make: 'Ford', model: 'Transit'};
// 同じCar型のプロパティであるcarとvanに同じインスタンスを設定します。
person.car = person.van;
});
####1対多の関連
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; // 添字を使って各要素にアクセスします
逆方向の関連
Realmの関連は一方通行です。Dog
への1対多の関連であるPerson.dogs
のプロパティと、Person
への1対1の関連であるDog.owner
の2つのプロパティは、それぞれ互いに独立して動作します。Person.dogs
プロパティにDog
オブジェクトを追加しても、Dog.owner
プロパティにPerson
のオブジェクトは自動的に追加されたりはしません。手作業でこの2つの関連の整合性を保とうとすることは、間違いを起こしやすく、複雑で冗長さを招くので、Realmでは下記に示すような、逆方向の関連(バックリンクとも呼ばれます)を表現するためのプロパティを使用することができます。
Linkng Object型のプロパティを使用することで、関連元のオブジェクトを特定のプロパティを使って取得することができます。例えば、Dog
クラスにowners
という自分自身を関連として保持しているPerson
オブジェクトをすべて取得するというプロパティを持たせることができます。その方法はowners
プロパティをlinkingObjects
型として定義し、owners
プロパティはPerson
クラスの関連であると指定するだけです。
const PersonSchema = {
name: 'Person',
properties: {
dogs: {type: 'list', objectType: 'Dog'},
}
}
const DogSchema = {
name:'Dog',
properties: {
owners: {type: 'linkingObjects', objectType: 'Person', property: 'dogs'}
}
}
linkingObjects
プロパティは、List
プロパティ(1対多の関連)または Object
プロパティ(1対1の関連)のいずれかを指定できます。
const ShipSchema = {
name: 'Ship',
properties: {
captain: 'Captain'
}
}
const CaptainSchema = {
name: 'Captain',
properties: {
ships: {type: 'linkingObjects', objectType: 'Ship', property: 'captain'}
}
}
linkingObjects
プロパティにアクセスするとResults
オブジェクトが返ります。Results
オブジェクトなのでさらにクエリやソートを連鎖する操作が利用できます。 linkingObject
プロパティは取得元のオブジェクトに属していて、直接代入したり変更はできません。トランザクションがコミットされると自動的に更新されます。
スキーマを持たない状態でlinkingObjects
にアクセスするには: スキーマを指定せずにRealmファイルを開いた場合(例: Realm Functionsのコールバックなど)は、Object
インスタンスに対してlinkingObjects(objectType、property)
をメソッドを呼び出すことによって inkingObjects
プロパティを取得できます。
let captain = realm.objectForPrimaryKey('Captain', 1);
let ships = captain.linkingObjects('Ship', 'captain');
### オプショナル(NULL可)プロパティ
各プロパティは`optional`属性を用いて、オプショナル(NULL可)、または非オプショナル(NULL不可)として定義できます。
```js
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
に設定すると、そのプロパティにはインデックスが付加されます。 int
、string
、および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へのオブジェクトの追加、変更、削除は、write()
メソッドを用いてトランザクション内で行う必要があります。 トランザクションのオーバーヘッドは大きいのでコード中のトランザクションは、できる限り少なくなるように設計してください。
オブジェクトの生成
オブジェクトの生成にはcreate
メソッドを使用します。
try {
realm.write(() => {
realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'});
});
} catch (e) {
console.log("Error on creation");
}
write()
メソッド内で例外が発生した場合は、トランザクションは自動的にキャンセルされます。try/catch
ブロックはすべてのコード例に出てきませんが、try/catch
ブロックを使うことは良いプラクティスとして推奨しています。
ネストしたオブジェクト
オブジェクト型のプロパティを持つオブジェクトは、各オブジェクトのプロパティの値をJSONを用いて再帰的に子のプロパティも含めて一度に生成できます。
realm.write(() => {
realm.create('Person', {
name: 'Joe',
// nested objects are created recursively
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
オブジェクトの内容を変更することはできません。
オブジェクトを検索するもっとも基本的なメソッドは、Realm
のobjects
メソッドです。引数で与えられた型のオブジェクトをすべて取得します。
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
、ENDSWITH、
CONTAINSは文字列に対して使用できます。文字列の比較では、
==[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
クラスのopen
スタティックメソッドを呼び出すだけです。パラメータには設定オブジェクトを渡します。これまでに登場したコード例では、設定オブジェクトにschema
キーを渡しています。
// Get the default Realm with support for our objects
Realm.open({schema: [Car, Person]})
.then(realm => {
// ...use the realm instance here
})
.catch(error => {
// Handle the error here if something went wrong
});
設定オブジェクトに関する完全は詳細については、APIリファレンスをご覧ください。下記にschema
以外のよく使われるキーについて示します。
path
: Realmファイルのパスを示します。詳しくはデフォルト以外のRealmをご覧ください。migration
: マイグレーション処理の関数ですsync
: Realm Object Serverに接続し、同期されたRealmを開くための同期情報を示すオブジェクトです
デフォルトRealm
これまでのコード例を見てすでにお気づきだと思いますが、path
引数は指定しなくても構いません。その場合はデフォルトのRealmパスが自動的に使用されます。グローバルプロパティのRealm.defaultPath
を設定するとデフォルトのパスを変更できます。
デフォルト以外のRealm
複数のRealmを使い分ける
複数のRealmファイルを別の場所に保存して使い分けることができると便利です。例えば、事前に用意した組み込み済みデータを、メインのデータファイルとは別に読み込み専用のRealmとして利用するなどです。
Realmオブジェクトの初期化時にpath
を指定することで、Realmファイルの保存場所を指定できます。path
の指定はアプリケーションのドキュメントディレクトリからの相対パスになります。
// Realmをデフォルト値とは別の保存先を指定して取得します
let realmAtAnotherPath = new Realm({
path: 'anotherRealm.realm',
schema: [CarSchema]
});
スキーマバージョン
Realmの初期化時に指定できるもう1つのプロパティはschemaVersion
です。指定しなかった場合は、デフォルトの値として0
が使われます。既存のデータからスキーマを変更した場合は、必ずschemaVersion
を初期化時に指定しなければなりません。もしスキーマが変更されているにもかかわらず、schemaVersion
を指定しなかった場合は例外が発生します。
const PersonSchema = {
name: 'Person',
properties: {
name: 'string'
}
};
// schemaVersionのデフォルトは0です
Realm.open({schema: [PersonSchema]});
Realmを開いた後は、たとえば次のように使用します。
const UpdatedPersonSchema = {
// スキーマの`name`プロパティが同じなので`Person`オブジェクトのスキーマが
// 更新されたことになります
name: 'Person',
properties: {
name: 'string',
dog: 'Dog' // 新しいプロパティを追加
}
};
// 下記の記述はスキーマが更新されているにもかかわらず、
// スキーマバージョンを指定していないので例外が発生します。
Realm.open({schema: [UpdatedPersonSchema]});
// 下記の記述ではスキーマの更新が成功し、新しいスキーマのRealmを取得します
Realm.open({schema: [UpdatedPersonSchema], schemaVersion: 1});
Realmを開く前に、スキーマバージョンがいくつであるかを知るには、下記のようにRealm.schemaVersion
メソッドを使用します。
const currentVersion = Realm.schemaVersion(Realm.defaultPath);
同期的にRealmを開く Opening Realms
Realmのコンストラクタを使ってRealmインスタンスを生成することもできます。設定オブジェクトはコンストラクタのパラメータとして渡します。しかし、この方法は処理をブロックしてしまうので、通常は推奨いたしません。特に[マイグレーション(#migrations)]が必要な場合や、Realm Mobile Platformを使って同期されたRealmを利用する場合など、Realmファイルを開くときに時間がかかってしまうことがあります。同期されたRealmのダウンロードが終わるまで次の処理ができないということにならないように、open
やopenAsync
を利用することをおすすめします。
それでも同期的に開きたい場合は、下記のようにします。
const realm = new Realm({schema: [PersonSchema]});
// You can now access the realm instance.
realm.write(/* ... */);
読み取り権限しか与えられていない同期されたRealmを開く場合は、非同期メソッド(open
やopenAsync
)を 必ず 使用しなければなりません。読み取り権限しか持たないRealmを同期的に開こうとするとエラーが発生します。
マイグレーション
データベースを使ってる場合、時間が経つにつれ、データモデルは変更されていくものです。例えば、以下のPerson
モデルについて考えてみてください。
const PersonSchema = {
name: 'Person',
properties: {
firstName: 'string',
lastName: 'string',
age: 'int'
}
}
ここで、firstName
とlastName
を一つにして、name
プロパティが必要になったとします。 そこで以下のような単純な変更をインターフェースに加えることにします。
const PersonSchema = {
name: 'Person',
properties: {
name: 'string',
age: 'int'
}
}
ここでのポイントは、もし以前に前のデータモデルでのデータが保存されている場合、新しく定義し直したデータモデルとディスクに保存されている古いデータモデルとの間に不整合が生じてしまいます。 マイグレーションを実行せずに新しいスキーマを用いてRealmを使おうとすると、例外が発生します。
マイグレーションを実行する
スキーマバージョンとマイグレーション処理(migration
function)(データを移行しなくてよいなら不要です)を更新してマイグレーション処理を定義します。 マイグレーション処理では古いデータ構造から新しいデータ構造に変換するための処理を記述します。 マイグレーション処理を記述すると、より新しいスキーマバージョンを指定してRealmを開こうとした際に適用されます。
schemaVersion
だけを更新して、マイグレーション処理を記述しなかった場合は、オートマイグレーションによりデータベースに自動的に新しいプロパティが追加され、古いプロパティは自動的に削除されます。 スキーマを更新する際に、古いプロパティを更新したり、新しいプロパティにデータを移行する必要がある場合は、マイグレーション処理を書く必要があります。
たとえば、上記のPersonクラスのマイグレーションについて考えてみましょう。 新しく追加したname
プロパティには、古いfirstName
とlastName
プロパティを結合した値を設定するとします。w
Realm.open({
schema: [PersonSchema],
schemaVersion: 1,
migration: (oldRealm, newRealm) => {
// only apply this change if upgrading to schemaVersion 1
if (oldRealm.schemaVersion < 1) {
const oldObjects = oldRealm.objects('Person');
const newObjects = newRealm.objects('Person');
// loop through all objects and set the name property in the new schema
for (let i = 0; i < oldObjects.length; i++) {
newObjects[i].name = oldObjects[i].firstName + ' ' + oldObjects[i].lastName;
}
}
}
}).then(realm => {
const fullName = realm.objects('Person')[0].name;
});
一度マイグレーション処理が適用されると、その後は通常どおりにRealmとRealmオブジェクトが使用できます。
複数世代のマイグレーション
上記で示したマイグレーションのパターンでは、複数世代にわたるマイグレーションを実行した際に問題が起こることがあります。それはユーザーがプロパティのマイグレーションが必要なアプリのアップデートをスキップした場合に起こります。その場合、古いマイグレーションのコードを修正する必要に迫られるかもしれません。
この問題は、すべてのマイグレーションを古いものから順番に実行していくことで避けることができます。そうすることで、確実にすべてのマイグレーションが実行されて最新のデータ構造にアップデートされます。下記に示すパターンに従うことで、1度書いたマイグレーションのコードは2度と触る必要はありません。ただ、古いスキーマとマイグレーションのコードを将来にわたってもずっと保持し続ける必要があります。
下記の例を参考にしてください。
const 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
let nextSchemaIndex = Realm.schemaVersion(Realm.defaultPath);
while (nextSchemaIndex < schemas.length) {
const migratedRealm = new Realm(schemas[nextSchemaIndex++]);
migratedRealm.close();
}
// open the Realm with the latest schema
Realm.open(schemas[schemas.length-1]);
通知
Realm
、Results
、List
型のオブジェクトではaddListener
メソッドを使って通知のコールバックを登録できます。オブジェクトに変更があったときは毎回この変更通知コールバックが呼ばれます。
通知は「Realmに対する通知」と「コレクションに対する通知」の2種類があります。「Realmに対する通知」は書き込みトランザクションがコミットされたときに呼ばれる単純な通知の仕組みです。「コレクションに対する通知」はより洗練された通知の仕組みで、追加・削除・変更といった変更内容を細かく受け取れます。
さらに、Professional EditionとEnterprise Editionにおいてはイベントハンドリング通知が利用できます。詳しくは”The Realm Mobile Platform”のドキュメントをご覧ください。
Realmに対する通知
Realmインスタンスは書き込みトランザクションがコミットされたときは常に通知を送信します。通知にコールバックを登録するには下記のようにします。
function updateUI() {
// ...
}
// Realmの変更通知を監視します。
realm.addListener('change', updateUI);
// ..不要になったら変更通知の監視を解除します
realm.removeListener('change', updateUI);
// ..または、このメソッドですべての通知を解除できます
realm.removeAllListeners();
コレクションに対する通知
コレクションに対する通知には細かい変更内容の情報が含まれています。変更内容の情報はインデックスの配列を用いて通知されます。インデックスの配列には、1つ前に通知を受けたときから、追加、削除、および変更されたオブジェクトのインデックスが格納されています。
コレクションに対する通知は、非同期で通知されます。初回の通知はクエリが実行完了された時点で呼ばれます。そのあとは、コレクションに格納されているオブジェクトに変更、削除、または追加が発生するトランザクションがコミットされるたびに通知されます。
通知に対するコールバックは2つの引数を受け取ります。1つ目は変更があったコレクションオブジェクト自身です。2つ目はchanges
オブジェクトです。このオブジェクトは変更があったインデックスの情報をdeletions
、insertions
、modifications
という変数で保持しています。
最初の2つ、deletionsとinsertions変数はコレクションに対してオブジェクトを追加・削除するたびに変更された部分のインデックスを記録しています。 コレクションが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();
同期
Node.js版Realm(Linux)で同期機能を利用するにはProfessional Edition以上が必要です。機能を有効にするにはライセンスが必要です。
Node.js版をLinuxサーバーで動かす場合は、同期機能を有効にするために、下記のようにアクセストークンをセットしてください。
const token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...";
// Professional EditionのAPIをアンロック
Realm.Sync.setAccessToken(token);
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', 'p@s$w0rd', (error, user) => { /* ... */ });
上記のメソッドを用いてログインするためには、あらかじめユーザー登録が必要です。ユーザー登録はWebの管理者用のダッシュボードから、もしくはregister
メソッドを用います。
Realm.Sync.User.register('http://my.realm-auth-server.com:9080', 'username', 'p@s$w0rd', (error, user) => { /* ... */ });
const googleAccessToken = 'acc3ssT0ken...';
Realm.Sync.User.registerWithProvider('http://my.realm-auth-server.com:9080', 'google', googleAccessToken, (error, user) => { /* ... */ });
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',
'custom/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) {
Realm.open({
sync: {
user: user,
url: 'realm://object-server-url:9080/~/my-realm',
},
schema: [/* ... */]
}).then(realm => {
/* ... */
});
}
});
Realm.Sync.User.current
を用いてログイン中のユーザーを取得できます。ログイン中のユーザーが存在しない、またはすべてのユーザーがログアウト済みである場合はこのプロパティはundefined
を返します。複数のユーザーがログイン中の場合は例外が発生します。
const 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.objects('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ドキュメントのアクセスコントロールセクションをご覧ください。
アクセス権限を取得する
各ユーザーに付与されたアクセス権限をすべて取得するにはUser.getGrantedPermissions
メソッドを使用します。
const permissions = user.getGrantedPermissions("currentUser");
// Permissions is a regular query
const writePermissions = permissions.filtered("mayWrite = true");
// Queries are live and emit notifications
writePermissions.addListener((collection, changes) => {
// handle permission changes
});
特定のユーザーによって与えられたアクセス権限を取得するにはotherUser
パラメータを渡します。
アクセス権限を変更する
Realmファイルのアクセス権限を変更するには、直接アクセス権限を変更する方法と、リクエストベースの2つの方法があります。
アクセス権限の付与
アクセス権限の変更(付与および取り消し)はUser.applyPermissions
メソッドを使用して、Realmファイルに対するアクセス権限を直接変更できます。
const condition = { userId: 'some-user-id' };
const realmUrl = "realm://my-server.com/~/myRealm";
user.applyPermissions(condition, realmUrl, 'read');
condition
引数は次に示すいずれかのオブジェクトを含んでいなければなりません。
userId
- ユーザーIDを用いてアクセス権を付与するユーザーを指定する場合に使用します。(Realmが内部的に生成したIDです)metadataKey
とmetadataValue
-'email'
キーとEメールの値を指定します。ユーザー名/パスワードの認証プロバイダを使用している場合にEメールアドレスを用いてアクセス権を付与するユーザーを特定できます。
最後の引数によってユーザーに付与されるアクセス権のレベルを指定します。より高いレベルの権限は低いレベルの権限を含みます。たとえば、write
はread
を含んでいます。admin
はread
とwrite
権限を含みます。none
を指定するとユーザーからRealmに対するアクセス権限を取り消します。
Offer/Response
offerPermissionsAsync
メソッドから返されるトークンを用いて、Realmファイルを共有する仕組みです。
const realmUrl = 'realm://my-server.com/~/myRealm';
const oneDay = 1000 * 60 * 60 * 24;
const expiration = new Date(Date.now() + 7 * oneDay);
userA.offerPermissions(realmUrl, 'write', expiration)
.then(token => { /* ... */ });
必要に応じてexpiresAt
引数を使って、リクエストの期限を設定できます。期限の切れたトークンではRealmのアクセス権を変更することはできません。すでにトークンを使用して獲得した権限については、期限が過ぎたとしても失われることはありません。アクセス権を取り消す場合はapplyPermissionsAsync
を使用します。
受け取ったトークンはメッセージアプリで送ったり、QRコードとして表示したりして、別のユーザーに共有します。別のユーザーは、受け取ったトークンを使用してパーミッションのリクエストを了承します。
const token = "...";
userB.acceptPermissionOffer(token)
.then(realmUrl => Realm.open({ schema: [/* ... */], sync: { user: userB, url: realmUrl }}))
.then(realm => {
// ..use the realm
});
暗号化
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
Realm.open({schema: [CarObject], encryptionKey: key})
.then(realm => {
// 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
ファイルに次のように記述します。
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アプリを開発しているなら、下記を実行してください。
- すべてのシミュレータを終了しデバイスの接続を切る。
- ターミナルからパッケージマネージャの実行を停止する。(またはターミナルを再起動する)
- アプリケーションのディレクトリにあるiosフォルダをFinderから開く。
- buildフォルダに移動する(注意: Atomエディタにbuildフォルダが表示されない場合は、右クリックメニューの「Finderで開く」を選択してください。)
- buildフォルダの内容をすべて削除する。(ゴミ箱に移動する)
react-native run-ios
を実行し際ビルドする。
Chromeを使ったデバッグが遅い
この問題は既知の問題です。理由はRealmがC++で書かれていて、ネイティブコードが実行されるからです。そのためRealmはデバイスまたはシミュレータ上で実行する必要があります。しかし、Realmのゼロコピーアーキテクチャのため、RealmオブジェクトをデバッグするたびにRPCを通じて値を送る必要があるためです。
この問題を解決するためのさまざまな方法を調査しています。進捗につきましては、こちらのGitHub issueをチェックしてください。
クラッシュレポート
私たちは開発者の方にクラッシュレポーターを使用していただくことを推奨しています。Realmの操作のうちの多くは(ディスクI/Oを伴う操作などと同様に)実行時に失敗する可能性があります。そのため、クラッシュレポートを収集することは、何が原因で問題が発生したのかを特定し、エラー処理の改善や不具合の修正に有効です。
多くの商用のクラッシュレポーターはログを収集するオプションを提供しています。この機能を有効にすることを強く推奨します。Realm例外が発生した時や、致命的な状態に陥った際にはメタデータの情報をログに出力しており(そこにユーザーデータは一切含まれていません)、それらのログは我々が問題を調査する際に役に立ちます。
Realmの問題や不具合を報告するには
Realmについて問題を発見した際は、GitHubで問題を報告するか、help@realm.ioにEメール、またはSlackで報告してください。その際には、こちらで問題を再現できるように、できるだけ多くの情報をあわせて教えてください。
下記に示す情報は問題を解決するために非常に役に立ちます。
- あなたが実際にやりたいこと・目的。
- 期待している結果。
- 実際に発生した結果。
- 問題の再現方法。
- 問題を再現、または理解できるサンプルコード (そのままビルドして実行できるプロジェクトだと理想的です) 。
- Realmのバージョン
- クラッシュログやスタックトレース。上述のクラッシュレポートの項目も参考にしてください。