Perform migration by adding List() and another model class
Asked Answered
C

2

4

I have the following models

class Area: Object {

// Specify properties to ignore (Realm won't persist these)

//  override static func ignoredProperties() -> [String] {
//    return []
//  }

    dynamic var id = 0
    dynamic var name = ""

    override static func primaryKey() -> String? {
        return "id"
    }

}

class Region: Object {

// Specify properties to ignore (Realm won't persist these)

//  override static func ignoredProperties() -> [String] {
//    return []
//  }

    dynamic var id = 0
    dynamic var name = ""

    override static func primaryKey() -> String? {
        return "id"
    }

}

And I would like to add let areas = List<Area>() to Region class and dynamic var region: Region? to Area class, may I ask how to perform the migration block? Cause the example in the migration documentation is only demo-ing the primitive types.

Cordoba answered 16/12, 2015 at 8:27 Comment(0)
T
1

Edited After Receiving Clarification

Alrighty. So since you do want to pre-populate areas when you add it to your model, you will need to implement some logic in your migration block after all.

let migrationBlock: MigrationBlock = { migration, oldSchemaVersion in
    migration.enumerate(Region.className()) { oldObject, newObject in
        if oldSchemaVersion < 1 {
            let areas = newObject?["areas"] as? List<MigrationObject>
            // Add new objects to 'areas' as needed
        }
    }
}

There's some sample code showing how to handle List objects in migrations in Realm Swift's sample code collection

If your goal in adding a region property to Area is so you can find out which Region object this Area is a child of, then you don't need to implement that as a model property. Instead, you can use linkingObjects(_: forProperty: ) to have Realm work that out on your behalf.

class Area: Object {
    dynamic var id = 0
    dynamic var name = ""
    var regions: [Region] {
        return linkingObjects(Region.self, forProperty: "areas")
    }

    override static func primaryKey() -> String? {
        return "id"
    }
}

To confirm what I said in the comments, migrations are a one-way path. They cannot be downgraded to previous schema versions. If you want to rapidly debug the migration process on a Realm file, I recommend putting the original Realm file aside and working on copies.


Original Answer

Do you actually have any data you wish to add to these new properties? Since it doesn't look like you do, you shouldn't need to implement any code in the migration block.

Simply increase the Realm schema version number, and supply an empty migration block.

let config = Realm.Configuration(
    schemaVersion: 1,
    migrationBlock: { migration, oldSchemaVersion in  

})

Realm.Configuration.defaultConfiguration = config

While the migration block cannot be nil, you only need to put code in there if there's any data in an old Realm file that you want to manipulate during a migration (i.e., moving it to another property). If you're adding brand new properties, it's not necessary to do anything to them inside the migration block.

It takes a little while to get into the mindset of Realm migrations, but thankfully once you do, you realise they're easier than you thought. :)

(Disclaimer: I work for Realm, but I use it in one of my own shipping iOS apps, where I've played with multiple migrations on real user data at this point. :) )

Termite answered 16/12, 2015 at 13:16 Comment(9)
I actually do. That's why I'm asking "How do I debug my migration btw? Like if my schema is already version 1 how do I change back to version 0 to test my schemaversion 1 schema"Cordoba
And you don't need write blocks inside migration? @_@Cordoba
Cause I actually have an Outlet class that has both region and area, what I'm trying to do now is link both of these togetherCordoba
Oh! Okay. I'll update my answer. Sadly migrations are only 1-way. You can't revert a Realm file back to version 0. If you want to rapidly test migrating a Realm file, use copies of the original Realm file.Termite
Then what about the accessing realm to get my outlet for example to update the region/area model?Cordoba
Um, sorry, what do you mean by outlet exactly? In any case, I've updated the answer now!Termite
Outlet is another model that holds both Area and Region.Cordoba
Oh! Whoops. That comment above makes sense now. Sorry! Hmm, that actually sounds to me like something that would be better off done manually, outside of a migration. For example, use the migration to add the Outlet model, and then before your app runs, check if there are 0 Outlet objects, and create them as needed.Termite
Ahh, alright. Thanks for the help :) This link would be my final question for now >.< #34326892Cordoba
M
0

Here is a possible solution imagining that older schema version was 0, and new one is 1:

let migrationBlock: MigrationBlock = { migration, oldSchemaVersion in

  if oldSchemaVersion < 1 {
    //Migrate Regions
    migration.enumerate(Region.className()) { oldObject, newObject in
      if oldSchemaVersion < 1 {

        //Get appropriate area object for this Region Object
        let area = areaForRegion(newObject)  //<-- implement this
        newObject.areas.append(area)
      }
    }

    //Migrate areas
    migration.enumerate(Area.className()) { oldObject, newObject in
      if oldSchemaVersion < 1 {

        //Get appropriate region object for this area Object and set up the relation
        let region = regionForArea(newObject)  //<-- implement this
        newObject.region = region
      }
    }

  }
    print("Migration complete.")
}

Realm.Configuration.defaultConfiguration = Realm.Configuration(schemaVersion: 1, migrationBlock: migrationBlock)

// print out all migrated objects in the default realm
// migration is performed implicitly on Realm access
print("Migrated Area objects in the default Realm: \(try! Realm().objects(Area))")
print("Migrated Region objects in the default Realm: \(try! Realm().objects(Region))")

Note: You will need to implement the methods that return you area for a given region and vice versa.

Malemute answered 16/12, 2015 at 9:7 Comment(5)
Sorry I'm very new in migration as you may know. But where can I implement region for area and area for region?Cordoba
Can I not just define them like.. let's say in string's case, ""(empty value) first?Cordoba
Sure, you can have region in the area as optional, and just set nil value during migration to it. And if you don't have any area to add yet, you can remove the migration code for Region class.Malemute
if oldSchemaVersion < 2 { newObject!["region"] = Region? } returns compile error :(Cordoba
How do I debug my migration btw? Like if my schema is already version 1 how do I change back to version 0 to test my schemaversion 1 schemaCordoba

© 2022 - 2024 — McMap. All rights reserved.