Skip to content

Commit

Permalink
feat: improve ParseRelation and ParseRole (#328)
Browse files Browse the repository at this point in the history
* feat: improve ParseRelation and ParseRole

* improve docs

* add tests and update playgrounds

* improve coverage
  • Loading branch information
cbaker6 authored Jan 24, 2022
1 parent c119033 commit ea631f5
Show file tree
Hide file tree
Showing 14 changed files with 717 additions and 204 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ __New features__
- Add DocC for SDK documentation ([#209](https://github.com/parse-community/Parse-Swift/pull/214)), thanks to [Corey Baker](https://github.com/cbaker6).

__Improvements__
- (Breaking Change) Make ParseRelation conform to Codable and add methods to make decoded stored
ParseRelations "usable". ParseObjects can now contain properties of ParseRelation<Self>.
In addition, ParseRelations can now be made from ParseObject pointers. For ParseRole, the
computed properties: users and roles, are now optional. The queryRoles property has been
changed to queryRoles() to improve the handling of thrown errors
([#328](https://github.com/parse-community/Parse-Swift/pull/328)), thanks to [Corey Baker](https://github.com/cbaker6).
- (Breaking Change) Change the following method parameter names: isUsingMongoDB -> usingMongoDB, isIgnoreCustomObjectIdConfig -> ignoringCustomObjectIdConfig, isUsingEQ -> usingEqComparator ([#321](https://github.com/parse-community/Parse-Swift/pull/321)), thanks to [Corey Baker](https://github.com/cbaker6).
- (Breaking Change) Change the following method parameter names: isUsingTransactions -> usingTransactions, isAllowingCustomObjectIds -> allowingCustomObjectIds, isUsingEqualQueryConstraint -> usingEqualQueryConstraint, isMigratingFromObjcSDK -> migratingFromObjcSDK, isDeletingKeychainIfNeeded -> deletingKeychainIfNeeded ([#323](https://github.com/parse-community/Parse-Swift/pull/323)), thanks to [Corey Baker](https://github.com/cbaker6).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct User: ParseUser {

//: Your custom keys.
var customKey: String?
var scores: ParseRelation<Self>?

//: Implement your own version of merge
func merge(with object: Self) throws -> Self {
Expand Down Expand Up @@ -144,7 +145,7 @@ if savedRole != nil {
do {
//: `ParseRoles` have `ParseRelations` that relate them either `ParseUser` and `ParseRole` objects.
//: The `ParseUser` relations can be accessed using `users`. We can then add `ParseUser`'s to the relation.
try savedRole!.users.add([User.current!]).save { result in
try savedRole!.users?.add([User.current!]).save { result in
switch result {
case .success(let saved):
print("The role saved successfully: \(saved)")
Expand All @@ -160,9 +161,9 @@ do {
}

//: To retrieve the users who are all Administrators, we need to query the relation.
let templateUser = User()
do {
try savedRole!.users.query(templateUser).find { result in
let query: Query<User>? = try savedRole!.users?.query()
query?.find { result in
switch result {
case .success(let relatedUsers):
print("""
Expand All @@ -180,7 +181,7 @@ do {

//: Of course, you can remove users from the roles as well.
do {
try savedRole!.users.remove([User.current!]).save { result in
try savedRole!.users?.remove([User.current!]).save { result in
switch result {
case .success(let saved):
print("The role removed successfully: \(saved)")
Expand Down Expand Up @@ -233,7 +234,7 @@ if savedRoleModerator != nil {
do {
//: `ParseRoles` have `ParseRelations` that relate them either `ParseUser` and `ParseRole` objects.
//: The `ParseUser` relations can be accessed using `users`. We can then add `ParseUser`'s to the relation.
try savedRole!.roles.add([savedRoleModerator!]).save { result in
try savedRole!.roles?.add([savedRoleModerator!]).save { result in
switch result {
case .success(let saved):
print("The role saved successfully: \(saved)")
Expand All @@ -249,22 +250,26 @@ do {

//: To retrieve the users who are all Administrators, we need to query the relation.
//: This time we will use a helper query from `ParseRole`.
savedRole!.queryRoles?.find { result in
switch result {
case .success(let relatedRoles):
print("""
The following roles are part of the
\"\(String(describing: savedRole!.name)) role: \(relatedRoles)
""")
do {
try savedRole!.queryRoles().find { result in
switch result {
case .success(let relatedRoles):
print("""
The following roles are part of the
\"\(String(describing: savedRole!.name)) role: \(relatedRoles)
""")

case .failure(let error):
print("Error saving role: \(error)")
case .failure(let error):
print("Error saving role: \(error)")
}
}
} catch {
print("Error: \(error)")
}

//: Of course, you can remove users from the roles as well.
do {
try savedRole!.roles.remove([savedRoleModerator!]).save { result in
try savedRole!.roles?.remove([savedRoleModerator!]).save { result in
switch result {
case .success(let saved):
print("The role removed successfully: \(saved)")
Expand Down Expand Up @@ -293,7 +298,10 @@ let score2 = GameScore(points: 57)
//: Make an array of all scores that were properly saved.
let scores = savedScores.compactMap { try? $0.get() }
do {
let newRelations = try relation.add("scores", objects: scores)
guard let newRelations = try relation?.add("scores", objects: scores) else {
print("Error: should have unwrapped relation")
return
}
newRelations.save { result in
switch result {
case .success(let saved):
Expand All @@ -312,11 +320,11 @@ let score2 = GameScore(points: 57)
}
}

let specificRelation = User.current!.relation("scores", child: score1)
//: You can also do
// let specificRelation = User.current!.relation("scores", className: "GameScore")
do {
try specificRelation.query(score1).find { result in
let specificRelation = try User.current!.relation("scores", child: score1)
try (specificRelation.query() as Query<GameScore>).find { result in
switch result {
case .success(let scores):
print("Found related scores: \(scores)")
Expand All @@ -328,21 +336,47 @@ do {
print(error)
}

//: In addition, you can leverage the child to find scores related to the parent.
do {
//: You can also leverage the child to find scores related to the parent.
try score1.relation.query("scores", parent: User.current!).find { result in
try GameScore.queryRelations("scores", parent: User.current!).find { result in
switch result {
case .success(let scores):
print("Found related scores: \(scores)")
print("Found related scores from child: \(scores)")
case .failure(let error):
print("Error finding scores: \(error)")
print("Error finding scores from child: \(error)")
}
}
} catch {
print(error)
}

//: Example: try relation.remove(<#T##key: String##String#>, objects: <#T##[ParseObject]#>)
//: Now we will see how to use the stored `ParseRelation on` property in User to create query
//: all of the relations to `scores`.
var currentUser: User?
do {
//: Fetch the updated user since the previous relations were created on the server.
currentUser = try User.current?.fetch()
print("Updated current user with relation: \(String(describing: currentUser))")
} catch {
print("\(error.localizedDescription)")
}

do {
if let usableStoredRelation = try currentUser?.relation(currentUser?.scores, key: "scores") {
try (usableStoredRelation.query() as Query<GameScore>).find { result in
switch result {
case .success(let scores):
print("Found related scores from stored ParseRelation: \(scores)")
case .failure(let error):
print("Error finding scores from stored ParseRelation: \(error)")
}
}
} else {
print("Error: should unwrapped relation")
}
} catch {
print("\(error.localizedDescription)")
}

PlaygroundPage.current.finishExecution()
//: [Next](@next)
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ public struct ParseApple<AuthenticatedUser: ParseUser>: ParseAuthentication {
return true
}
}

public static var __type: String { // swiftlint:disable:this identifier_name
"apple"
}

public init() { }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ public struct ParseLDAP<AuthenticatedUser: ParseUser>: ParseAuthentication {
return true
}
}

public static var __type: String { // swiftlint:disable:this identifier_name
"ldap"
}

public init() { }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,11 @@ public struct ParseTwitter<AuthenticatedUser: ParseUser>: ParseAuthentication {
return true
}
}

public static var __type: String { // swiftlint:disable:this identifier_name
"twitter"
}

public init() { }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ public struct ParseAnonymous<AuthenticatedUser: ParseUser>: ParseAuthentication
[AuthenticationKeys.id.rawValue: UUID().uuidString.lowercased()]
}
}

public static var __type: String { // swiftlint:disable:this identifier_name
"anonymous"
}

public init() { }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ import Combine
*/
public protocol ParseAuthentication: Codable {
associatedtype AuthenticatedUser: ParseUser
init()

/// The type of authentication.
static var __type: String { get } // swiftlint:disable:this identifier_name

/// Returns `true` if the *current* user is linked to the respective authentication type.
var isLinked: Bool { get }

/// The default initializer for this authentication type.
init()

/**
Login a `ParseUser` *asynchronously* using the respective authentication type.
- parameter authData: The authData for the respective authentication type.
Expand Down
22 changes: 18 additions & 4 deletions Sources/ParseSwift/Objects/ParseObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import Foundation

// swiftlint:disable line_length

/**
Objects that conform to the `ParseObject` protocol have a local representation of data persisted to the Parse cloud.
This is the main protocol that is used to interact with objects in your app.
Expand All @@ -25,8 +27,8 @@ import Foundation
- important: It is required that all added properties be optional properties so they can eventually be used as
Parse `Pointer`'s. If a developer really wants to have a required key, they should require it on the server-side or
create methods to check the respective properties on the client-side before saving objects. See
[here](https://github.com/parse-community/Parse-Swift/issues/157#issuecomment-858671025)
for more information.
[here](https://github.com/parse-community/Parse-Swift/pull/315#issuecomment-1014701003)
for more information on the reasons why. See the [Playgrounds](https://github.com/parse-community/Parse-Swift/blob/c119033f44b91570997ad24f7b4b5af8e4d47b64/ParseSwift.playground/Pages/1%20-%20Your%20first%20Object.xcplaygroundpage/Contents.swift#L32-L66) for an example.
- important: To take advantage of `mergeable`, the developer should implement the `merge` method in every
`ParseObject`.
- warning: If you plan to use "reference types" (classes), you are using at your risk as this SDK is not designed
Expand Down Expand Up @@ -65,6 +67,20 @@ public protocol ParseObject: Objectable,
*/
var mergeable: Self { get }

/**
The default initializer to ensure all `ParseObject`'s can be encoded/decoded properly.
- important: The compiler will give you this initialzer for free
([memberwise initializer](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html))
as long as you declare all properties as **optional** (see **Warning** section) and you declare all other initializers in
an **extension**. See the [Playgrounds](https://github.com/parse-community/Parse-Swift/blob/c119033f44b91570997ad24f7b4b5af8e4d47b64/ParseSwift.playground/Pages/1%20-%20Your%20first%20Object.xcplaygroundpage/Contents.swift#L32-L66) for an example.
- warning: It is required that all added properties be optional properties so they can eventually be used as
Parse `Pointer`'s. If a developer really wants to have a required key, they should require it on the server-side or
create methods to check the respective properties on the client-side before saving objects. See
[here](https://github.com/parse-community/Parse-Swift/pull/315#issuecomment-1014701003)
for more information.
*/
init()

/**
Determines if a `KeyPath` of the current `ParseObject` should be restored
by comparing it to another `ParseObject`.
Expand Down Expand Up @@ -125,8 +141,6 @@ public protocol ParseObject: Objectable,
use `shouldRestoreKey` to compare key modifications between objects.
*/
func merge(with object: Self) throws -> Self

init()
}

// MARK: Default Implementations
Expand Down
Loading

0 comments on commit ea631f5

Please sign in to comment.