Skip to content

fix: updating saved objects using saveAll #423

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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)
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading