Skip to content
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

feat: improve ParseRelation and ParseRole #328

Merged
merged 4 commits into from
Jan 24, 2022

Conversation

cbaker6
Copy link
Contributor

@cbaker6 cbaker6 commented Jan 23, 2022

New Pull Request Checklist

Issue Description

Currently a ParseRelation isn't Decodable. This is because decoding a ParseRelation doesn't yield a "usable" ParseRelation out-of-the-box.

See discussion here: #294 (comment)

Related issue: #n/a

Approach

Make ParseRelation conform to Codable and add methods to make decoded stored ParseRelations "usable". ParseObjects can now contain properties of ParseRelation. In addition, ParseRelations can now be made from ParseObject pointers. Also removed the need to specify the child object for a ParseRelation query as this can be type inferred by the developer.

Breaking Changes to both ParseRelation and ParseRole. 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

Note the improvements of this PR are possible because of the requirements made in 4.0.0 which require all ParseObject's to have an init(); giving flexibility back to the SDK by enabling it to initialize ParseObject's internally. For example, previously when creating a ParseObject, the SDK can only convert: ParseObject->Pointer<ParseObject>, but not reverse this conversion. Since an object can be initialized, the SDK can now reverse the aforementioned conversion: Pointer<ParseObject>->ParseObject. See more here: #315 (comment)

The best way still to use ParseRelation is by using computed properties which is available in older and newer versions of the Swift SDK. An example of creating ParseRelation's that are computed properties (these are usable out-of-the-box) are the users and roles in ParseRole:

>= 4.0.0:

var users: ParseRelation<Self>? {
try? ParseRelation(parent: self, key: "users", className: RoleUser.className)
}
var roles: ParseRelation<Self>? {
try? ParseRelation(parent: self, key: "roles", className: Self.className)
}

< 4.0.0:

/**
Gets the `ParseRelation` for the `ParseUser` objects that are direct children of this role.
These users are granted any privileges that this role has been granted
(e.g. read or write access through `ParseACL`s). You can add or remove users from
the role through this relation.
*/
var users: ParseRelation<Self> {
ParseRelation(parent: self, key: "users", className: "_User")
}
/**
Gets the `ParseRelation` for the `ParseRole` objects that are direct children of this role.
These roles' users are granted any privileges that this role has been granted
(e.g. read or write access through `ParseACL`s). You can add or remove child roles
from this role through this relation.
*/
var roles: ParseRelation<Self> {
ParseRelation(parent: self, key: "roles", className: "_Role")
}

and then querying the relations by using the methods available on the returned computed property.

For stored ParseRelation's (those decoded from the ParseServer), the "computed property" mentioned earlier still works the best, assuming you now how to construct the ParseRelation without the server. If you need to make a stored ParseRelation "usable", the following methods have been added to all ParseObect's in this PR:

    /**
     Establish a relation based on a stored relation.
     - parameter relation: The stored relation property.
     - parameter key: The key for the relation.
     - parameter with: The parent `ParseObject` Pointer of the `ParseRelation`.
     - returns: A usable `ParseRelation` based on the stored relation property.
     */
    static func relation<T: ParseObject>(_ relation: ParseRelation<T>?,
                                         key: String,
                                         with parent: Pointer<T>) throws -> ParseRelation<T>

    /**
     Establish a relation based on a stored relation.
     - parameter relation: The stored relation property.
     - parameter key: The key for the relation.
     - parameter with: The parent `ParseObject` of the `ParseRelation`.
     - returns: A usable `ParseRelation` based on the stored relation property.
     */
    static func relation<T: ParseObject>(_ relation: ParseRelation<T>?,
                                         key: String,
                                         with parent: T) throws -> ParseRelation<T>

    /**
     Establish a relation based on a stored relation with this `ParseObject` as the parent.
     - parameter relation: The stored relation property.
     - parameter key: The key for the relation.
     - returns: A usable `ParseRelation` based on the stored relation property.
     */
    func relation(_ relation: ParseRelation<Self>?,
                  key: String) throws -> ParseRelation<Self>

    /**
     Establish a relation based on a stored relation.
     - parameter relation: The stored relation property.
     - parameter key: The key for the relation.
     - parameter with: The parent `ParseObject` of the `ParseRelation`.
     - returns: A usable `ParseRelation` based on the stored relation property.
     */
    func relation<T: ParseObject>(_ relation: ParseRelation<T>?,
                                  key: String,
                                  with parent: T) throws -> ParseRelation<T>

    /**
     Establish a relation based on a stored relation.
     - parameter relation: The stored relation property.
     - parameter key: The key for the relation.
     - parameter with: The parent `ParseObject` Pointer of the `ParseRelation`.
     - returns: A usable `ParseRelation` based on the stored relation property.
     */
    func relation<T: ParseObject>(_ relation: ParseRelation<T>?,
                                  key: String,
                                  with parent: Pointer<T>) throws -> ParseRelation<T>

An example of using one of the new methods is in the Playgrounds. They are all similar. Assuming you have the var scores: ParseRelation<Self>? as one of your stored properties of User:

struct User: ParseUser {
//: These are required by `ParseObject`.
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var originalData: Data?
//: These are required by `ParseUser`.
var username: String?
var email: String?
var emailVerified: Bool?
var password: String?
var authData: [String: [String: String]?]?
//: Your custom keys.
var customKey: String?
var scores: ParseRelation<Self>?
//: Implement your own version of merge
func merge(with object: Self) throws -> Self {
var updated = try mergeParse(with: object)
if updated.shouldRestoreKey(\.customKey,
original: object) {
updated.customKey = object.customKey
}
return updated
}
}

You can make scores usable by doing the following (it's actually a lot cleaner if you use guard, but Playgrounds only allows guard inside of closures because of it's structure:

//: 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)")
}

TODOs before merging

  • Add tests
  • Add entry to changelog
  • Add changes to documentation (guides, repository pages, in-code descriptions)

@parse-github-assistant
Copy link

parse-github-assistant bot commented Jan 23, 2022

Thanks for opening this pull request!

  • 🎉 We are excited about your hands-on contribution!

@codecov
Copy link

codecov bot commented Jan 23, 2022

Codecov Report

Merging #328 (8af4e1a) into main (7cbf97b) will increase coverage by 0.08%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #328      +/-   ##
==========================================
+ Coverage   85.02%   85.11%   +0.08%     
==========================================
  Files         114      114              
  Lines       12048    12116      +68     
==========================================
+ Hits        10244    10312      +68     
  Misses       1804     1804              
Impacted Files Coverage Δ
...thentication/3rd Party/ParseApple/ParseApple.swift 82.50% <ø> (ø)
...Authentication/3rd Party/ParseLDAP/ParseLDAP.swift 100.00% <ø> (ø)
...tication/3rd Party/ParseTwitter/ParseTwitter.swift 100.00% <ø> (ø)
...Swift/Authentication/Internal/ParseAnonymous.swift 100.00% <ø> (ø)
...Authentication/Protocols/ParseAuthentication.swift 72.44% <ø> (ø)
Sources/ParseSwift/Objects/ParseObject.swift 82.29% <ø> (ø)
Sources/ParseSwift/Objects/ParseRole.swift 100.00% <100.00%> (ø)
Sources/ParseSwift/Types/ParseACL.swift 87.65% <100.00%> (ø)
Sources/ParseSwift/Types/ParseRelation.swift 97.40% <100.00%> (+1.03%) ⬆️
Sources/ParseSwift/Types/Pointer.swift 96.82% <100.00%> (+0.27%) ⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7cbf97b...8af4e1a. Read the comment docs.

@cbaker6
Copy link
Contributor Author

cbaker6 commented Jan 23, 2022

@jaysonng you can track this PR for adjustments. Note that though this will improve the ability of a ParseRelation you will still need to create a computed property or use the added methods in this PR to create a "usable" ParseRelation.

@cbaker6 cbaker6 merged commit ea631f5 into parse-community:main Jan 24, 2022
@cbaker6 cbaker6 deleted the relation branch January 24, 2022 01:14
@jaysonng
Copy link
Contributor

@jaysonng you can track this PR for adjustments. Note that though this will improve the ability of a ParseRelation you will still need to create a computed property or use the added methods in this PR to create a "usable" ParseRelation.

yessir! I've been working with ParseRelation the past couple of days and this PR will be a big help. You rock!

@jaysonng
Copy link
Contributor

jaysonng commented Jan 24, 2022

Linking this current example as the earlier one posted above links to an older revision of ParseRole.

Current way to use computed property ParseRelation needs try?

var users: ParseRelation<Self>? {
try? ParseRelation(parent: self, key: "users", className: RoleUser.className)
}
var roles: ParseRelation<Self>? {
try? ParseRelation(parent: self, key: "roles", className: Self.className)
}

If you can update your message above @cbaker6, I'll just remove this one as later.

thanks,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants