はじめに
私めが比較的よく使う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
も取得しておいて、それで検索絞り込みしたほうが良かったかもしれないですね。タイトルが変わる可能性があるので。
動かしてみる
いざ。
相変わらず例のへっぽこなので、MongoExpressも見て確認します。
MongoDBのドキュメント更新の補足
更新対象のドキュメントが複数ある時
タイトルを部分一致検索で検索して更新する場合や全ドキュメントを一括で更新する場合などにはupdateOne
ではなくupdateMany
を使ってください。
複数検索されたときにupdateOne
を使うと、検索された先頭の1件のみが更新されます。
存在しないキーを渡した時
更新対象のドキュメントにそもそも無いキーを渡すと、その差分が追加されます。注意点は、更新したドキュメントにしか追加されないことです。RDBに慣れているとうっかり勘違いしてしまいそうなところです。
既存のキーの一部を削除したい時
更新とはちょっと毛色が違うかもしれないですが、ドキュメントを削除するのではなく、その中身のうち特定のキーと値のセットを削除するときです。
一旦取得してから、削除からの追加で対応することになりそうです。
既存のキーを削除するシチュエーションは、おそらく全ドキュメントを一括に対応することかと思うので(いわゆるメンテナンス)、それ専用の処理を作ったほうが早そうですね。
特定のキーと値のセットだけを削除する方法があるかもしれません。
まとめ
よく使うHTTPリクエストが一通りできるようになりました。ただし、やり取りはできるようになったものの、API周りの深掘りが必要ですね。特にエンドポイントの設計。UIと並行して進めていきます。
そういえば未だあやふやなPUTとPOSTの使い分けも、はっきりとさせないといけないですね。