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 by adding more methods #294

Merged
merged 4 commits into from
Dec 2, 2021
Merged

feat: improve ParseRelation by adding more methods #294

merged 4 commits into from
Dec 2, 2021

Conversation

cbaker6
Copy link
Contributor

@cbaker6 cbaker6 commented Dec 2, 2021

New Pull Request Checklist

Issue Description

ParseRelation can use some additional methods to make it easier to create and query relations.

Related issue: #n/a

Approach

Add the following:

  • ParseRelation: func add<U>(_ objects: [U]) throws -> ParseOperation<T> where U: ParseObject
  • ParseRelation: func remove<U>(_ objects: [U]) throws -> ParseOperation<T> where U: ParseObject
  • ParseRelation: func query<U>(_ key: String, parent: U) throws -> Query<T> where U: ParseObject
  • ParseRelation: func query<U>(_ key: String, child: U) throws -> Query<U> where U: ParseObject
  • ParseRelation Playground save/query example
  • QueryConstraint: func related(key: String) -> QueryConstraint
  • QueryConstraint: func related <T>(object: T) throws -> QueryConstraint where T: ParseObject
  • QueryConstraint: func related <T>(object: Pointer<T>) -> QueryConstraint where T: ParseObject

Removed the following:

  • optional targets/parents from ParseOperation and ParseRelation to get rid of unnecessary unwrapping

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 Dec 2, 2021

Thanks for opening this pull request!

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

@codecov
Copy link

codecov bot commented Dec 2, 2021

Codecov Report

Merging #294 (15dafa1) into main (4734212) will increase coverage by 0.34%.
The diff coverage is 75.86%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #294      +/-   ##
==========================================
+ Coverage   82.36%   82.70%   +0.34%     
==========================================
  Files         104      104              
  Lines       10659    10638      -21     
==========================================
+ Hits         8779     8798      +19     
+ Misses       1880     1840      -40     
Impacted Files Coverage Δ
Sources/ParseSwift/Types/Query.swift 92.46% <0.00%> (-1.40%) ⬇️
Sources/ParseSwift/Types/ParseRelation.swift 96.74% <96.66%> (+16.39%) ⬆️
Sources/ParseSwift/Types/ParseOperation.swift 94.32% <100.00%> (+13.43%) ⬆️
Sources/ParseSwift/Objects/ParseUser.swift 79.97% <0.00%> (-0.13%) ⬇️
...urces/ParseSwift/API/API+NonParseBodyCommand.swift 75.79% <0.00%> (+0.31%) ⬆️
...Authentication/Protocols/ParseAuthentication.swift 72.16% <0.00%> (+0.88%) ⬆️

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 4734212...15dafa1. Read the comment docs.

@cbaker6 cbaker6 merged commit 1352a83 into parse-community:main Dec 2, 2021
@cbaker6 cbaker6 deleted the fixRelation branch December 2, 2021 21:24
@jaysonng
Copy link
Contributor

jaysonng commented Jan 11, 2022

I was wondering what the reason was for changing ParseRelation protocol from Codable to Encodable? (Removing Decodable?)

I'm asking because I now have to implement custom init(from:) for my ParseObjects which has a ParseRelation object nested inside them, when I didn't have to before.

Should I not be decoding relations objects?

For context, I add Codable to an object of mine for saving in UserDefaults.

also, if this is not the right place to ask this, I can re-ask this in the forums.

@cbaker6
Copy link
Contributor Author

cbaker6 commented Jan 11, 2022

What’s the usecase for decoding a ParseRelation? You currently can’t fetch this type from the server, why would you fetch it from storage?

@jaysonng
Copy link
Contributor

What’s the usecase for decoding a ParseRelation? You currently can’t fetch this type from the server, why would you fetch it from storage?

In may case, I store a Location object in UserDefaults. (so I have a default Location loaded up in the app without having to wait and find the device current location or have the user select a location.)

The Location object has 2 ParseRelation properties to 2 other ParseObjects -> Tags and Images.

I can do away saving the images in UserDefaults as they will be huge, but Tags might be something I want stored as well.

@cbaker6
Copy link
Contributor Author

cbaker6 commented Jan 11, 2022

A ParseRelation type isn't intended to be decoded. In fact, the only keys that have ever been encoded are:

enum CodingKeys: String, CodingKey {
case className
case __type // swiftlint:disable:this identifier_name
}

This means that even if you decoded a ParseRelation in the past, your relation wasn't usable unless you set the parent property manually. If you want to decode what you have stored in your UserDefaults (and get a working ParseRelation), you can create a simple struct with the same keys that are being encoded above:

struct StoredParseRelation: Decodable {
    var className: String

    // Call this function to get a working ParseRelation on an instance of the struct.
    func createParseRelation<T: ParseObject>(_ parent: T, key: String) -> ParseRelation<T> {
        ParseRelation(parent: parent, key: key, className: className)
    }
}

@jaysonng
Copy link
Contributor

I'll rework my code to get this working. and no, I haven't been able to get to the part of actually checking the ParseRelation objects in my ParseObject before but all these errors popped up when I updated my ParseSwift to 3.1.0 so I had to tackle them now. :)

Thank you once again! I think I understand this more now.

@jaysonng
Copy link
Contributor

jaysonng commented Jan 23, 2022

@cbaker6 , a follow up question,

Should we never place ParseRelation properties in structs?

So whenever a struct that has a ParseRelation, I get the "does not conform to protocol 'Decodable' error.

Screen Shot 2022-01-23 at 5 43 39 PM

It seems like we don't really need to have ParseRelation properties in the struct do we? (Even if the relation is in the Parse database). Was the intention of ParseRelation not be part of the struct object itself?

From what I understand, I don't access that property at all when I want to query relations anyway right? Otherwise I'd need to be implementing init(from:) on all objects with ParseRelations.

Thank you very much!

@cbaker6
Copy link
Contributor Author

cbaker6 commented Jan 23, 2022

It seems like we don't really need to have ParseRelation properties in the struct do we? (Even if the relation is in the Parse database). Was the intention of ParseRelation not be part of the struct object itself?

From what I understand, I don't access that property at all when I want to query relations anyway right? Otherwise I'd need to be implementing init(from:) on all objects with ParseRelations.

An encoded ParseRelation doesn’t have enough info to be useful when decoding. Instead, you should be saving and querying the relation like the example in the Playgrounds:

//: Using this relation, you can create one-to-many relationships with other `ParseObjecs`,
//: similar to `users` and `roles`.
//: All `ParseObject`s have a `ParseRelation` attribute that be used on instances.
//: For example, the User has:
var relation = User.current!.relation
let score1 = GameScore(points: 53)
let score2 = GameScore(points: 57)
//: Add new child relationships.
[score1, score2].saveAll { result in
switch result {
case .success(let savedScores):
//: 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)
newRelations.save { result in
switch result {
case .success(let saved):
print("The relation saved successfully: \(saved)")
print("Check \"points\" field in your \"_User\" class in Parse Dashboard.")
case .failure(let error):
print("Error saving role: \(error)")
}
}
} catch {
print(error)
}
case .failure(let error):
print("Couldn't save scores. \(error)")
}
}
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
switch result {
case .success(let scores):
print("Found related scores: \(scores)")
case .failure(let error):
print("Error finding scores: \(error)")
}
}
} catch {
print(error)
}
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
switch result {
case .success(let scores):
print("Found related scores: \(scores)")
case .failure(let error):
print("Error finding scores: \(error)")
}
}
} catch {
print(error)
}

I’ll look into adding ParseRelation as decodable for version 4.0.0, but my guess is you will still need to pass the string name of the property and access it similar to the Playgrounds as it can’t be used by itself.

You typically want to create computed properties for your ParseObject's that have ParseRelation properties as these are usable out of the box. An example of this are the users and roles getters on ParseRole:

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

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.

None yet

2 participants