Hi,
I just wasted the whole day on MigrationStage
's custom option.
My first advice is: Just care which document are you reading.
. It can lead you in the wrong direction if document is missing, not totally correct, etc.
My app has one model and I call it with Story.
and it look likes this:
enum StorySchema_Vx_x_x: VersionedSchema {
static var versionIdentifier: Schema.Version = Schema.Version(x, x, x)
static var models: [any PersistentModel.Type] {
return [Story.self]
}
@Model
class Story {
var title: String
var story: String
...
}
...
}
Now I have a new feature on app. However, due to the new design requirements I need to store Story.story
as below.
struct StoryPage: Codable {
let page: Int
let story: String
}
Here's the problem:
How should I store story on last database design?
- Replace
var story: String
withvar storiesAsPart: [StoryPage]
- Just add new parameter as
var storiesAsPart: [StoryPage]?
First approach
Of course my first decision is first one! Because I think it's cleaner as code, easier to understand. There are no any other distracting variables. Bla, bla, bla...
Ta-daa, That's why I wasted my whole day.
I couldn't find proper way to achieve first option.
Every development I tried, came with another issue...
Second approach
I stopped to waste my time. Because this feature need to be release ASAP due to market-trending.
But still got errors. WHY!?
Because wrong usage of parameters...
Solution
Do you remember what I wrote earlier?
You have to check if it is correct; what you read, what you watch, what you listen...
I just found a video(1) and realize how should I use MigrationStage.custom
.
Let's see how achieve to this migration.
enum StoryMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
return [
StorySchema_Vprevious_one.self,
StorySchema_Vlatest_one.self
]
}
static var stages: [MigrationStage] {
[
migrate_Vprevious_one_TO_Vlatest_one
]
}
static let migrate_Vprevious_one_TO_Vlatest_one = MigrationStage.custom(
fromVersion: StorySchema_Vprevious_one.self,
toVersion: StorySchema_Vlatest_one.self,
willMigrate: nil,
didMigrate: { context in
var stories: [StorySchema_Vlatest_one.Story]
do {
stories = try context.fetch(FetchDescriptor<StorySchema_Vlatest_one.Story>())
} catch {
fatalError("Failed to fetch stories: \(error)")
}
stories.forEach { story in
story.storiesAsPart = [
StoryPage(page: 1, story: story.story)
]
}
do {
try context.save()
} catch {
fatalError("Failed to save migration: \(error)")
}
}
)
}
We have two additional parameters compared to MigrationStage.lightweight: the willMigrate and didMigrate closures.
I was already familiar with these closures, having read a lot about them, but as I mentioned before, it's important to know if the documentation is correct.
On that video (1), I got that the meaning of those closures.
willMigrate: Is commonly used for clean up data, prepare data to migrate etc.
didMigrate: Update new model variables as you wish.
In my case, I used didMigrate
because I just wanted to fill new variable (because I know that's enough and it's okay).
Last words
I didn't waste anymore time on this feature. So, that's why I chose the quicker solution from my perspective.
Top comments (1)
video link: https://www.youtube.com/watch?v=PDKaZpWuH1I&t=1240s