Realmって何?って思った方はまずは下記の記事を読んで下さい。簡単に言えばCoreDataやSQLiteに変わる次世代モバイルデータベースです。
- Realm公式ドキュメント(日本語)
http://realm.io/jp/docs/cocoa/0.87.1/ - [Qiita] 次世代mobile版データベース"Realm"を使ってみた
http://qiita.com/moriyaman/items/1a2916f4c2b79e934370 - [Qiita] Realmを使ってデータ管理【Swift編】-その1-
http://qiita.com/jtemplej/items/1c1b7204341ac01e561e - [Qiita] CoreDataはもう古い?新しいモバイルデバイス向けデータベース「Realm」を使ってみた
http://qiita.com/caesar_cat/items/632d1e85a67f2c65a473
概要
図はCoreDataで作ったものですが、今回はこの構成をRealmで実装します。
よくある構成で、記事(article)、サムネイル(thumbnail)、コメント(comment)、カテゴリ(category)、のそれぞれを次の3種類のリレーションシップで実装します。
-
One-to-One
記事とサムネイルの関係。記事のアイキャッチ的な画像なので1記事に対して0以上1以下存在します。記事が消えるとサムネイルのレコードも削除される必要があります。 -
One-to-Many
記事とコメントの関係。コメントは1記事に対して複数存在します。記事とコメントは親子関係となるため、記事が消えたらそれに紐付いたコメント全てが消えるべきです。 -
Many-to-Many
記事とカテゴリの関係。記事は複数のカテゴリを複数持つことが出来ますが、そのカテゴリは他の記事でも使用するため親子関係は無く削除の影響を受けません。
MySQLなど一般的なRDBではこれらの関連に外部キーの定義が必須となりますが、CoreDataやRealmではオブジェクトそのものの参照を持つためキーは不要です。キーを用いたクエリを書かなくても関連したオブジェクトを芋づる式で引き出せるのでコードがとても簡潔になります。
尚、試したRealmのバージョンは 0.89.2 です。
One-to-One
記事とサムネイルが1対1となる最も単純なリレーションです。まずはArticleとThumbnailのモデルを定義しましょう。まだここではリレーション関係はありません。
class Article: RLMObject {
dynamic var title = ""
dynamic var body = ""
}
class Thumbnail: RLMObject {
dynamic var imageData:NSData?
}
このAritcleクラスにThumbnailのリレーションプロパティを追加します。今回は1対1なので単にプロパティの型宣言を参照先のモデルクラスにすればよいだけです。
class Article: RLMObject {
dynamic var title = ""
dynamic var body = ""
dynamic var thumbnail:Thumbnail? //←追加
}
このプロパティにThumbnailのインスタンスをセットすればリレーションの完成です。
let realm = RLMRealm.defaultRealm();
// トランザクション開始
realm.beginWriteTransaction();
// 記事オブジェクト作成
let articleInfo = ["title": "タイトル", "body": "本文"];
let article = Article.createInDefaultRealmWithObject(articleInfo)
// サムネイルのインスタンスを作成
let thumbnail = Thumbnail(object: ["imageData": NSData()])
// 記事オブジェクトにセット
article.thumbnail = thumbnail
// コミット
realm.commitWriteTransaction();
ただ、このままだと親となるArticleから子へのThumbnailへの一方的な関連となります。子のThumbnailオブジェクトから親のArticleを知ることができません。そこでCoreDataにもあったInverse Relationship(逆関連)を設定することにより相互の関係を結びつけることができます。
CoreDataだとこういうやつ↑
Inverse Relationshipなプロパティはデータベース上の実カラムではなく親のクラス名とプロパティ名から取り出したオブジェクトを返すreadonlyなプロパティを自前で定義する感じです。関連した親オブジェクトを返すメソッドは linkingObjectsOfClass(className: forProperty:) で取得できます。
class Thumbnail: RLMObject
{
dynamic var imageData:NSData?
dynamic var article:Article? {
return linkingObjectsOfClass("Article", forProperty: "thumbnail").first as? Article
}
}
これで子のThumbnailオブジェクトから親のArticleへプロパティでアクセスできるようになりました。
let thumbnail = article.thumbnail
if (thumbnail.article == article) {
println("同じだよ!")
}
この linkingObjectsOfClass(className: forProperty:) メソッドは関連するオブジェクトを全て返します。今回は1対1で親が1件特定できれば良いため、最初のオブジェクトを返すようにしましたが、親が複数存在するような多対多の関連でも使用できます。
One-to-Many
記事とコメントの関係は1対多となります。Articleに複数のコメントオブジェクトを保持するにはcommentsプロパティにRLMArrayオブジェクトのインスタンスをセットします。CommentクラスにはInverse RelationshipでArticleへの関連を定義しておきます。
class Article: RLMObject {
dynamic var title = ""
dynamic var body = ""
dynamic var thumbnail:Thumbnail?
dynamic var comments = RLMArray(objectClassName: Comment.className()) // ← 追加
}
class Comment: RLMObject {
dynamic var name = ""
dynamic var comment = ""
dynamic var article:Article? {
return self.linkingObjectsOfClass("Article", forProperty: "comments").first as? Article
}
}
記事へのコメント追加はcomments.addObject()でCommentインスタンスを追加します。
realm.beginWriteTransaction();
// コメントオブジェクトを作成
let comInfo = ["comment": "コメント", "name": "氏名"];
let comment = Comment.createInDefaultRealmWithObject(comInfo)
// 記事のプロパティに追加
article.comments.addObject(comment)
realm.commitWriteTransaction();
// Comment->Articleの逆参照も可
println("articleTitle: \(comment.article.title)")
簡単ですね!
Many-to-Many
最後は多対多の関連。記事とカテゴリの関係です。カテゴリオブジェクトはマスター的な扱いで記事ごとに作成されるものではなく他の記事と共有されます。図で表すと↓こんな感じ。
Realmの実装はOne-To-Manyの応用です。Articleクラスにcategoriesプロパティを追加し、CategoryクラスにArticleへの逆参照を定義します。子から親への関連も対多となるため、配列でreturnするところがOne-to-Manyと違います。
class Article: RLMObject {
dynamic var title = ""
dynamic var body = ""
dynamic var thumbnail:Thumbnail?
dynamic var comments = RLMArray(objectClassName: Comment.className())
dynamic var categories = RLMArray(objectClassName: Category.className()) // ← 追加
}
class Category: RLMObject {
dynamic var name = ""
dynamic var key = ""
var articles:[Article] {
return self.linkingObjectsOfClass("Article", forProperty: "categories") as [Article]
}
}
Cascade Delete(?)
と、ここまででモデル間の関連が出来たから参照整合性制約もバッチリ!と思い、Articleオブジェクトをまとめて消してみました。Inverse Relationshipも設定済みなので、親を消せば子も自動で消えるよねってことで10件くらい記事とコメントなどを登録後、全ての記事オブジェクトをサクッと消してみたのが↓のコードです。
// 10件くらい挿入後に全件取得
let allArticles = Article.allObjects()
// まとめて削除
RLMRealm.defaultRealm().deleteObjects(allArticles)
// 子のオブジェクト数を取得
let allThumbnailcount = Thumbnail.allObjects().count
let allCommentCount = Comment.allObjects().count
ThumbnailCount と CommentCount の期待値はいずれもゼロになるはず。
println("ThumbnailCount: \(allThumbnailcount)")
println("CommentCount: \(allCommentCount)")
> ThumbnailCount: 10
> CommentCount: 10
?!
子オブジェクトが消えてない!
Google先生に聞いたところStackOverFlowに↓のような投稿が。。。
どうも現時点のRealmでは参照整合制約ガン無視なので自前で子オブジェクトを削除する必要があるらしい。。
今後に期待ですね!