-
-
Notifications
You must be signed in to change notification settings - Fork 69
/
Contents.swift
376 lines (324 loc) · 11.4 KB
/
Contents.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
//: [Previous](@previous)
//: For this page, make sure your build target is set to ParseSwift (macOS) and targeting
//: `My Mac` or whatever the name of your mac is. Also be sure your `Playground Settings`
//: in the `File Inspector` is `Platform = macOS`. This is because
//: Keychain in iOS Playgrounds behaves differently. Every page in Playgrounds should
//: be set to build for `macOS` unless specified.
import PlaygroundSupport
import Foundation
import ParseSwift
PlaygroundPage.current.needsIndefiniteExecution = true
initializeParse()
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
}
}
struct Role<RoleUser: ParseUser>: ParseRole {
//: Required by `ParseObject`.
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var originalData: Data?
//: Provided by Role.
var name: String?
//: Implement your own version of merge
func merge(with object: Self) throws -> Self {
var updated = try mergeParse(with: object)
if updated.shouldRestoreKey(\.name,
original: object) {
updated.name = object.name
}
return updated
}
}
//: Create your own value typed `ParseObject`.
struct GameScore: ParseObject {
//: These are required by ParseObject
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var originalData: Data?
//: Your own properties.
var points: Int?
//: Implement your own version of merge
func merge(with object: Self) throws -> Self {
var updated = try mergeParse(with: object)
if updated.shouldRestoreKey(\.points,
original: object) {
updated.points = object.points
}
return updated
}
}
//: It's recommended to place custom initializers in an extension
//: to preserve the memberwise initializer.
extension GameScore {
init(points: Int) {
self.points = points
}
init(objectId: String?) {
self.objectId = objectId
}
}
//: 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.
guard let currentUser = User.current else {
fatalError("User currently isn't signed in")
}
//: 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.
let 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.
do {
let query: Query<User>? = try savedRole!.users?.query()
query?.find { result in
switch result {
case .success(let relatedUsers):
print("""
The following users are part of the
\"\(String(describing: savedRole!.name)) role: \(relatedUsers)
""")
case .failure(let error):
print("Error saving role: \(error)")
}
}
} catch {
print(error)
}
//: Of course, you can remove users from the roles as well.
do {
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)")
}
}
} catch {
print(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>?
do {
//: Create the actual Role with a name and ACL.
let 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`.
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)")
}
}
} catch {
print("Error: \(error)")
}
//: Of course, you can remove users from the roles as well.
do {
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)")
}
}
} catch {
print(error)
}
//: 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 {
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):
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)")
}
}
//: You can also do
// let specificRelation = User.current!.relation("scores", className: "GameScore")
do {
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)")
case .failure(let error):
print("Error finding scores: \(error)")
}
}
} catch {
print(error)
}
//: In addition, you can leverage the child to find scores related to the parent.
do {
try GameScore.queryRelations("scores", parent: User.current!).find { result in
switch result {
case .success(let scores):
print("Found related scores from child: \(scores)")
case .failure(let error):
print("Error finding scores from child: \(error)")
}
}
} catch {
print(error)
}
//: Now we will see how to use the stored `ParseRelation on` property in User to create query
//: all of the relations to `scores`.
var updatedCurrentUser: User
do {
//: Fetch the updated user since the previous relations were created on the server.
updatedCurrentUser = try User.current!.fetch()
print("Updated current user with relation: \(updatedCurrentUser)")
} catch {
updatedCurrentUser = User.current!
print("\(error.localizedDescription)")
}
do {
let usableStoredRelation = try updatedCurrentUser.relation(updatedCurrentUser.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)")
}
}
} catch {
print("\(error.localizedDescription)")
}
PlaygroundPage.current.finishExecution()
//: [Next](@next)