Swiftでデータベースを使おう！②（関連付け・検索）

__【お知らせ】Swift3に対応しました！(2017/02/23更新)__

# 【Swift×mBaaS】シリーズ概要
* Swiftで[ニフティクラウドmobile backend](http://mb.cloud.nifty.com/)(通称：mBaaS)を使ってSwiftで開発を始めたい人向けのドキュメントです。
* [ニフティクラウドmobile backendのiOS(Objectiv-C SDK)用ドキュメント](http://mb.cloud.nifty.com/doc/current/index.html)を__Swift__用に書き換えて動かしてみたものをまとめました。
* 初心者でもわかりやすいよう心掛けて作っていますが、わかりにくい部分がありましたらコメントをいただければ訂正しますのでお気軽にご意見をお願いします^^*
* 今回は__＜データストア編②＞__として_関連付け(ポインタ・リレーション)_・_検索_・_標準クラスの検索_・_検索条件_・_クエリの合成_です！
 * 注：「データストア」が[ニフティクラウドmobile backend](http://mb.cloud.nifty.com/)の「データベース」のことです◎


# 事前準備のお願い
* 事前にニフティクラウド[mobil backendの登録](http://mb.cloud.nifty.com/)とアプリの新規作成をし、 [＜CocoaPods＞SwiftでmBaaSを始めよう！](http://qiita.com/natsumo/items/b2d18d87d57300c8d81c)をご覧いただき、必要な初期設定(SDKのインストールなど)をXcode上で行った上でご利用ください。



# **Swiftでデータベースを使おう！**
* [ニフティクラウドmobile backend](http://mb.cloud.nifty.com/)のダッシュボードで確認をしながらXcodeで動作確認をしてください。
* ドキュメントの動作確認の方法については[Swiftでデータベースを使おう！①（保存・取得・更新・削除）](http://qiita.com/natsumo/items/c00cf7a48e0f8cd8d236)の「ドキュメントの動作確認方法」にまとめてありますので参考にしてみてください◎


## オブジェクトの関連付け
### ポインタ
* NCMBObjectのインスタンスに、別のNCMBObjectを設定すると、設定したNCMBObjectへのポインタが設定されます。
* ポインタ先のオブジェクトがデータストアに登録されていない場合は、ポインタ元のオブジェクトを保存する前に自動で作成されるようになっています。
* 以下の例はobjectクラスのpointerフィールドでchildクラスを参照するサンプルです。

```swift:Swift3
// childクラスを作成（keyフィールドにvalueの値を保存）
let pointer = NCMBObject(className: "child")
pointer?.setObject("value", forKey : "key")
pointer?.save(nil) /*同期処理*/
// objectクラスを作成
let object = NCMBObject(className: "object")
// ポインターの設定
object?.setObject(pointer, forKey: "pointer")
object?.saveInBackground({(error) in
    if (error != nil) {
        // 保存に失敗した場合の処理
    }else{
        // 保存に成功した場合の処理
    }
})
```

```swift:Swift2
// childクラスを作成（keyフィールドにvalueの値を保存）
let pointer = NCMBObject(className: "child")
pointer.setObject("value", forKey : "key")
pointer.save(nil) /*同期処理*/
// objectクラスを作成
let object = NCMBObject(className: "object")
// ポインターの設定
object.setObject(pointer, forKey: "pointer")
object.saveInBackgroundWithBlock({(NSError error) in
    if (error != nil) {
        // 保存に失敗した場合の処理
    }else{
        // 保存に成功した場合の処理
    }
})
```

### リレーション
* ポインタは1つのオブジェクトへの参照しか持つことができないので、複数参照を行いたい場合にはリレーションを利用します。
* リレーションは特定クラスの複数オブジェクトと関連づけることができます。
* リレーションの追加・削除にはNCMBRelationクラスを利用します。
* ポインタでは参照先のオブジェクトが登録されていない場合、自動で作成されましたが、リレーションの場合は自動的に保存されることはありません。
* 以下の例は、PostクラスのcommentsフィールドにNCMBRelationを作成してCommentクラスの２つのオブジェクト（comment1
とcomment2）をリレーション先として設定しているサンプルです。

```swift:Swift3
// リレーション元のオブジェクトを作成
let post = NCMBObject(className: "Post")
post?.setObject("about mobile backend", forKey: "title")
        
// リレーション先1のオブジェクトを作成
let comment1 = NCMBObject(className: "Comment")
comment1?.setObject("good", forKey: "text")
comment1?.save(nil)

// リレーション先2のオブジェクトを作成
let comment2 = NCMBObject(className: "Comment")
comment2?.setObject("bad", forKey: "text")
comment2?.save(nil)
        
// commentsフィールドにNCMBRelationを作成
let relation = NCMBRelation(className: post, key: "comments")
        
// リレーションオブジェクトを追加
relation?.add(comment1)
relation?.add(comment2)
        
// リレーションを設定したオブジェクトを保存
post?.save(nil)
```

```swift:Swift2
// リレーション元のオブジェクトを作成
let post = NCMBObject(className: "Post")
post.setObject("about mobile backend", forKey: "title")

// リレーション先1のオブジェクトを作成
let comment1 = NCMBObject(className: "Comment")
comment1.setObject("good", forKey: "text")
comment1.save(nil)
        
// リレーション先2のオブジェクトを作成
let comment2 = NCMBObject(className: "Comment")
comment2.setObject("bad", forKey: "text")
comment2.save(nil)
        
// commentsフィールドにNCMBRelationを作成
let relation = NCMBRelation(className: post, key: "comments")
        
// リレーションオブジェクトを追加
relation.addObject(comment1)
relation.addObject(comment2)

// リレーションを設定したオブジェクトを保存
post.save(nil)
```

## オブジェクトの検索
* 検索には、NCMBQueryクラスを利用します。
* 下記の例は、条件なしで検索を行うサンプルです。クラスのデータが全件検索されます。

```swift:Swift3
// TestClassクラスを検索するNCMBQueryを作成
let query = NCMBQuery(className: "TestClass")
        
/** 条件を入れる場合はここに書きます **/
        
// データストアの検索を実施
query?.findObjectsInBackground({(objects, error) in
    if (error != nil){
        // 検索失敗時の処理
    }else{
        // 検索成功時の処理
        print(objects! as! [NCMBObject]) // (例)検索結果を表示する
    }
})
```

```swift:Swift2
// TestClassクラスを検索するNCMBQueryを作成
let query = NCMBQuery(className: "TestClass")

    /** 条件を入れる場合はここに書きます **/

// データストアの検索を実施
query.findObjectsInBackgroundWithBlock({(NSArray objects, NSError error) in
    if (error != nil){
        // 検索失敗時の処理

    }else{
        // 検索成功時の処理
        print(objects) // (例)検索結果を表示する
    }
})
```

* 「/** 条件を入れる場合はここに書きます **/」とあるところに条件を入れて検索できます。
* 条件については後の項目に書きます。

## 標準クラスの検索
* 標準で存在するクラスは以表の５つあります。

| クラス名     | 役割                                             |
|:-------------|:------------------------------------------------|
| user         | 会員管理クラス                                   |
| role         | データストアのデフォルトクラス(会員グルーピング)  |
| installation | データストアのデフォルトクラス(端末情報一覧)      |
| file         | ファイルストアのクラス                         　|
| push         | プッシュ通知のクラス                             |

* これらのクラス名を使って、データストアに新規のクラスを作成しようとするとエラーになります。
* 上記のクラス名を指定して、同様にオブジェクト検索を行うことができます。userクラスの検索の場合は以下のようになります。

```swift:Swift_共通
let query = NCMBQuery(className: "user")
```
* userクラスの場合はNCMBUserオブジェクトを使って次のようにクエリを作成することも出来ます。

```swift:Swift_共通
let query = NCMBUser.query()
```

### 補足
* 各データにはACL(アクセス権限)設定機能があります。デフォルトでは権限は設定されていません。（誰でも読み書き可能）
* 会員クラス(user)に関してはユーザー本人しか読み書きが行えない設定になっています。上記サンプルを実行する前にダッシュボード上からACL設定を確認してください。詳しくは[＜データストア編③＞](http://qiita.com/natsumo/items/25074fa1ce209033e98e)で説明します。


## 検索条件の設定
以下は検索条件のサンプルです。

```swift:Swift3
// keyフィールドの値がvalueと一致するデータを検索する条件
query?.whereKey("key", equalTo: "value")
// ["A","B","C"]と一致する配列を検索する条件
query?.whereKey("array", equalTo:["A","B","C"])
// "A"と"B"の両方の値が入っている配列を検索する条件
query?.whereKey("array", containsAllObjectsIn: ["A","B"])
```

```swift:Swift2
// keyフィールドの値がvalueと一致するデータを検索する条件
query.whereKey("key", equalTo: "value")
// ["A","B","C"]と一致する配列を検索する条件
query.whereKey("array", equalTo:["A","B","C"])
// "A"と"B"の両方の値が入っている配列を検索する条件
query.whereKey("array", containsAllObjectsInArray: ["A","B"])
```

* whereKey:equalToメソッドなど以外にも検索条件を設定することが可能です。

### ポインタ検索条件の設定
objectクラスのpointerフィールドに設定されたポインタ(childクラスのデータ)を検索します。※関連付け(ポインタ)で作成したサンプルでで試すことができます。

```swift:Swift3
// childクラスを検索するNCMBQueryを作成
let innerQuery = NCMBQuery(className:"child")
// keyフィールドの値でvalueと一致するデータを検索する条件を設定
innerQuery?.whereKey("key", equalTo:"value")
// objectクラスを検索するNCMBQueryを作成
let query = NCMBQuery(className:"object")
// innerQueryの条件と一致するデータをpointerフィールが参照しているchildクラスのデータから検索する条件を設定
query?.whereKey("pointer", matchesQuery:innerQuery)
// 設定した条件で検索
query?.findObjectsInBackground({(objects, error) in
    if (error != nil){
        // 検索失敗時の処理
    }else{
        // 検索成功時の処理
        print(objects! as! [NCMBObject])
    }
})
```

```swift:Swift2
// childクラスを検索するNCMBQueryを作成
let innerQuery = NCMBQuery(className:"child")
// keyフィールドの値でvalueと一致するデータを検索する条件を設定
innerQuery.whereKey("key", equalTo:"value")
// objectクラスを検索するNCMBQueryを作成
let query = NCMBQuery(className:"object")
// innerQueryの条件と一致するデータをpointerフィールが参照しているchildクラスのデータから検索する条件を設定
query.whereKey("pointer", matchesQuery:innerQuery)
// 設定した条件で検索
query.findObjectsInBackgroundWithBlock({(NSArray objects, NSError error) in
    if (error != nil){
        // 検索失敗時の処理

    }else{
        // 検索成功時の処理
        print(objects) 
    }
})
```

### リレーションの検索条件の設定

* Postクラスのcommentsフィールドに設定されたリレーションを検索します。
* __！！注意！！__以下のプログラムの動作確認はできておりません。。。(*2016/2/17現在*)
    * SDKのせいなのか？？エラー原因は調査中です。やはりSwiftにちゃんと対応したSDKではないせいなのかな...;_;
    * 対処方法が見つかり次第更新します。

```swift:Swift
let post = NCMBObject(className:"Post")
// Postクラスのcommentsフィールドにリレーションを作成
let relation = NCMBRelation(className: post, key: "comments")
// リレーションを検索するクエリを取得
let query = relation.query()                             // ←この行を実行中にエラー発生...
```

## クエリの合成
検索条件としてand検索やor検索も簡単に指定できます。
### and検索
* 条件を複数指定するとand検索をすることができます。

```swift:Swift3
//intフィールドの値が30より上で、50未満の値を検索する条件が設定される
query?.whereKey("int", greaterThan:30)
query?.whereKey("int", lessThan:50)
```

```swift:Swift2
//intフィールドの値が30より上で、50未満の値を検索する条件が設定される
query.whereKey("int", greaterThan:30)
query.whereKey("int", lessThan:50)
```

### or検索
* or検索にはorQueryWithSubqueriesメソッドを利用します。

```swift:Swift3
let className = "test"
let query1 = NCMBQuery(className:className)
query1?.whereKey("key1", equalTo:"value1")
let query2 = NCMBQuery(className:className)
query2?.whereKey("key2", equalTo:"value2")
//key1がvalue1であるか、key2がvalue2であるデータを検索する条件
let query = NCMBQuery.orQuery(withSubqueries: [query1!,query2!])
```

```swift:Swift2
let className = "test"
let query1 = NCMBQuery(className:className)
query1.whereKey("key1", equalTo:"value1")
let query2 = NCMBQuery(className:className)
query2.whereKey("key2", equalTo:"value2")
//key1がvalue1であるか、key2がvalue2であるデータを検索する条件
let query = NCMBQuery.orQueryWithSubqueries([query1,query2])
```

# 参考
* 元サイト
 * [ニフティクラウドmobile backendのiOS(Objectiv-C)用ドキュメント](http://mb.cloud.nifty.com/doc/current/index.html)
* 【Swift×mBaaS】シリーズ
 * [Swiftでデータベースを使おう！①（保存・取得・更新・削除）](http://qiita.com/natsumo/items/c00cf7a48e0f8cd8d236)
 * [Swiftでデータベースを使おう！③（ランキングを作る・アクセス権限）](http://qiita.com/natsumo/items/25074fa1ce209033e98e)
 * [Swiftでログイン機能をつけてみよう！](http://qiita.com/natsumo/items/25c97182fab4db5629b1)
 * [Swiftでプッシュ通知を送ろう！](http://qiita.com/natsumo/items/8ffafee05cb7eb69d815)
 * [SwiftでFacebook連携しよう！](http://qiita.com/natsumo/items/f5c4d41edae17a4a75ce)