Skip to content

Commit 86d4eb7

Browse files
cbaker6TomWFox
andauthored
Add Roles and Relations (#54)
* 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 <[email protected]> * Apply suggestions from code review Co-authored-by: Tom Fox <[email protected]> Co-authored-by: Tom Fox <[email protected]>
1 parent 5d15b43 commit 86d4eb7

File tree

56 files changed

+3387
-560
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+3387
-560
lines changed

.codecov.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ coverage:
55
status:
66
patch:
77
default:
8-
target: 67
8+
target: auto
99
changes: false
1010
project:
1111
default:
12-
target: 74
12+
target: 76
1313
comment:
1414
require_changes: true

.github/workflows/ci.yml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ on:
66
branches: '*'
77
env:
88
CI_XCODE_VER: '/Applications/Xcode_11.7.app/Contents/Developer'
9-
CI_XCODE_VER_12: '/Applications/Xcode_12.2.app/Contents/Developer'
109

1110
jobs:
1211
xcode-test-ios:
@@ -98,19 +97,15 @@ jobs:
9897
uses: actions/cache@v2
9998
with:
10099
path: vendor/bundle
101-
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
100+
key: ${{ runner.os }}-gem-v1-${{ hashFiles('**/Gemfile.lock') }}
102101
restore-keys: |
103-
${{ runner.os }}-gem-
102+
${{ runner.os }}-gem-v1
104103
- name: Install Bundle
105104
run: |
106105
bundle config path vendor/bundle
107106
bundle install
108-
env:
109-
DEVELOPER_DIR: ${{ env.CI_XCODE_VER_12 }}
110107
- name: Create Jazzy Docs
111108
run: ./Scripts/jazzy.sh
112-
env:
113-
DEVELOPER_DIR: ${{ env.CI_XCODE_VER_12 }}
114109
- name: Deploy Jazzy Docs
115110
if: github.ref == 'refs/heads/main'
116111
uses: peaceiris/actions-gh-pages@v3

ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,15 @@ let score2ToFetch = GameScore(objectId: score2ForFetchedLater?.objectId)
216216
}
217217
}
218218

219+
var fetchedScore: GameScore!
220+
219221
//: Synchronously fetchAll GameScore's based on it's objectId's alone.
220222
do {
221223
let fetchedScores = try [scoreToFetch, score2ToFetch].fetchAll()
222224
fetchedScores.forEach { result in
223225
switch result {
224226
case .success(let fetched):
227+
fetchedScore = fetched
225228
print("Successfully fetched: \(fetched)")
226229
case .failure(let error):
227230
print("Error fetching: \(error)")
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
//: [Previous](@previous)
2+
3+
import PlaygroundSupport
4+
import Foundation
5+
import ParseSwift
6+
7+
PlaygroundPage.current.needsIndefiniteExecution = true
8+
initializeParse()
9+
10+
struct User: ParseUser {
11+
//: These are required for ParseObject
12+
var objectId: String?
13+
var createdAt: Date?
14+
var updatedAt: Date?
15+
var ACL: ParseACL?
16+
17+
//: These are required for ParseUser
18+
var username: String?
19+
var email: String?
20+
var password: String?
21+
var authData: [String: [String: String]?]?
22+
23+
//: Your custom keys
24+
var customKey: String?
25+
}
26+
27+
struct Role<RoleUser: ParseUser>: ParseRole {
28+
29+
// required by ParseObject
30+
var objectId: String?
31+
var createdAt: Date?
32+
var updatedAt: Date?
33+
var ACL: ParseACL?
34+
35+
// provided by Role
36+
var name: String
37+
38+
init() {
39+
self.name = ""
40+
}
41+
}
42+
43+
//: Roles can provide additional access/security to your apps.
44+
45+
//: This variable will store the saved role
46+
var savedRole: Role<User>?
47+
48+
//: Now we will create the Role.
49+
if let currentUser = User.current {
50+
51+
//: Every Role requires an ACL that can't be changed after saving.
52+
var acl = ParseACL()
53+
acl.setReadAccess(user: currentUser, value: true)
54+
acl.setWriteAccess(user: currentUser, value: true)
55+
56+
do {
57+
//: Create the actual Role with a name and ACL.
58+
var adminRole = try Role<User>(name: "Administrator", acl: acl)
59+
adminRole.save { result in
60+
switch result {
61+
case .success(let saved):
62+
print("The role saved successfully: \(saved)")
63+
print("Check your \"Role\" class in Parse Dashboard.")
64+
65+
//: Store the saved role so we can use it later...
66+
savedRole = saved
67+
68+
case .failure(let error):
69+
print("Error saving role: \(error)")
70+
}
71+
}
72+
} catch {
73+
print("Error: \(error)")
74+
}
75+
}
76+
77+
//: Lets check to see if our Role has saved
78+
if savedRole != nil {
79+
print("We have a saved Role")
80+
}
81+
82+
//: Users can be added to our previously saved Role.
83+
do {
84+
//: `ParseRoles` have `ParseRelations` that relate them either `ParseUser` and `ParseRole` objects.
85+
//: The `ParseUser` relations can be accessed using `users`. We can then add `ParseUser`'s to the relation.
86+
try savedRole!.users.add([User.current!]).save { result in
87+
switch result {
88+
case .success(let saved):
89+
print("The role saved successfully: \(saved)")
90+
print("Check \"users\" field in your \"Role\" class in Parse Dashboard.")
91+
92+
case .failure(let error):
93+
print("Error saving role: \(error)")
94+
}
95+
}
96+
97+
} catch {
98+
print("Error: \(error)")
99+
}
100+
101+
//: To retrieve the users who are all Administrators, we need to query the relation.
102+
let templateUser = User()
103+
savedRole!.users.query(templateUser).find { result in
104+
switch result {
105+
case .success(let relatedUsers):
106+
print("The following users are part of the \"\(savedRole!.name) role: \(relatedUsers)")
107+
108+
case .failure(let error):
109+
print("Error saving role: \(error)")
110+
}
111+
}
112+
113+
//: Of course, you can remove users from the roles as well.
114+
try savedRole!.users.remove([User.current!]).save { result in
115+
switch result {
116+
case .success(let saved):
117+
print("The role removed successfully: \(saved)")
118+
print("Check \"users\" field in your \"Role\" class in Parse Dashboard.")
119+
120+
case .failure(let error):
121+
print("Error saving role: \(error)")
122+
}
123+
}
124+
125+
//: Additional roles can be created and tied to already created roles. Lets create a "Member" role.
126+
127+
//: This variable will store the saved role
128+
var savedRoleModerator: Role<User>?
129+
130+
//: We need another ACL
131+
var acl = ParseACL()
132+
acl.setReadAccess(user: User.current!, value: true)
133+
acl.setWriteAccess(user: User.current!, value: false)
134+
135+
do {
136+
//: Create the actual Role with a name and ACL.
137+
var memberRole = try Role<User>(name: "Member", acl: acl)
138+
memberRole.save { result in
139+
switch result {
140+
case .success(let saved):
141+
print("The role saved successfully: \(saved)")
142+
print("Check your \"Role\" class in Parse Dashboard.")
143+
144+
//: Store the saved role so we can use it later...
145+
savedRoleModerator = saved
146+
147+
case .failure(let error):
148+
print("Error saving role: \(error)")
149+
}
150+
}
151+
} catch {
152+
print("Error: \(error)")
153+
}
154+
155+
//: Lets check to see if our Role has saved
156+
if savedRoleModerator != nil {
157+
print("We have a saved Role")
158+
}
159+
160+
//: Roles can be added to our previously saved Role.
161+
do {
162+
//: `ParseRoles` have `ParseRelations` that relate them either `ParseUser` and `ParseRole` objects.
163+
//: The `ParseUser` relations can be accessed using `users`. We can then add `ParseUser`'s to the relation.
164+
try savedRole!.roles.add([savedRoleModerator!]).save { result in
165+
switch result {
166+
case .success(let saved):
167+
print("The role saved successfully: \(saved)")
168+
print("Check \"roles\" field in your \"Role\" class in Parse Dashboard.")
169+
170+
case .failure(let error):
171+
print("Error saving role: \(error)")
172+
}
173+
}
174+
175+
} catch {
176+
print("Error: \(error)")
177+
}
178+
179+
//: To retrieve the users who are all Administrators, we need to query the relation.
180+
//: This time we will use a helper query from `ParseRole`.
181+
savedRole!.queryRoles?.find { result in
182+
switch result {
183+
case .success(let relatedRoles):
184+
print("The following roles are part of the \"\(savedRole!.name) role: \(relatedRoles)")
185+
186+
case .failure(let error):
187+
print("Error saving role: \(error)")
188+
}
189+
}
190+
191+
//: Of course, you can remove users from the roles as well.
192+
try savedRole!.roles.remove([savedRoleModerator!]).save { result in
193+
switch result {
194+
case .success(let saved):
195+
print("The role removed successfully: \(saved)")
196+
print("Check the \"roles\" field in your \"Role\" class in Parse Dashboard.")
197+
198+
case .failure(let error):
199+
print("Error saving role: \(error)")
200+
}
201+
}
202+
203+
//: All `ParseObjects` have a `ParseRelation` attribute that be used on instances.
204+
//: For example, the User has:
205+
let relation = User.current!.relation
206+
207+
//: Example: relation.add(<#T##users: [ParseUser]##[ParseUser]#>)
208+
//: Example: relation.remove(<#T##key: String##String#>, objects: <#T##[ParseObject]#>)
209+
210+
//: Using this relation, you can create many-to-many relationships with other `ParseObjecs`,
211+
//: similar to `users` and `roles`.
212+
213+
PlaygroundPage.current.finishExecution()
214+
215+
//: [Next](@next)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//: [Previous](@previous)
2+
3+
import PlaygroundSupport
4+
import Foundation
5+
import ParseSwift
6+
7+
PlaygroundPage.current.needsIndefiniteExecution = true
8+
initializeParse()
9+
10+
//: Some ValueTypes ParseObject's we will use...
11+
struct GameScore: ParseObject {
12+
//: Those are required for Object
13+
var objectId: String?
14+
var createdAt: Date?
15+
var updatedAt: Date?
16+
var ACL: ParseACL?
17+
18+
//: Your own properties
19+
var score: Int = 0
20+
21+
//custom initializer
22+
init(score: Int) {
23+
self.score = score
24+
}
25+
26+
init(objectId: String?) {
27+
self.objectId = objectId
28+
}
29+
}
30+
31+
//: You can have the server do operations on your ParseObjects for you.
32+
33+
//: First lets create another GameScore
34+
let savedScore: GameScore!
35+
do {
36+
savedScore = try GameScore(score: 102).save()
37+
} catch {
38+
savedScore = nil
39+
fatalError("Error saving: \(error)")
40+
}
41+
42+
//: Then we will increment the score.
43+
let incrementOperation = savedScore
44+
.operation.increment("score", by: 1)
45+
46+
incrementOperation.save { result in
47+
switch result {
48+
case .success:
49+
print("Original score: \(savedScore). Check the new score on Parse Dashboard.")
50+
case .failure(let error):
51+
assertionFailure("Error saving: \(error)")
52+
}
53+
}
54+
55+
//: You can increment the score again syncronously.
56+
do {
57+
_ = try incrementOperation.save()
58+
print("Original score: \(savedScore). Check the new score on Parse Dashboard.")
59+
} catch {
60+
print(error)
61+
}
62+
63+
//: There are other operations: add/remove/delete objects from `ParseObjects`.
64+
//: In fact, the `users` and `roles` relations from `ParseRoles` used the add/remove operations.
65+
let operations = savedScore.operation
66+
67+
//: Example: operations.add("hello", objects: ["test"])
68+
69+
PlaygroundPage.current.finishExecution()
70+
//: [Next](@next)

ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ score.score = 200
2121
try score.save()
2222

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

2626
// Query asynchronously (preferred way) - Performs work on background
2727
// queue and returns to designated on designated callbackQueue.

ParseSwift.playground/Pages/3 - User - Sign Up.xcplaygroundpage/Contents.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ struct User: ParseUser {
1414
var updatedAt: Date?
1515
var ACL: ParseACL?
1616

17-
// These are required for ParseUser
17+
//: These are required for ParseUser
1818
var username: String?
1919
var email: String?
2020
var password: String?
21+
var authData: [String: [String: String]?]?
2122

2223
//: Your custom keys
2324
var customKey: String?

ParseSwift.playground/contents.xcplayground

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@
1212
<page name='9 - Files'/>
1313
<page name='10 - Cloud Code'/>
1414
<page name='11 - LiveQuery'/>
15+
<page name='12 - Roles and Relations'/>
16+
<page name='13 - Operations'/>
1517
</pages>
1618
</playground>

0 commit comments

Comments
 (0)