22
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Swift] Firebase Cloud FirestoreのTransactionでSubCollectionを更新する

Last updated at Posted at 2019-04-18

##はじめに
開発しているアプリにクーポン機能を入れることになり、TransactionでSubCollectionを更新する方法が出てこなったのでその方法と、その際にハマったことを共有したいと思います。
(手探りだったのでもっと良い方法や間違いがありましたら教えてください...)

##やりたいこと
そのときの状況を説明した方がわかりやすいかと思うので軽く説明します。

###クーポン機能の実装
↓仕様はこちら
・使用したユーザーにポイントを付与する
・同じクーポンは一度しか使えない

クーポンは同時に更新がかかる可能性があるためTransactionを使用します。
Transactionってなんぞやって思った方はこちら

クーポンは各ユーザーごとに一度しか使えないのでCouponのSubCollectionのUsersに自分がいるかどうかで判定をするためにクーポン使用時にSubCollectionにUserを保存します。

##登場人物(DB)

// User
[
  "name": Boo, 
  "point": 0, 
  "createdAt": FIRTimestamp: seconds=1555408365 nanoseconds=64000000>,
  "updatedAt": FIRTimestamp: seconds=1555408365 nanoseconds=64000000>
]
// Coupon
[
  "point": 500, 
  "users": [] // このUserにクーポンを使用したUserを保存する
  "createdAt": FIRTimestamp: seconds=1555408137 nanoseconds=664000000>,
  "updatedAt": FIRTimestamp: seconds=1555467699 nanoseconds=701000000>
]

##TransactionでSubCollectionを保存する

TransactionでCouponのSubcollectionであるUsersを更新するにはCouponのDocumentReferenceとは別で考える必要があります。
今回の場合はUsersのDocumentReferenceは以下のようになります。

let usersReference: DocumentReference = Firestore.firestore().collection("version").document("1").collection("coupon").document("OqX0FUetRXaOBrQrn9Yz").collection("users").document()

では実際にTransactionで更新してみます。

// 更新するUserのDocumentReference
let userReference: DocumentReference = Firestore.firestore().collection("version").document("1").collection("user").document("Rdw5F1tIPCcLYYKhbBjH")

// 更新するCouponのDocumentReference
let couponReference: DocumentReference = Firestore.firestore().collection("version").document("1").collection("coupon").document("OqX0FUetRXaOBrQrn9Yz")

// 更新するCouponが持っているサブコレクションのUsersのDocumentReference
let usersReference: DocumentReference = Firestore.firestore().collection("version").document("1").collection("coupon").document("OqX0FUetRXaOBrQrn9Yz").collection("users").document()

Firestore.firestore().runTransaction({ (transaction, errorPointer) -> Any? in
    do {
        // 更新するCoupon
        let coupon: DocumentSnapshot = try transaction.getDocument(couponReference)
        // クーポンを使用したUserに付与するポイントをCouponから取得する
        let point: Int = coupon.data()!["point"] as? Int ?? 0

        // 更新するUser
        let user: DocumentSnapshot = try transaction.getDocument(userReference)
        // ユーザーが現在持っているポイントをUserから取得する
        let toPoint: Int = userSnapshot.data()!["point"] as? Int ?? 0

        // Userの持っているポイントにCouponのポイントを加算して更新する
        transaction.setData(["point" : toPoint + point], forDocument: userReference, merge: true)

        // CouponのUsersに使用したUserを保存する
        transaction.setData(userSnapshot.data()!, forDocument: usersReference, merge: true)
    } catch (let error) {
        print(error)
    }
    return nil
}, completion: { (_, error)  in
    if let error = error {
        print(error)
        return
    }
})

##実装結果
一見うまくいきそうですが

Every document read in a transaction must also be written in that transaction.

このようなエラーが出力されます。どのようなエラーかというと、

トランザクションで読み取られるすべての文書はそのトランザクションに書き込まれる必要があります。

ということみたいです。

もう一度先程のコードを見返してみると


let coupon: DocumentSnapshot = try transaction.getDocument(couponReference)
// クーポンを使用したUserに付与するポイントをCouponから取得する
let point: Int = coupon.data()!["point"] as? Int ?? 0

付与するポイントを取得するためにtransactionからCouponのDocumentReferenceを取得しています。

// CouponのUsersに使用したUserを保存する
transaction.setData(userSnapshot.data()!, forDocument: usersReference, merge: true)

SubCollectionであるusersは保存していますが、親のCouponを保存してないことがわかるかと思います。
エラーの原因はこれです。

では親のCouponも保存する処理を入れてみます。

// CouponのupdatedAtを更新する
transaction.setData(["updatedAt" : FieldValue.serverTimestamp()], forDocument: couponReference, merge: true)

こちらの処理を追加します。

##再度実行してみる

####Coupon
スクリーンショット 2019-04-18 19.17.40.png

####User
スクリーンショット 2019-04-18 19.18.20.png

このようにCouponのSubCollectionであるUsersにUserが保存され、Userのポイントも更新されています。

##まとめ

####TransactionでSubCollectionを更新する場合

  1. 親のDocumentReferenceとは別で考える
  2. runTransaction内で親のDocumentReferenceを取得している場合、親のDocumentも更新する必要がある
22
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?