Skip to content

Commit

Permalink
fix: updating saved objects using saveAll (#423)
Browse files Browse the repository at this point in the history
* initial deprecations

* improve messages

* fix: updating saved objects using saveAll

* update to latest Xcode

* fix changelog
  • Loading branch information
cbaker6 committed Oct 9, 2022
1 parent e415f21 commit dc7666f
Show file tree
Hide file tree
Showing 31 changed files with 497 additions and 169 deletions.
2 changes: 1 addition & 1 deletion .codecov.yml
Expand Up @@ -6,7 +6,7 @@ coverage:
status:
patch:
default:
target: auto
target: 58
changes: false
project:
default:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -7,7 +7,7 @@ on:
env:
CI_XCODE_OLDEST: '/Applications/Xcode_12.5.1.app/Contents/Developer'
CI_XCODE_13: '/Applications/Xcode_13.4.1.app/Contents/Developer'
CI_XCODE_LATEST: '/Applications/Xcode_14.0.app/Contents/Developer'
CI_XCODE_LATEST: '/Applications/Xcode_14.0.1.app/Contents/Developer'

jobs:
xcode-test-ios:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Expand Up @@ -4,7 +4,7 @@ on:
types: [published]
env:
CI_XCODE_13: '/Applications/Xcode_13.4.1.app/Contents/Developer'
CI_XCODE_LATEST: '/Applications/Xcode_14.0.app/Contents/Developer'
CI_XCODE_LATEST: '/Applications/Xcode_14.0.1.app/Contents/Developer'

jobs:
cocoapods:
Expand Down
8 changes: 7 additions & 1 deletion CHANGELOG.md
@@ -1,9 +1,15 @@
# Parse-Swift Changelog

### main
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.14.1...main), [Documentation](https://swiftpackageindex.com/parse-community/Parse-Swift/main/documentation/parseswift)
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.14.2...main), [Documentation](https://swiftpackageindex.com/parse-community/Parse-Swift/main/documentation/parseswift)
* _Contributing to this repo? Add info about your change here to be included in the next release_

### 4.14.2
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.14.1...4.14.2), [Documentation](https://swiftpackageindex.com/parse-community/Parse-Swift/4.14.2/documentation/parseswift)

__Fixes__
- Addressed an issue that prevented updating ParseObjects with saveAll ([#423](https://github.com/parse-community/Parse-Swift/pull/423)), thanks to [Corey Baker](https://github.com/cbaker6).

### 4.14.1
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.14.0...4.14.1), [Documentation](https://swiftpackageindex.com/parse-community/Parse-Swift/4.14.1/documentation/parseswift)

Expand Down
Expand Up @@ -118,7 +118,7 @@ do {
//: There may be cases where you want to set/forceSet a value to null
//: instead of unsetting
let setToNullOperation = savedScore
.operation.set(("name", \.name), value: nil)
.operation.set(("name", \.name), to: nil)
do {
let updatedScore = try setToNullOperation.save()
print("Updated score: \(updatedScore). Check the new score on Parse Dashboard.")
Expand Down
Expand Up @@ -55,9 +55,10 @@ do {
try score.location = ParseGeoPoint(latitude: 40.0, longitude: -30.0)
}

/*: Save asynchronously (preferred way) - performs work on background
queue and returns to specified callbackQueue.
If no callbackQueue is specified it returns to main queue.
/*:
Save asynchronously (preferred way) - performs work on background
queue and returns to specified callbackQueue.
If no callbackQueue is specified it returns to main queue.
*/
score.save { result in
switch result {
Expand Down Expand Up @@ -107,8 +108,9 @@ do {
}
}

/*: If you only want to query for points in descending order, use the order enum.
Notice the "var", the query has to be mutable since it is a value type.
/*:
If you only want to query for points in descending order, use the order enum.
Notice the "var", the query has to be mutable since it is a value type.
*/
var querySorted = query
querySorted.order([.descending("points")])
Expand Down
Expand Up @@ -108,7 +108,7 @@ author.save { result in
assert(savedAuthorAndBook.createdAt != nil)
assert(savedAuthorAndBook.updatedAt != nil)

print("Saved \(savedAuthorAndBook)")
print("Saved: \(savedAuthorAndBook)")
case .failure(let error):
assertionFailure("Error saving: \(error)")
}
Expand All @@ -132,7 +132,7 @@ author2.save { result in
Notice the pointer objects have not been updated on the
client.If you want the latest pointer objects, fetch and include them.
*/
print("Saved \(savedAuthorAndBook)")
print("Saved: \(savedAuthorAndBook)")

case .failure(let error):
assertionFailure("Error saving: \(error)")
Expand Down Expand Up @@ -243,7 +243,7 @@ do {
assert(updatedBook.updatedAt != nil)
assert(updatedBook.relatedBook != nil)

print("Saved \(updatedBook)")
print("Saved: \(updatedBook)")
case .failure(let error):
assertionFailure("Error saving: \(error)")
}
Expand Down Expand Up @@ -323,12 +323,12 @@ author4.otherBooks = [otherBook3, otherBook4]
assert(savedAuthorAndBook.createdAt != nil)
assert(savedAuthorAndBook.updatedAt != nil)
assert(savedAuthorAndBook.otherBooks?.count == 2)

author4 = savedAuthorAndBook
/*:
Notice the pointer objects have not been updated on the
client.If you want the latest pointer objects, fetch and include them.
*/
print("Saved \(savedAuthorAndBook)")
print("Saved: \(savedAuthorAndBook)")
case .failure(let error):
assertionFailure("Error saving: \(error)")
}
Expand All @@ -339,5 +339,37 @@ author4.otherBooks = [otherBook3, otherBook4]
}
}

//: Batching saves by updating an already saved object.
author4.fetch { result in
switch result {
case .success(var fetchedAuthor):
print("The latest author: \(fetchedAuthor)")
fetchedAuthor.name = "R.L. Stine"
[fetchedAuthor].saveAll { result in
switch result {
case .success(let savedAuthorsAndBook):
savedAuthorsAndBook.forEach { eachResult in
switch eachResult {
case .success(let savedAuthorAndBook):
assert(savedAuthorAndBook.objectId != nil)
assert(savedAuthorAndBook.createdAt != nil)
assert(savedAuthorAndBook.updatedAt != nil)
assert(savedAuthorAndBook.otherBooks?.count == 2)

print("Updated: \(savedAuthorAndBook)")
case .failure(let error):
assertionFailure("Error saving: \(error)")
}
}

case .failure(let error):
assertionFailure("Error saving: \(error)")
}
}
case .failure(let error):
assertionFailure("Error fetching: \(error)")
}
}

PlaygroundPage.current.finishExecution()
//: [Next](@next)
Expand Up @@ -74,9 +74,10 @@ let profilePic = ParseFile(name: "profile.svg", cloudURL: linkToFile)
//: Set the picture as part of your ParseObject
score.profilePicture = profilePic

/*: Save asynchronously (preferred way) - Performs work on background
queue and returns to specified callbackQueue.
If no callbackQueue is specified it returns to main queue.
/*:
Save asynchronously (preferred way) - Performs work on background
queue and returns to specified callbackQueue.
If no callbackQueue is specified it returns to main queue.
*/
score.save { result in
switch result {
Expand Down Expand Up @@ -121,7 +122,8 @@ score.save { result in
}
}

/*: Files can also be saved from data. Below is how to do it synchronously, but async is similar to above
/*:
Files can also be saved from data. Below is how to do it synchronously, but async is similar to above
Create a new `ParseFile` for your data.
*/
let sampleData = "Hello World".data(using: .utf8)!
Expand Down Expand Up @@ -153,7 +155,8 @@ do {
print("The file is now saved at: \(fetchedFile.localURL!)")
print("The full details of your data ParseFile are: \(fetchedFile)")

/*: If you want to use the data from the file to display the text file or image, you need to retreive
/*:
If you want to use the data from the file to display the text file or image, you need to retreive
the data from the file.
*/
guard let dataFromParseFile = try? Data(contentsOf: fetchedFile.localURL!) else {
Expand All @@ -179,7 +182,8 @@ do {
fatalError("Error saving: \(error)")
}

/*: Files can also be saved from files located on your device by using:
/*:
Files can also be saved from files located on your device by using:
let localFile = ParseFile(name: "hello.txt", localURL: URL).
*/

Expand Down
2 changes: 2 additions & 0 deletions Sources/ParseSwift/API/API+Command+async.swift
Expand Up @@ -15,6 +15,7 @@ import FoundationNetworking
internal extension API.Command {
// MARK: Asynchronous Execution
func executeAsync(options: API.Options,
batching: Bool = false,
callbackQueue: DispatchQueue,
notificationQueue: DispatchQueue? = nil,
childObjects: [String: PointerType]? = nil,
Expand All @@ -24,6 +25,7 @@ internal extension API.Command {
downloadProgress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil) async throws -> U {
try await withCheckedThrowingContinuation { continuation in
self.executeAsync(options: options,
batching: batching,
callbackQueue: callbackQueue,
notificationQueue: notificationQueue,
childObjects: childObjects,
Expand Down
37 changes: 28 additions & 9 deletions Sources/ParseSwift/API/API+Command.swift
Expand Up @@ -55,7 +55,10 @@ internal extension API {
childFiles: [UUID: ParseFile]? = nil,
uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
stream: InputStream) throws {
switch self.prepareURLRequest(options: options, childObjects: childObjects, childFiles: childFiles) {
switch self.prepareURLRequest(options: options,
batching: false,
childObjects: childObjects,
childFiles: childFiles) {

case .success(let urlRequest):
if method == .POST || method == .PUT || method == .PATCH {
Expand All @@ -80,6 +83,7 @@ internal extension API {
}

func execute(options: API.Options,
batching: Bool = false,
notificationQueue: DispatchQueue? = nil,
childObjects: [String: PointerType]? = nil,
childFiles: [UUID: ParseFile]? = nil,
Expand All @@ -94,6 +98,7 @@ internal extension API {
let group = DispatchGroup()
group.enter()
self.executeAsync(options: options,
batching: batching,
callbackQueue: synchronizationQueue,
notificationQueue: notificationQueue,
childObjects: childObjects,
Expand All @@ -115,6 +120,7 @@ internal extension API {
// MARK: Asynchronous Execution
// swiftlint:disable:next function_body_length cyclomatic_complexity
func executeAsync(options: API.Options,
batching: Bool = false,
callbackQueue: DispatchQueue,
notificationQueue: DispatchQueue? = nil,
childObjects: [String: PointerType]? = nil,
Expand All @@ -131,6 +137,7 @@ internal extension API {
if !path.urlComponent.contains("/files/") {
// All ParseObjects use the shared URLSession
switch self.prepareURLRequest(options: options,
batching: batching,
childObjects: childObjects,
childFiles: childFiles) {
case .success(let urlRequest):
Expand All @@ -156,6 +163,7 @@ internal extension API {
// ParseFiles are handled with a dedicated URLSession
if method == .POST || method == .PUT || method == .PATCH {
switch self.prepareURLRequest(options: options,
batching: batching,
childObjects: childObjects,
childFiles: childFiles) {

Expand Down Expand Up @@ -187,6 +195,7 @@ internal extension API {
} else if method == .DELETE {

switch self.prepareURLRequest(options: options,
batching: batching,
childObjects: childObjects,
childFiles: childFiles) {
case .success(let urlRequest):
Expand All @@ -213,6 +222,7 @@ internal extension API {

if parseURL != nil {
switch self.prepareURLRequest(options: options,
batching: batching,
childObjects: childObjects,
childFiles: childFiles) {

Expand Down Expand Up @@ -266,6 +276,7 @@ internal extension API {

// MARK: URL Preperation
func prepareURLRequest(options: API.Options,
batching: Bool = false,
childObjects: [String: PointerType]? = nil,
childFiles: [UUID: ParseFile]? = nil) -> Result<URLRequest, ParseError> {
let params = self.params?.getURLQueryItems()
Expand Down Expand Up @@ -299,7 +310,9 @@ internal extension API {
} else {
guard let bodyData = try? ParseCoding
.parseEncoder()
.encode(urlBody, collectChildren: false,
.encode(urlBody,
batching: batching,
collectChildren: false,
objectsSavedBeforeThisOne: childObjects,
filesSavedBeforeThisOne: childFiles) else {
return .failure(ParseError(code: .unknownError,
Expand Down Expand Up @@ -393,14 +406,16 @@ internal extension API.Command {
// MARK: Saving ParseObjects
static func save<T>(_ object: T,
original data: Data?,
ignoringCustomObjectIdConfig: Bool) throws -> API.Command<T, T> where T: ParseObject {
if Parse.configuration.isAllowingCustomObjectIds
ignoringCustomObjectIdConfig: Bool,
batching: Bool = false) throws -> API.Command<T, T> where T: ParseObject {
if Parse.configuration.isRequiringCustomObjectIds
&& object.objectId == nil && !ignoringCustomObjectIdConfig {
throw ParseError(code: .missingObjectId, message: "objectId must not be nil")
}
if object.isSaved {
// MARK: Should be switched to "update" when server supports PATCH.
return try replace(object, original: data)
return try replace(object,
original: data)
}
return create(object)
}
Expand All @@ -420,7 +435,8 @@ internal extension API.Command {
mapper: mapper)
}

static func replace<T>(_ object: T, original data: Data?) throws -> API.Command<T, T> where T: ParseObject {
static func replace<T>(_ object: T,
original data: Data?) throws -> API.Command<T, T> where T: ParseObject {
guard object.objectId != nil else {
throw ParseError(code: .missingObjectId,
message: "objectId must not be nil")
Expand All @@ -446,7 +462,8 @@ internal extension API.Command {
mapper: mapper)
}

static func update<T>(_ object: T, original data: Data?) throws -> API.Command<T, T> where T: ParseObject {
static func update<T>(_ object: T,
original data: Data?) throws -> API.Command<T, T> where T: ParseObject {
guard object.objectId != nil else {
throw ParseError(code: .missingObjectId,
message: "objectId must not be nil")
Expand Down Expand Up @@ -504,8 +521,10 @@ internal extension API.Command where T: ParseObject {
guard let body = command.body else {
return nil
}
return API.Command<T, T>(method: command.method, path: .any(path),
body: body, mapper: command.mapper)
return API.Command<T, T>(method: command.method,
path: .any(path),
body: body,
mapper: command.mapper)
}

let mapper = { (data: Data) -> [Result<T, ParseError>] in
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/API/API+NonParseBodyCommand.swift
Expand Up @@ -170,7 +170,7 @@ internal extension API.NonParseBodyCommand {
}

let path = Parse.configuration.mountPath + objectable.endpoint.urlComponent
let encoded = try ParseCoding.parseEncoder().encode(object)
let encoded = try ParseCoding.parseEncoder().encode(object, batching: true)
let body = try ParseCoding.jsonDecoder().decode(AnyCodable.self, from: encoded)
return API.BatchCommand<AnyCodable, PointerType>(method: method,
path: .any(path),
Expand Down

0 comments on commit dc7666f

Please sign in to comment.