【Swift4】Realm+Codableを使ったお手軽なDB Part.3（クエリ編）

# はじめに

前回の記事[【Swift4】Realm+Codableを使ったお手軽なDB Part.2（リレーション編）](https://qiita.com/cottpan/items/4bb5ad8bce2e28c0220a)の続き、Part3です。

今回はRealmへのクエリをまとめました！😀
Codableから少し遠ざかってしまいますが、あしからず。

# Realmへのクエリ
RealmへのクエリはCore Dataと同様にNSPredicateを使用します。formatを文字列として指定する必要があったりと、あまり使い勝手がよくありません。
そのため、[SwiftでRealmを使う時のTips(3) NSPredicate編](https://qiita.com/nakau1/items/40865299dacc50d71604)をそのまま使わせて頂いています😎

### テーブル全件取得

```swift
realm.objects(<Type>.self)
```

### 1件取得
プライマリーキーが設定されているテーブルは、以下のように取得できます。

```swift
realm.object(ofType: <Type>.self, forPrimaryKey: <Key>)
```

### クエリを用いての取得(よく使うもの)

```swift
// =
realm.objects(<Type>.self).filter(NSPredicate("<key>", equal: "<query>" as AnyObject))
// !=
realm.objects(<Type>.self).filter(NSPredicate("<key>", notEqual: "<query>" as AnyObject))
// 部分一致
realm.objects(<Type>.self).filter(NSPredicate("<key>", contains: "<query>" as AnyObject))
// IN句
realm.objects(<Type>.self).filter(NSPredicate("<key>", valuesIn: [<query>] as [AnyObject]))

// 条件の結合
let predicates = NSPredicate.empty()
	.and(NSPredicate("<key>", equal: "<query>" as AnyObject))
	.or(NSPredicate("<key>", equal: "<query>" as AnyObject))
	...
realm.objects(<Type>.self).filter(predicates)

// 結果のソート
realm.objects(<Type>.self).sorted(byKeyPath: "<key>", ascending: false)
```

このように、クラス拡張とメソッドチェインを使うことで、Realmへのアクセスが断然使いやすくなりました！

# enum定義
NSPredicateの拡張によって使いやすくなったクエリですが、keyを直打ちするのがスマートではありません🙃
Typoやカラムの変更があった際に、コンパイルしても気づくことが出来ず、実行時にクラッシュする原因になります。

そこで各テーブル（クラス）に各変数の名前を**enumで定義**してあげます。[前回の記事](https://qiita.com/cottpan/items/4bb5ad8bce2e28c0220a)で使用したSalesクラスを元にすると、

```swift
enum StringKey: String {
	case id
	case date
	case birthday
	case productID
	case employeeID
	case employee
	case products
	case customer
}
```

これとクエリを組み合わせると、

```swift
realm.objects(Sales.self).filter(NSPredicate(Sales.StringKey.id.rawValue, equal: 553 as AnyObject))
```
このような感じになります。少し1行が長くなってしまっていますが、コンパイラの支援を受けることが出来ます！

# クエリのラップクラス
Clean Architectureなどのアーキテクチャを使用してアプリ開発を行う場合、実際のRealmへのアクセスとデータベースへのI/Fは分離し、データベースに依存しないような構成を取ることがあると思います。

```DataStore.swift
class DataStore {
	let realm = try! Realm()
	
	func readAll<T: Object>() -> [T] {
		let result = realm.objects(T.self)
		return Array(result)
	}
	
	func readEqual<T: Object>(ofTypes: String, forQuery: AnyObject) -> [T] {
		let result = realm.objects(T.self).filter(NSPredicate(ofTypes, equal: forQuery))
		return Array(result)
	}
	
	func create<T: Object>(data: [T]) {
		try! realm.write {
				realm.add(data)
			}
	}
	
	func delete<T: Object>(_ type: T.Type, predicates: NSPredicate) {
		try! realm.write {
				let obj = realm.objects(T.self).filter(predicates)
				realm.delete(obj)
			}
	}
}
```

そのため、このようなCRUDに相当するクラスを作成しました。(今回は説明のため、force-unwrappingはそのままです)
Genericsメソッドにしてあるので、このクラスを使うことですべてのテーブルにアクセス出来るようになります😎

# さいごに
今回の記事では、クエリ編としてRealmをより便利に使うためのポイントを紹介しました。

続編書きました！
[【Swift4】Realm+Codableを使ったお手軽なDB Part.4（番外編）](https://qiita.com/cottpan/items/e9647991936f249dfb32)


***
[株式会社Nexceed](https://www.wantedly.com/companies/nexceed) にて、一緒に働いてくれる仲間を募集中です:point_down::point_down::point_down:
 - [建築業界に革命を！！AI × BIMの世界を作り出すアプリエンジニア募集！](https://www.wantedly.com/projects/248737)