Skip to content

Commit

Permalink
Add Roles and Relations (#54)
Browse files Browse the repository at this point in the history
* WIP

* Add role

* Add Operation tests, some renaming

* name change

* Add all operation tests and playgrounds example

* Add/Remove Relation operation

* merge and update

* Add Query "distinct"

* Make query thread safe since it's a reference type.
- Added aggregate to query. This needs to be tested on a real server.

* remove old file

* more compatibility for linux build

* use new cache for jazzy

* initial role

* Clean up

* Update naming conventions

* Update ParseSwift initialization/

* Improved ParseACL

* remove renamed files

* Working Role and Relation

* Make Query a value type instead of reference type

* Remove ParseRelation.save, ParseOperations can handle all saves for Relations.

* More ParseRelation tests

* Add ParseRole tests

* Added Relation query tests. Fixed some bugs in batch object test cases causing random failures.

* More tests

* Add complete ParseRole tutorial to Playgrounds.

* Finished Playground examples.

* Add missing query constraints with tests.

* Apply suggestions from code review

Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com>

Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com>
  • Loading branch information
cbaker6 and TomWFox committed Jan 21, 2021
1 parent 5d15b43 commit 86d4eb7
Show file tree
Hide file tree
Showing 56 changed files with 3,387 additions and 560 deletions.
4 changes: 2 additions & 2 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ coverage:
status:
patch:
default:
target: 67
target: auto
changes: false
project:
default:
target: 74
target: 76
comment:
require_changes: true
9 changes: 2 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ on:
branches: '*'
env:
CI_XCODE_VER: '/Applications/Xcode_11.7.app/Contents/Developer'
CI_XCODE_VER_12: '/Applications/Xcode_12.2.app/Contents/Developer'

jobs:
xcode-test-ios:
Expand Down Expand Up @@ -98,19 +97,15 @@ jobs:
uses: actions/cache@v2
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
key: ${{ runner.os }}-gem-v1-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-
${{ runner.os }}-gem-v1
- name: Install Bundle
run: |
bundle config path vendor/bundle
bundle install
env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER_12 }}
- name: Create Jazzy Docs
run: ./Scripts/jazzy.sh
env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER_12 }}
- name: Deploy Jazzy Docs
if: github.ref == 'refs/heads/main'
uses: peaceiris/actions-gh-pages@v3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,15 @@ let score2ToFetch = GameScore(objectId: score2ForFetchedLater?.objectId)
}
}

var fetchedScore: GameScore!

//: Synchronously fetchAll GameScore's based on it's objectId's alone.
do {
let fetchedScores = try [scoreToFetch, score2ToFetch].fetchAll()
fetchedScores.forEach { result in
switch result {
case .success(let fetched):
fetchedScore = fetched
print("Successfully fetched: \(fetched)")
case .failure(let error):
print("Error fetching: \(error)")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
//: [Previous](@previous)

import PlaygroundSupport
import Foundation
import ParseSwift

PlaygroundPage.current.needsIndefiniteExecution = true
initializeParse()

struct User: ParseUser {
//: These are required for ParseObject
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?

//: These are required for ParseUser
var username: String?
var email: String?
var password: String?
var authData: [String: [String: String]?]?

//: Your custom keys
var customKey: String?
}

struct Role<RoleUser: ParseUser>: ParseRole {

// required by ParseObject
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?

// provided by Role
var name: String

init() {
self.name = ""
}
}

//: Roles can provide additional access/security to your apps.

//: This variable will store the saved role
var savedRole: Role<User>?

//: Now we will create the Role.
if let currentUser = User.current {

//: Every Role requires an ACL that can't be changed after saving.
var acl = ParseACL()
acl.setReadAccess(user: currentUser, value: true)
acl.setWriteAccess(user: currentUser, value: true)

do {
//: Create the actual Role with a name and ACL.
var adminRole = try Role<User>(name: "Administrator", acl: acl)
adminRole.save { result in
switch result {
case .success(let saved):
print("The role saved successfully: \(saved)")
print("Check your \"Role\" class in Parse Dashboard.")

//: Store the saved role so we can use it later...
savedRole = saved

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

//: Lets check to see if our Role has saved
if savedRole != nil {
print("We have a saved Role")
}

//: Users can be added to our previously saved Role.
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
switch result {
case .success(let saved):
print("The role saved successfully: \(saved)")
print("Check \"users\" field in your \"Role\" class in Parse Dashboard.")

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

} catch {
print("Error: \(error)")
}

//: To retrieve the users who are all Administrators, we need to query the relation.
let templateUser = User()
savedRole!.users.query(templateUser).find { result in
switch result {
case .success(let relatedUsers):
print("The following users are part of the \"\(savedRole!.name) role: \(relatedUsers)")

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

//: Of course, you can remove users from the roles as well.
try savedRole!.users.remove([User.current!]).save { result in
switch result {
case .success(let saved):
print("The role removed successfully: \(saved)")
print("Check \"users\" field in your \"Role\" class in Parse Dashboard.")

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

//: Additional roles can be created and tied to already created roles. Lets create a "Member" role.

//: This variable will store the saved role
var savedRoleModerator: Role<User>?

//: We need another ACL
var acl = ParseACL()
acl.setReadAccess(user: User.current!, value: true)
acl.setWriteAccess(user: User.current!, value: false)

do {
//: Create the actual Role with a name and ACL.
var memberRole = try Role<User>(name: "Member", acl: acl)
memberRole.save { result in
switch result {
case .success(let saved):
print("The role saved successfully: \(saved)")
print("Check your \"Role\" class in Parse Dashboard.")

//: Store the saved role so we can use it later...
savedRoleModerator = saved

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

//: Lets check to see if our Role has saved
if savedRoleModerator != nil {
print("We have a saved Role")
}

//: Roles can be added to our previously saved Role.
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
switch result {
case .success(let saved):
print("The role saved successfully: \(saved)")
print("Check \"roles\" field in your \"Role\" class in Parse Dashboard.")

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

} catch {
print("Error: \(error)")
}

//: 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 \"\(savedRole!.name) role: \(relatedRoles)")

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

//: Of course, you can remove users from the roles as well.
try savedRole!.roles.remove([savedRoleModerator!]).save { result in
switch result {
case .success(let saved):
print("The role removed successfully: \(saved)")
print("Check the \"roles\" field in your \"Role\" class in Parse Dashboard.")

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

//: All `ParseObjects` have a `ParseRelation` attribute that be used on instances.
//: For example, the User has:
let relation = User.current!.relation

//: Example: relation.add(<#T##users: [ParseUser]##[ParseUser]#>)
//: Example: relation.remove(<#T##key: String##String#>, objects: <#T##[ParseObject]#>)

//: Using this relation, you can create many-to-many relationships with other `ParseObjecs`,
//: similar to `users` and `roles`.

PlaygroundPage.current.finishExecution()

//: [Next](@next)
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//: [Previous](@previous)

import PlaygroundSupport
import Foundation
import ParseSwift

PlaygroundPage.current.needsIndefiniteExecution = true
initializeParse()

//: Some ValueTypes ParseObject's we will use...
struct GameScore: ParseObject {
//: Those are required for Object
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?

//: Your own properties
var score: Int = 0

//custom initializer
init(score: Int) {
self.score = score
}

init(objectId: String?) {
self.objectId = objectId
}
}

//: You can have the server do operations on your ParseObjects for you.

//: First lets create another GameScore
let savedScore: GameScore!
do {
savedScore = try GameScore(score: 102).save()
} catch {
savedScore = nil
fatalError("Error saving: \(error)")
}

//: Then we will increment the score.
let incrementOperation = savedScore
.operation.increment("score", by: 1)

incrementOperation.save { result in
switch result {
case .success:
print("Original score: \(savedScore). Check the new score on Parse Dashboard.")
case .failure(let error):
assertionFailure("Error saving: \(error)")
}
}

//: You can increment the score again syncronously.
do {
_ = try incrementOperation.save()
print("Original score: \(savedScore). Check the new score on Parse Dashboard.")
} catch {
print(error)
}

//: There are other operations: add/remove/delete objects from `ParseObjects`.
//: In fact, the `users` and `roles` relations from `ParseRoles` used the add/remove operations.
let operations = savedScore.operation

//: Example: operations.add("hello", objects: ["test"])

PlaygroundPage.current.finishExecution()
//: [Next](@next)
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ score.score = 200
try score.save()

let afterDate = Date().addingTimeInterval(-300)
let query = GameScore.query("score" > 100, "createdAt" > afterDate)
var query = GameScore.query("score" > 100, "createdAt" > afterDate)

// Query asynchronously (preferred way) - Performs work on background
// queue and returns to designated on designated callbackQueue.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ struct User: ParseUser {
var updatedAt: Date?
var ACL: ParseACL?

// These are required for ParseUser
//: These are required for ParseUser
var username: String?
var email: String?
var password: String?
var authData: [String: [String: String]?]?

//: Your custom keys
var customKey: String?
Expand Down
2 changes: 2 additions & 0 deletions ParseSwift.playground/contents.xcplayground
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@
<page name='9 - Files'/>
<page name='10 - Cloud Code'/>
<page name='11 - LiveQuery'/>
<page name='12 - Roles and Relations'/>
<page name='13 - Operations'/>
</pages>
</playground>

0 comments on commit 86d4eb7

Please sign in to comment.