diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d5f45a5..24551e7e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ __New features__ - Add modifiers to containsString, hasPrefix, hasSuffix ([#85](https://github.com/parse-community/Parse-Swift/pull/85)), thanks to [Corey Baker](https://github.com/cbaker6). +__Improvements__ +- Can use a variadic version of exclude. Added examples of select and exclude query in playgrounds ([#88](https://github.com/parse-community/Parse-Swift/pull/88)), thanks to [Corey Baker](https://github.com/cbaker6). + ### 1.1.6 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.5...1.1.6) diff --git a/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift index 0d0f709b8..88f450996 100644 --- a/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift @@ -14,14 +14,22 @@ struct GameScore: ParseObject { var ACL: ParseACL? var score: Int? + var oldScore: Int? } var score = GameScore() score.score = 200 -try score.save() +score.oldScore = 10 +do { + try score.save() +} catch { + print(error) +} let afterDate = Date().addingTimeInterval(-300) -var query = GameScore.query("score" > 100, "createdAt" > afterDate) +var query = GameScore.query("score" > 50, + "createdAt" > afterDate) + .order([.descending("score")]) // Query asynchronously (preferred way) - Performs work on background // queue and returns to designated on designated callbackQueue. @@ -68,6 +76,36 @@ query.first { results in } } +let querySelect = query.select("score") +querySelect.first { results in + switch results { + case .success(let score): + + guard score.objectId != nil, + let createdAt = score.createdAt else { fatalError() } + assert(createdAt.timeIntervalSince1970 > afterDate.timeIntervalSince1970, "date should be ok") + print("Found score using select: \(score)") + + case .failure(let error): + assertionFailure("Error querying: \(error)") + } +} + +let queryExclude = query.exclude("score") +queryExclude.first { results in + switch results { + case .success(let score): + + guard score.objectId != nil, + let createdAt = score.createdAt else { fatalError() } + assert(createdAt.timeIntervalSince1970 > afterDate.timeIntervalSince1970, "date should be ok") + print("Found score using exclude: \(score)") + + case .failure(let error): + assertionFailure("Error querying: \(error)") + } +} + PlaygroundPage.current.finishExecution() //: [Next](@next) diff --git a/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift index 422af3be6..6cd3d0f98 100644 --- a/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift @@ -100,7 +100,7 @@ User.login(username: "hello", password: "world") { results in User.current?.fetch(includeKeys: ["score"]) { result in switch result { case .success: - print("Successfully fetched user with score key: \(User.current)") + print("Successfully fetched user with score key: \(String(describing: User.current))") case .failure(let error): print("Error fetching score: \(error)") } @@ -111,7 +111,7 @@ User.current?.fetch(includeKeys: ["score"]) { result in User.current?.fetch(includeKeys: ["*"]) { result in switch result { case .success: - print("Successfully fetched user with all keys: \(User.current)") + print("Successfully fetched user with all keys: \(String(describing: User.current))") case .failure(let error): print("Error fetching score: \(error)") } diff --git a/Sources/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift index f6dc436af..809c22c71 100644 --- a/Sources/ParseSwift/API/API+Commands.swift +++ b/Sources/ParseSwift/API/API+Commands.swift @@ -389,8 +389,7 @@ internal extension API.Command { var params: [String: String]? if let includeParams = include { - let joined = includeParams.joined(separator: ",") - params = ["include": joined] + params = ["include": "\(includeParams)"] } return API.Command( diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index dfbd9512c..61a646838 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -413,8 +413,7 @@ extension ParseInstallation { var params: [String: String]? if let includeParams = include { - let joined = includeParams.joined(separator: ",") - params = ["include": joined] + params = ["include": "\(includeParams)"] } return API.Command(method: .GET, diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index 2d394d3f2..03cbbf414 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -724,8 +724,7 @@ extension ParseUser { var params: [String: String]? if let includeParams = include { - let joined = includeParams.joined(separator: ",") - params = ["include": joined] + params = ["include": "\(includeParams)"] } return API.Command(method: .GET, diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index 6f516f7b7..e0be2eb7a 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -697,9 +697,21 @@ public struct Query: Encodable, Equatable where T: ParseObject { /** Exclude specific keys for a `ParseObject`. Default is to nil. - - parameter keys: An arrays of keys to exclude. + - parameter keys: A variadic list of keys include in the result. + - warning: Requires Parse Server > 4.5.0 + */ + public func exclude(_ keys: String...) -> Query { + var mutableQuery = self + mutableQuery.excludeKeys = keys + return mutableQuery + } + + /** + Exclude specific keys for a `ParseObject`. Default is to nil. + - parameter keys: An array of keys to exclude. + - warning: Requires Parse Server > 4.5.0 */ - public func exclude(_ keys: [String]?) -> Query { + public func exclude(_ keys: [String]) -> Query { var mutableQuery = self mutableQuery.excludeKeys = keys return mutableQuery @@ -709,6 +721,7 @@ public struct Query: Encodable, Equatable where T: ParseObject { Make the query restrict the fields of the returned `ParseObject`s to include only the provided keys. If this is called multiple times, then all of the keys specified in each of the calls will be included. - parameter keys: A variadic list of keys include in the result. + - warning: Requires Parse Server > 4.5.0 */ public func select(_ keys: String...) -> Query { var mutableQuery = self @@ -720,6 +733,7 @@ public struct Query: Encodable, Equatable where T: ParseObject { Make the query restrict the fields of the returned `ParseObject`s to include only the provided keys. If this is called multiple times, then all of the keys specified in each of the calls will be included. - parameter keys: An array of keys to include in the result. + - warning: Requires Parse Server > 4.5.0 */ public func select(_ keys: [String]) -> Query { var mutableQuery = self @@ -1100,7 +1114,7 @@ extension Query { func findCommand() -> API.NonParseBodyCommand, [ResultType]> { let query = self - return API.NonParseBodyCommand(method: .POST, path: endpoint, body: query) { + return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results } } @@ -1108,7 +1122,7 @@ extension Query { func firstCommand() -> API.NonParseBodyCommand, ResultType?> { var query = self query.limit = 1 - return API.NonParseBodyCommand(method: .POST, path: endpoint, body: query) { + return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results.first } } @@ -1117,7 +1131,7 @@ extension Query { var query = self query.limit = 1 query.isCount = true - return API.NonParseBodyCommand(method: .POST, path: endpoint, body: query) { + return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).count ?? 0 } } @@ -1126,7 +1140,7 @@ extension Query { var query = self query.explain = explain query.hint = hint - return API.NonParseBodyCommand(method: .POST, path: endpoint, body: query) { + return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { if let results = try JSONDecoder().decode(AnyResultsResponse.self, from: $0).results { return results } @@ -1139,7 +1153,7 @@ extension Query { query.limit = 1 query.explain = explain query.hint = hint - return API.NonParseBodyCommand(method: .POST, path: endpoint, body: query) { + return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { if let results = try JSONDecoder().decode(AnyResultsResponse.self, from: $0).results { return results } @@ -1153,7 +1167,7 @@ extension Query { query.isCount = true query.explain = explain query.hint = hint - return API.NonParseBodyCommand(method: .POST, path: endpoint, body: query) { + return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { if let results = try JSONDecoder().decode(AnyResultsResponse.self, from: $0).results { return results } diff --git a/Tests/ParseSwiftTests/ParseInstallationTests.swift b/Tests/ParseSwiftTests/ParseInstallationTests.swift index df6599d36..7826ab017 100644 --- a/Tests/ParseSwiftTests/ParseInstallationTests.swift +++ b/Tests/ParseSwiftTests/ParseInstallationTests.swift @@ -468,7 +468,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l var installation = Installation() let objectId = "yarr" installation.objectId = objectId - let includeExpected = ["include": "yolo,test"] + let includeExpected = ["include": "[\"yolo\", \"test\"]"] do { let command = try installation.fetchCommand(include: ["yolo", "test"]) XCTAssertNotNil(command) @@ -477,7 +477,8 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l XCTAssertEqual(command.params, includeExpected) XCTAssertNil(command.body) - guard let urlExpected = URL(string: "http://localhost:1337/1/installations/yarr?include=yolo,test") else { + // swiftlint:disable:next line_length + guard let urlExpected = URL(string: "http://localhost:1337/1/installations/yarr?include=%5B%22yolo%22,%20%22test%22%5D") else { XCTFail("Should have unwrapped") return } diff --git a/Tests/ParseSwiftTests/ParseObjectTests.swift b/Tests/ParseSwiftTests/ParseObjectTests.swift index f49c2f21e..7db966848 100644 --- a/Tests/ParseSwiftTests/ParseObjectTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectTests.swift @@ -244,7 +244,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length let className = score.className let objectId = "yarr" score.objectId = objectId - let includeExpected = ["include": "yolo,test"] + let includeExpected = ["include": "[\"yolo\", \"test\"]"] do { let command = try score.fetchCommand(include: ["yolo", "test"]) XCTAssertNotNil(command) @@ -255,7 +255,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertNil(command.data) // swiftlint:disable:next line_length - guard let urlExpected = URL(string: "http://localhost:1337/1/classes/GameScore/yarr?include=yolo,test") else { + guard let urlExpected = URL(string: "http://localhost:1337/1/classes/GameScore/yarr?include=%5B%22yolo%22,%20%22test%22%5D") else { XCTFail("Should have unwrapped") return } diff --git a/Tests/ParseSwiftTests/ParseQueryTests.swift b/Tests/ParseSwiftTests/ParseQueryTests.swift index 179cb4965..fc91a9b71 100644 --- a/Tests/ParseSwiftTests/ParseQueryTests.swift +++ b/Tests/ParseSwiftTests/ParseQueryTests.swift @@ -162,26 +162,61 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertEqual(query2.include, ["*"]) } - func testExcludeKeys() { + func testExcludeKeys() throws { let query = GameScore.query() XCTAssertNil(query.excludeKeys) - let query2 = GameScore.query().exclude(["yolo"]) + var query2 = GameScore.query().exclude("yolo") XCTAssertEqual(query2.excludeKeys, ["yolo"]) XCTAssertEqual(query2.excludeKeys, ["yolo"]) + let encoded = try ParseCoding.jsonEncoder().encode(query2) + let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) + guard let decodedKeys = decodedDictionary["excludeKeys"], + let decodedValues = decodedKeys.value as? [String] else { + XCTFail("Should have casted") + return + } + XCTAssertEqual(decodedValues, ["yolo"]) + + query2 = GameScore.query().exclude(["yolo", "wow"]) + XCTAssertEqual(query2.excludeKeys, ["yolo", "wow"]) + XCTAssertEqual(query2.excludeKeys, ["yolo", "wow"]) + let encoded2 = try ParseCoding.jsonEncoder().encode(query2) + let decodedDictionary2 = try JSONDecoder().decode([String: AnyCodable].self, from: encoded2) + guard let decodedKeys2 = decodedDictionary2["excludeKeys"], + let decodedValues2 = decodedKeys2.value as? [String] else { + XCTFail("Should have casted") + return + } + XCTAssertEqual(decodedValues2, ["yolo", "wow"]) } - func testSelectKeys() { + func testSelectKeys() throws { let query = GameScore.query() XCTAssertNil(query.keys) + var query2 = GameScore.query().select("yolo") XCTAssertEqual(query2.keys?.count, 1) XCTAssertEqual(query2.keys?.first, "yolo") - query2 = query2.select("yolo", "wow") + let encoded = try ParseCoding.jsonEncoder().encode(query2) + let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) + guard let decodedKeys = decodedDictionary["keys"], + let decodedValues = decodedKeys.value as? [String] else { + XCTFail("Should have casted") + return + } + XCTAssertEqual(decodedValues, ["yolo"]) + + query2 = query2.select(["yolo", "wow"]) XCTAssertEqual(query2.keys?.count, 2) XCTAssertEqual(query2.keys, ["yolo", "wow"]) - query2 = query2.select(["yolo"]) - XCTAssertEqual(query2.keys?.count, 1) - XCTAssertEqual(query2.keys, ["yolo"]) + let encoded2 = try ParseCoding.jsonEncoder().encode(query2) + let decodedDictionary2 = try JSONDecoder().decode([String: AnyCodable].self, from: encoded2) + guard let decodedKeys2 = decodedDictionary2["keys"], + let decodedValues2 = decodedKeys2.value as? [String] else { + XCTFail("Should have casted") + return + } + XCTAssertEqual(decodedValues2, ["yolo", "wow"]) } func testAddingConstraints() { diff --git a/Tests/ParseSwiftTests/ParseUserTests.swift b/Tests/ParseSwiftTests/ParseUserTests.swift index e35d91c68..aee6f831e 100644 --- a/Tests/ParseSwiftTests/ParseUserTests.swift +++ b/Tests/ParseSwiftTests/ParseUserTests.swift @@ -109,7 +109,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length var user = User() let objectId = "yarr" user.objectId = objectId - let includeExpected = ["include": "yolo,test"] + let includeExpected = ["include": "[\"yolo\", \"test\"]"] do { let command = try user.fetchCommand(include: ["yolo", "test"]) XCTAssertNotNil(command) @@ -119,7 +119,8 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertNil(command.body) XCTAssertNil(command.data) - guard let urlExpected = URL(string: "http://localhost:1337/1/users/yarr?include=yolo,test") else { + // swiftlint:disable:next line_length + guard let urlExpected = URL(string: "http://localhost:1337/1/users/yarr?include=%5B%22yolo%22,%20%22test%22%5D") else { XCTFail("Should have unwrapped") return }