Skip to content

Commit 22be4df

Browse files
authored
feat: add ability to merge updated objects with original (#315)
* feat: allow for developer to specify updated keys * add tests and Playgrounds * nit * add ACL to default implementation * test Patch merge * update documentation * merge ParseObjects automatically * fix build on Swift 5.2 * Make sure originalData never saves to Keychain * move test to correct file * Update Playgrounds and move ParseObjectMutable into ParseObject * doc updates * coverage * update Playgrounds * tested and nit playgrounds * fix merge conflicts * mutable -> mergeable * fix sortByText * add info about POP in README * Move the where for Swift 5.2 * Update README.md * try windows action * Update ci.yml * Update ci.yml * don't build async on windows * revert async back to Swift 5.5.2
1 parent 3c3a6e8 commit 22be4df

File tree

95 files changed

+1931
-319
lines changed

Some content is hidden

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

95 files changed

+1931
-319
lines changed

CHANGELOG.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,22 @@
22

33
### main
44

5-
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/3.1.2...main)
5+
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.0.0...main)
66
* _Contributing to this repo? Add info about your change here to be included in the next release_
77

8+
### 4.0.0
9+
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/3.1.2...4.0.0)
10+
11+
__New features__
12+
- (Breaking Change) Add the ability to merge updated ParseObject's with original objects when using the
13+
.mergeable property. To do this, developers need to add an implementation of merge() to
14+
respective ParseObject's. The compiler will recommend the new originalData property be added to
15+
every ParseObject. If you used ParseObjectMutable in the past, you should remove it as it is now
16+
part of ParseObject. In addition, all ParseObject properties should be optional and every object
17+
needs to have a default initilizer of init(). See the Playgrounds for recommendations on how to
18+
define a ParseObject. Look at the PR for
19+
details on why this is important when using the SDK ([#315](https://github.com/parse-community/Parse-Swift/pull/315)), thanks to [Corey Baker](https://github.com/cbaker6).
20+
821
### 3.1.2
922
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/3.1.1...3.1.2)
1023

@@ -31,7 +44,7 @@ __New features__
3144
- Adds equalTo QueryConstraint along with ability to change the SDK default behavior of using $eq QueryConstraint parameter or not ([#310](https://github.com/parse-community/Parse-Swift/pull/310)), thanks to [Corey Baker](https://github.com/cbaker6).
3245
- Adds isNull and isNotNull QueryConstraint along with the ability set/forceSet null using ParseOperation ([#308](https://github.com/parse-community/Parse-Swift/pull/308)), thanks to [Corey Baker](https://github.com/cbaker6).
3346
- Adds auth support for GitHub, Google, and LinkedIn ([#307](https://github.com/parse-community/Parse-Swift/pull/307)), thanks to [Corey Baker](https://github.com/cbaker6).
34-
- (Breaking Change) Adds options to matchesText QueryConstraint along with the ability to see matching score. The compiler should recommend the new score property to all ParseObjects ([#306](https://github.com/parse-community/Parse-Swift/pull/306)), thanks to [Corey Baker](https://github.com/cbaker6).
47+
- (Breaking Change) Adds options to matchesText QueryConstraint along with the ability to see matching score. The compiler will recommend the new score property be added to all ParseObjects ([#306](https://github.com/parse-community/Parse-Swift/pull/306)), thanks to [Corey Baker](https://github.com/cbaker6).
3548
- Adds withCount query ([#306](https://github.com/parse-community/Parse-Swift/pull/306)), thanks to [Corey Baker](https://github.com/cbaker6).
3649

3750
__Improvements__

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ Currently, we are not making use of the commit _scope_, which would be written a
119119

120120
## Evolution
121121

122-
It's not intended as a port of the Parse Objective-c SDK and has many new philosophies. Please see [this thread](https://github.com/parse-community/Parse-Swift/issues/3) for a detailed discussion about the intended evolution of this SDK.
122+
The ParseSwift SDK is not a port of the [Parse-SDK-iOS-OSX SDK](https://github.com/parse-community/Parse-SDK-iOS-OSX) and though some of it may feel familiar, it is not backwards compatible and is designed using [protocol oriented programming (POP) and value types](https://www.pluralsight.com/guides/protocol-oriented-programming-in-swift) instead of OOP and reference types. You can learn more about POP by watching [this](https://developer.apple.com/videos/play/wwdc2015/408/) or [that](https://developer.apple.com/videos/play/wwdc2016/419/) videos from previous WWDC's. Please see [this thread](https://github.com/parse-community/Parse-Swift/issues/3) for a detailed discussion about the intended evolution of this SDK.
123123

124124
## Code of Conduct
125125

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

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,26 @@ do {
3030
}
3131

3232
//: Create your own value typed `ParseObject`.
33-
struct GameScore: ParseObject, ParseObjectMutable {
33+
struct GameScore: ParseObject {
3434
//: These are required by ParseObject
3535
var objectId: String?
3636
var createdAt: Date?
3737
var updatedAt: Date?
3838
var ACL: ParseACL?
39+
var originalData: Data?
3940

4041
//: Your own properties.
41-
var points: Int = 0
42+
var points: Int?
43+
44+
//: Implement your own version of merge
45+
func merge(_ object: Self) throws -> Self {
46+
var updated = try mergeParse(object)
47+
if updated.shouldRestoreKey(\.points,
48+
original: object) {
49+
updated.points = object.points
50+
}
51+
return updated
52+
}
4253
}
4354

4455
//: It's recommended to place custom initializers in an extension
@@ -60,12 +71,27 @@ struct GameData: ParseObject {
6071
var createdAt: Date?
6172
var updatedAt: Date?
6273
var ACL: ParseACL?
74+
var originalData: Data?
6375

6476
//: Your own properties.
6577
var polygon: ParsePolygon?
6678
//: `ParseBytes` needs to be a part of the original schema
6779
//: or else you will need your masterKey to force an upgrade.
6880
var bytes: ParseBytes?
81+
82+
//: Implement your own version of merge
83+
func merge(_ object: Self) throws -> Self {
84+
var updated = try mergeParse(object)
85+
if shouldRestoreKey(\.polygon,
86+
original: object) {
87+
updated.polygon = object.polygon
88+
}
89+
if shouldRestoreKey(\.bytes,
90+
original: object) {
91+
updated.bytes = object.bytes
92+
}
93+
return updated
94+
}
6995
}
7096

7197
//: It's recommended to place custom initializers in an extension
@@ -99,19 +125,14 @@ score.save { result in
99125
allows you to only send the updated keys to the
100126
parse server as opposed to the whole object.
101127
*/
102-
var changedScore = savedScore.mutable
128+
var changedScore = savedScore.mergeable
103129
changedScore.points = 200
104130
changedScore.save { result in
105131
switch result {
106-
case .success(var savedChangedScore):
132+
case .success(let savedChangedScore):
107133
assert(savedChangedScore.points == 200)
108134
assert(savedScore.objectId == savedChangedScore.objectId)
109135

110-
/*: Note that savedChangedScore is mutable since it's
111-
a var after success.
112-
*/
113-
savedChangedScore.points = 500
114-
115136
case .failure(let error):
116137
assertionFailure("Error saving: \(error)")
117138
}
@@ -132,7 +153,10 @@ var score2ForFetchedLater: GameScore?
132153
otherResults.forEach { otherResult in
133154
switch otherResult {
134155
case .success(let savedScore):
135-
print("Saved \"\(savedScore.className)\" with points \(savedScore.points) successfully")
156+
print("""
157+
Saved \"\(savedScore.className)\" with
158+
points \(String(describing: savedScore.points)) successfully
159+
""")
136160
if index == 1 {
137161
score2ForFetchedLater = savedScore
138162
}
@@ -191,14 +215,15 @@ assert(savedScore?.points == 10)
191215
allows you to only send the updated keys to the
192216
parse server as opposed to the whole object.
193217
*/
194-
guard var changedScore = savedScore?.mutable else {
195-
fatalError()
218+
guard var changedScore = savedScore?.mergeable else {
219+
fatalError("Should have produced mutable changedScore")
196220
}
197221
changedScore.points = 200
198222

199223
let savedChangedScore: GameScore?
200224
do {
201225
savedChangedScore = try changedScore.save()
226+
print("Updated score: \(String(describing: savedChangedScore))")
202227
} catch {
203228
savedChangedScore = nil
204229
fatalError("Error saving: \(error)")
@@ -220,7 +245,7 @@ assert(otherResults != nil)
220245
otherResults!.forEach { result in
221246
switch result {
222247
case .success(let savedScore):
223-
print("Saved \"\(savedScore.className)\" with points \(savedScore.points) successfully")
248+
print("Saved \"\(savedScore.className)\" with points \(String(describing: savedScore.points)) successfully")
224249
case .failure(let error):
225250
assertionFailure("Error saving: \(error)")
226251
}

ParseSwift.playground/Pages/10 - Cloud Code.xcplaygroundpage/Contents.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,20 @@ struct GameScore: ParseObject {
106106
var createdAt: Date?
107107
var updatedAt: Date?
108108
var ACL: ParseACL?
109+
var originalData: Data?
109110

110111
//: Your own properties.
111-
var points: Int = 0
112+
var points: Int?
113+
114+
//: Implement your own version of merge
115+
func merge(_ object: Self) throws -> Self {
116+
var updated = try mergeParse(object)
117+
if updated.shouldRestoreKey(\.points,
118+
original: object) {
119+
updated.points = object.points
120+
}
121+
return updated
122+
}
112123
}
113124

114125
//: It's recommended to place custom initializers in an extension

ParseSwift.playground/Pages/11 - LiveQuery.xcplaygroundpage/Contents.swift

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,30 @@ struct GameScore: ParseObject {
1616
var createdAt: Date?
1717
var updatedAt: Date?
1818
var ACL: ParseACL?
19+
var originalData: Data?
1920

2021
//: Your own properties.
21-
var points: Int = 0
22+
var points: Int?
2223
var location: ParseGeoPoint?
2324
var name: String?
25+
26+
//: Implement your own version of merge
27+
func merge(_ object: Self) throws -> Self {
28+
var updated = try mergeParse(object)
29+
if updated.shouldRestoreKey(\.points,
30+
original: object) {
31+
updated.points = object.points
32+
}
33+
if updated.shouldRestoreKey(\.location,
34+
original: object) {
35+
updated.location = object.location
36+
}
37+
if updated.shouldRestoreKey(\.name,
38+
original: object) {
39+
updated.name = object.name
40+
}
41+
return updated
42+
}
2443
}
2544

2645
//: It's recommended to place custom initializers in an extension

ParseSwift.playground/Pages/12 - Roles and Relations.xcplaygroundpage/Contents.swift

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct User: ParseUser {
1919
var createdAt: Date?
2020
var updatedAt: Date?
2121
var ACL: ParseACL?
22+
var originalData: Data?
2223

2324
//: These are required by `ParseUser`.
2425
var username: String?
@@ -29,6 +30,16 @@ struct User: ParseUser {
2930

3031
//: Your custom keys.
3132
var customKey: String?
33+
34+
//: Implement your own version of merge
35+
func merge(_ object: Self) throws -> Self {
36+
var updated = try mergeParse(object)
37+
if updated.shouldRestoreKey(\.customKey,
38+
original: object) {
39+
updated.customKey = object.customKey
40+
}
41+
return updated
42+
}
3243
}
3344

3445
struct Role<RoleUser: ParseUser>: ParseRole {
@@ -38,25 +49,43 @@ struct Role<RoleUser: ParseUser>: ParseRole {
3849
var createdAt: Date?
3950
var updatedAt: Date?
4051
var ACL: ParseACL?
52+
var originalData: Data?
4153

4254
//: Provided by Role.
43-
var name: String
44-
45-
init() {
46-
self.name = ""
55+
var name: String?
56+
57+
//: Implement your own version of merge
58+
func merge(_ object: Self) throws -> Self {
59+
var updated = try mergeParse(object)
60+
if updated.shouldRestoreKey(\.name,
61+
original: object) {
62+
updated.name = object.name
63+
}
64+
return updated
4765
}
4866
}
4967

5068
//: Create your own value typed `ParseObject`.
51-
struct GameScore: ParseObject, ParseObjectMutable {
69+
struct GameScore: ParseObject {
5270
//: These are required by ParseObject
5371
var objectId: String?
5472
var createdAt: Date?
5573
var updatedAt: Date?
5674
var ACL: ParseACL?
75+
var originalData: Data?
5776

5877
//: Your own properties.
59-
var points: Int = 0
78+
var points: Int?
79+
80+
//: Implement your own version of merge
81+
func merge(_ object: Self) throws -> Self {
82+
var updated = try mergeParse(object)
83+
if updated.shouldRestoreKey(\.points,
84+
original: object) {
85+
updated.points = object.points
86+
}
87+
return updated
88+
}
6089
}
6190

6291
//: It's recommended to place custom initializers in an extension
@@ -136,7 +165,10 @@ do {
136165
try savedRole!.users.query(templateUser).find { result in
137166
switch result {
138167
case .success(let relatedUsers):
139-
print("The following users are part of the \"\(savedRole!.name) role: \(relatedUsers)")
168+
print("""
169+
The following users are part of the
170+
\"\(String(describing: savedRole!.name)) role: \(relatedUsers)
171+
""")
140172

141173
case .failure(let error):
142174
print("Error saving role: \(error)")
@@ -220,7 +252,10 @@ do {
220252
savedRole!.queryRoles?.find { result in
221253
switch result {
222254
case .success(let relatedRoles):
223-
print("The following roles are part of the \"\(savedRole!.name) role: \(relatedRoles)")
255+
print("""
256+
The following roles are part of the
257+
\"\(String(describing: savedRole!.name)) role: \(relatedRoles)
258+
""")
224259

225260
case .failure(let error):
226261
print("Error saving role: \(error)")
@@ -263,7 +298,7 @@ let score2 = GameScore(points: 57)
263298
switch result {
264299
case .success(let saved):
265300
print("The relation saved successfully: \(saved)")
266-
print("Check \"pointss\" field in your \"_User\" class in Parse Dashboard.")
301+
print("Check \"points\" field in your \"_User\" class in Parse Dashboard.")
267302

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

ParseSwift.playground/Pages/13 - Operations.xcplaygroundpage/Contents.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,25 @@ struct GameScore: ParseObject {
1919
var createdAt: Date?
2020
var updatedAt: Date?
2121
var ACL: ParseACL?
22+
var originalData: Data?
2223

2324
//: Your own properties.
24-
var points: Int? = 0
25+
var points: Int?
2526
var name: String?
27+
28+
//: Implement your own version of merge
29+
func merge(_ object: Self) throws -> Self {
30+
var updated = try mergeParse(object)
31+
if updated.shouldRestoreKey(\.points,
32+
original: object) {
33+
updated.points = object.points
34+
}
35+
if updated.shouldRestoreKey(\.name,
36+
original: object) {
37+
updated.name = object.name
38+
}
39+
return updated
40+
}
2641
}
2742

2843
//: It's recommended to place custom initializers in an extension

ParseSwift.playground/Pages/15 - Custom ObjectId.xcplaygroundpage/Contents.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,26 @@ npm start -- --appId applicationId --clientKey clientKey --masterKey masterKey -
2121
initializeParseCustomObjectId()
2222

2323
//: Create your own value typed `ParseObject`.
24-
struct GameScore: ParseObject, ParseObjectMutable {
24+
struct GameScore: ParseObject {
2525
//: These are required by ParseObject
2626
var objectId: String?
2727
var createdAt: Date?
2828
var updatedAt: Date?
2929
var ACL: ParseACL?
30+
var originalData: Data?
3031

3132
//: Your own properties.
32-
var points: Int = 0
33+
var points: Int?
34+
35+
//: Implement your own version of merge
36+
func merge(_ object: Self) throws -> Self {
37+
var updated = try mergeParse(object)
38+
if updated.shouldRestoreKey(\.points,
39+
original: object) {
40+
updated.points = object.points
41+
}
42+
return updated
43+
}
3344
}
3445

3546
//: It's recommended to place custom initializers in an extension
@@ -67,11 +78,11 @@ score.save { result in
6778
print("Saved score: \(savedScore)")
6879

6980
/*: To modify, need to make it a var as the value type
70-
was initialized as immutable. Using `mutable`
81+
was initialized as immutable. Using `.mergeable`
7182
allows you to only send the updated keys to the
7283
parse server as opposed to the whole object.
7384
*/
74-
var changedScore = savedScore.mutable
85+
var changedScore = savedScore.mergeable
7586
changedScore.points = 200
7687
changedScore.save { result in
7788
switch result {

0 commit comments

Comments
 (0)