Skip to content

Instantly share code, notes, and snippets.

@kishikawakatsumi
Created July 17, 2023 08:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kishikawakatsumi/c9fce7ee1a3966f5ddc48df856a67171 to your computer and use it in GitHub Desktop.
Save kishikawakatsumi/c9fce7ee1a3966f5ddc48df856a67171 to your computer and use it in GitHub Desktop.
Writing the correct code when persisting models containing associations is very difficult
If the incorrect code is written when attempting to persist a model containing associations, either run-time errors occur or only the associations are not saved (although they appear to be saved correctly).
The latter makes it very difficult to find the problem, as it appears to be working correctly without any errors.
Of the four code examples below, only the first one saves the data correctly, including the association.
All of the remaining three cases do not work correctly.
The important thing is to insert the association source object into the context first.
Otherwise, you will get either a run-time error or the unexpected behaviour that only the related objects are not saved.
```swift
// 1. The only correct code: inserting the event object before the relation.
let event = Event(title: "title", start: Date(), end: Date())
modelContext.insert(event)
event.attendees.append(Attendee(name: "name", email: "email"))
```
```swift
// 2. Runtime error. The event object must be added to the context before the association is added.
let event = Event(title: "title", start: Date(), end: Date())
event.attendees.append(Attendee(name: "name", email: "email"))
modelContext.insert(event)
```
```swift
// 3. Appears to work correctly, but the association is not persisted. Very harmful.
let event = Event(title: "title", start: Date(), end: Date())
event.attendees = [Attendee(name: "name", email: "email")]
modelContext.insert(event)
```
```swift
// 4. Same as #3. Appears to work correctly but the association is not persisted.
// Xcode suggests such initializers in auto-completion, so mistakes are easy to make.
let attendees = [Attendee(name: "name", email: "email")]
let event = Event(title: title: "title", start: Date(), end: Date(), attendees: attendees)
modelContext.insert(event)
```
The first code works correctly because the related source, the event object, is inserted into the context first.
The second code adds the association before the event object is inserted into the context, which causes a run-time error.
The third code appears to work correctly without any errors, but in fact only the association is not saved.
This behaviour is highly problematic and delays the detection of problems because it appears to work correctly in memory.
Also, the event object from which the association source is saved, so as long as the association is not displayed, it still appears to be working correctly.
The fourth piece of code is essentially the same as the third, only the association is not persisted in the same way.
And the risk of writing such code is very high as Xcode suggests an initializer that initializes all properties, including the association, via autocompletion.
The code for the model used in this example is as follows:
```swift
import SwiftData
@Model
final class Event {
var title: String
var start: Date
var end: Date
@Relationship(.nullify, inverse: \Attendee.events)
var attendees: [Attendee]
init(title: String, start: Date, end: Date) {
self.title = title
self.start = start
self.end = end
}
}
```
```swift
@Model
final class Attendee {
var name: String
@Attribute(.unique)
var email: String
@Relationship(.nullify)
var events: [Event]
init(name: String, email: String) {
self.name = name
self.email = email
}
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment