DEV Community

Utku Y.
Utku Y.

Posted on

SwiftData: MigrationStage.custom

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
    ...
    }
...
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Here's the problem:

How should I store story on last database design?

  1. Replace var story: String with var storiesAsPart: [StoryPage]
  2. 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)")
            }
        }
    )
}
Enter fullscreen mode Exit fullscreen mode

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.

I still believe that it's possible to achieve the first approach.

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (1)

Collapse
 
uy profile image
Utku Y.

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more