MongoDBのデータを更新してみる

MongoDBのデータを更新してみるMongoDB
MongoDBのデータを更新してみる

はじめに

私めが比較的よく使うHTTPリクエストはGET、POST、PUT、DELETEです。
Vaporアプリケーションサーバ経由でMongoDBにGET、POST、DELETEしてきまして、残すところあとPUTのみとなりました。

PUTを使ってMongoDBの既存のドキュメントを更新してみようと思います。
前提条件は毎度おなじみ、MongoDBからデータをGETしてみた時と同じです。

また、API周りにフォーカスを当てるので、UI周りの話はほぼ無しで進めます。

PUTまでの道のり

更新対象のドキュメントを特定する

今回は一旦ローカルに取得したドキュメントの情報を使ってサーバのドキュメントを更新するシチュエーションを考えます。
なので、「一旦ローカルに取得したドキュメント」となるものを想定し、既にローカルに取得した時点からのスタートになります。

今回更新対象のドキュメント
このドキュメントをターゲットにします

(参考)Vaporアプリケーション側の更新処理

コード例

router.put(Book.self, at:"api/book") { (req, book) -> Future<HTTPStatus> in
    let client = try! req.make(MongoClient.self)
    let collection = client.db("vaporapp").collection("books", withType: Book.self)
    return try req.content.decode(Book.self).map(to: HTTPStatus.self) { book in
        let query: Document = try Document.init(fromJSON: "{\"title\":\"" + book.title + "\"}")
        let updatedBook: Document = try Document.init(fromJSON: "{\"$set\": {\"title\":\"" + book.title + "(更新)\"" + "}}")
        _ = try! collection.updateMany(filter: query, update: updatedBook)
        return .ok
    }
}

HTTPステータスの返り値がokだけなのは大目に見てください。

ここでのポイントは2点です。

サーバ側でも更新対象のドキュメントを特定する

ローカルから与えたドキュメントの情報だけではサーバ側でドキュメントの特定ができないので、クエリを発行しています。

既存のキーの値を更新するときに$setを付ける

"{\"$set\": {\title\":" + book.title + "(更新)" + "}}"

これはキーtitleの値に(更新)をつなげるものですが、$setを付けることで、既存キーの値に更新であることを示しています。
ちなみに複数のキーを更新するときは$setの値に配列で渡してあげます。

データをPUTするメソッドを作る

引数にはローカルに取得したドキュメントを渡します。

func put(data:Book) {
    let encoder = JSONEncoder()
    do {
        let jsondata = try encoder.encode(data)
        let jsonstr:String = String(data: jsondata, encoding: .utf8)!
        print(jsonstr)
        let url: URL = URL(string: [接続先アドレス] + "/api/book")!
        var request = URLRequest(url: url)
        request.httpMethod = "PUT"
        request.httpBody = jsondata
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        let task:URLSessionTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
            if let dataString = data {
                print("data:" + (String(data: dataString, encoding: .utf8) ?? "nilです"))
            }
            print("response: \(String(describing: response))")
            print("error: \(String(describing: error))")
        }
        task.resume()
    } catch {
        print(error.localizedDescription)
    }
}

わざわざローカルに取得したドキュメントを渡さなくてもタイトルだけ渡せば良いような気がしますが、あえてドキュメントを渡しています。
これは、後でUI周りを整備したときに、ドキュメントの更新をできるようにしたいためです。そのときに改めてドキュメント更新用のメソッドを作るのも何ですし、タイトル以外の値を更新するかもしれないですし。この場限りならばタイトルだけポンすれば良いんですけどね。

あ!ローカル取得時にidも取得しておいて、それで検索絞り込みしたほうが良かったかもしれないですね。タイトルが変わる可能性があるので。

動かしてみる

いざ。

PUTによる更新が行われた結果(XCodeコンソール)
見た目上はうまく行っているようです。

相変わらず例のへっぽこなので、MongoExpressも見て確認します。

PUTによる更新が行われた結果
きちんと更新されていました!

MongoDBのドキュメント更新の補足

更新対象のドキュメントが複数ある時

タイトルを部分一致検索で検索して更新する場合や全ドキュメントを一括で更新する場合などにはupdateOneではなくupdateManyを使ってください。

複数検索されたときにupdateOneを使うと、検索された先頭の1件のみが更新されます。

存在しないキーを渡した時

更新対象のドキュメントにそもそも無いキーを渡すと、その差分が追加されます。注意点は、更新したドキュメントにしか追加されないことです。RDBに慣れているとうっかり勘違いしてしまいそうなところです。

既存のキーの一部を削除したい時

更新とはちょっと毛色が違うかもしれないですが、ドキュメントを削除するのではなく、その中身のうち特定のキーと値のセットを削除するときです。
一旦取得してから、削除からの追加で対応することになりそうです。

既存のキーを削除するシチュエーションは、おそらく全ドキュメントを一括に対応することかと思うので(いわゆるメンテナンス)、それ専用の処理を作ったほうが早そうですね。
特定のキーと値のセットだけを削除する方法があるかもしれません。

まとめ

よく使うHTTPリクエストが一通りできるようになりました。ただし、やり取りはできるようになったものの、API周りの深掘りが必要ですね。特にエンドポイントの設計。UIと並行して進めていきます。

そういえば未だあやふやなPUTとPOSTの使い分けも、はっきりとさせないといけないですね。

タイトルとURLをコピーしました