Realm Migration: Migrate an object into a List - swift

So, I've been trying to migrate my Realm schema, but I can't seem to do the following.
In the oldSchema, I have the following:
class Period: Object {
dynamic var weekday: Weekday! // this is just another Realm Object
}
In the newSchema, I'm trying to move the Weekday into a List of Weekday(s).
class Period: Object {
let weekdays: List<Weekday> = List<Weekday>()
}
When performing the Realm migration, how would I move the weekday object from the oldSchema into the weekdays list in the newSchema.
Thanks.

You can run a migration block under Realm Configuration.
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 2,
migrationBlock: { migration, oldSchemaVersion in
migration.enumerate(Period.className()) { oldObject, newObject in
// filter the versions where this change would take place
// if oldSchemaVersion < 1 {
// }...
newObject["weekdays"] = [oldObject["weekday"]]
})

Related

Realm Migration: Migrating objects to another

I've added a new Model object to my realm objects and I am trying to move data from my old realm object properties to this new object.
In the old Schema, I have the following:
class Item: Object {
#objc dynamic var image = ""
#objc dynamic var coverImage = ""
#objc dynamic var video = ""
}
In the new schema, I've added a new property called media
so now it's looking this this
class Item: Object {
#objc dynamic var image = ""
#objc dynamic var coverImage = ""
#objc dynamic var video = ""
#objc dynamic var media: Media?
}
I've also added this new Model object:
class Media: Object {
#objc dynamic var fullImage = ""
#objc dynamic var thumbnailImage = ""
#objc dynamic var video = ""
var item = LinkingObjects(fromType: Item.self, property: "media")
}
My goal is to move the data from the old Item objects to the new Media objects.
I was trying to do something like this, but I don't know how to migrate that linking object, any help in the right direction would be appreciated.
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
// enumerate first object
migration.enumerateObjects(ofType: Item.className()) { oldItem, newItem in
let image = oldItem?["image"] as! String
let coverImage = oldItem?["coverImage"] as! String
let video = oldItem?["video"] as! String
//migrate second object
migration.enumerateObjects(ofType: Media.className(), { (oldMedia, newMedia) in
})
}
}
})
You don't need to do anything with LinkingObjects, realm calculates those automatically when you query them.
All you'll need to do in your migration is set media to be a new Media object with the values you already have.
Other notes:
The second enumerateObjects isn't needed.
You can remove image, coverImage, and video from Item since you're moving those value to Media
Edit: This is what you would need to have in your migration.
let media = Media()
media.fullImage = oldItem?["image"] as! String
media.thumbnailImage = oldItem?["coverImage"] as! String
media.video = oldItem?["video"] as! String
newItem?["media"] = media
When adding objects to a project, the migration is super simple. In this case you're not changing or adding data to existing or new properties so it's even easier.
All that needs to be done is to increment the schemaVersion and implement your migration block. Suppose the prior version was 1, increment that to 2.
let config = Realm.Configuration (
schemaVersion: 2,
migrationBlock: { migration, oldSchemaVersion in
//nothing to do here since we not altering any data
})
Realm already knows your Object has data and that data will persist as it's not being altered. All of the Item objects will have the new media property added and linked to the new Media object.
See Local Migrations for more examples. The Updating values section is when you want to actually manipulate the existing data
Edit:
We now have a bit more information and what the OP is trying to do is to copy data from an existing object to a new object and then create a relationship between the objects. Here's the code that would do that.
The initial object is Item and the new object is Media. For this example I am copying the data in a Item property image to the Media property fullImage
let config = Realm.Configuration (
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
migration.enumerateObjects(ofType: Item.className() ) { oldObject, newObject in
let image = oldObject!["image"] as! String
let media = Media()
media.fullImage = image
newObject!["media"] = media
}
})

Getting invalid property name when trying to perform Realm migration

I'm having trouble with Realm giving me the error that a property of a given name does not exist for my object. But I know it does exist.
I've tried to follow the docs at https://realm.io/docs/swift/latest/#updating-values. I've searched for everything I can think of to find an applicable solution here and elsewhere, but nothing I've found works.
I previously performed a simple migration to just add properties to a different object within the same Realm. I just left the migration block empty and that worked fine. That migration should have set my schema from 0 to 1. If I run this with my schema set to 1 and to check for versions less than 2, it tells me that migration must be run in order to add these properties. The migration is running. I have a print statement for every time it executes. If I set the schema to 2, still checking for less than 2, I get the error for invalid property name for the old properties unless I uncomment them. Then I still get the error for the new properties.
Here's my Realm object. The commented out lines are the old properties I want to migrate from. The Int values are what I'm migrating to.
#objcMembers class Options: Object {
// dynamic var morningStartTime: Date?
// dynamic var afternoonStartTime: Date?
// dynamic var eveningStartTime: Date?
// dynamic var nightStartTime: Date?
dynamic var morningHour: Int = 7
dynamic var morningMinute: Int = 0
dynamic var afternoonHour: Int = 12
dynamic var afternoonMinute: Int = 0
dynamic var eveningHour: Int = 17
dynamic var eveningMinute: Int = 0
dynamic var nightHour: Int = 21
dynamic var nightMinute: Int = 0
dynamic var morningNotificationsOn: Bool = true
dynamic var afternoonNotificationsOn: Bool = true
dynamic var eveningNotificationsOn: Bool = true
dynamic var nightNotificationsOn: Bool = true
dynamic var firstItemAdded: Bool = false
dynamic var smartSnooze: Bool = false
dynamic var optionsKey = UUID().uuidString
override static func primaryKey() -> String? {
return "optionsKey"
}
}
My migration block:
let config = Realm.Configuration(
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
schemaVersion: 2,
// Set the block which will be called automatically when opening a Realm with
// a schema version lower than the one set above
migrationBlock: { migration, oldSchemaVersion in
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 2) {
migration.enumerateObjects(ofType: Options.className(), { (newObject, oldObject) in
let morningStartTime = oldObject!["morningStartTime"] as! Date?
let afternoonStartTime = oldObject!["afternoonStartTime"] as! Date?
let eveningStartTime = oldObject!["eveningStartTime"] as! Date?
let nightStartTime = oldObject!["nightStartTime"] as! Date?
newObject!["morningHour"] = self.getHour(date: morningStartTime)
newObject!["morningMinute"] = self.getMinute(date: morningStartTime)
newObject!["afternoonHour"] = self.getHour(date: afternoonStartTime)
newObject!["afternoonMinute"] = self.getMinute(date: afternoonStartTime)
newObject!["eveningHour"] = self.getHour(date: eveningStartTime)
newObject!["eveningMinute"] = self.getMinute(date: eveningStartTime)
newObject!["nightHour"] = self.getHour(date: nightStartTime)
newObject!["nightMinute"] = self.getMinute(date: nightStartTime)
})
}
})
getHour and getMinute are just functions I wrote to return an Int for the hour or minute from a Date. In case it's relevant, here they are.
func getHour(date: Date?) -> Int {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH"
let hour = dateFormatter.string(from: date!)
return Int(hour)!
}
func getMinute(date: Date?) -> Int {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "mm"
let minutes = dateFormatter.string(from: date!)
return Int(minutes)!
}
I know this is not the way to do it, but I made it work by taking a slightly more manual approach to the migration block. I uncommented the old properties in the Options object and changed my migration function to the following:
func migrateRealm() {
let configCheck = Realm.Configuration();
do {
let fileUrlIs = try schemaVersionAtURL(configCheck.fileURL!)
print("schema version \(fileUrlIs)")
} catch {
print(error)
}
print("performing realm migration")
let config = Realm.Configuration(
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
schemaVersion: 2,
// Set the block which will be called automatically when opening a Realm with
// a schema version lower than the one set above
migrationBlock: { migration, oldSchemaVersion in
print("oldSchemaVersion: \(oldSchemaVersion)")
if (oldSchemaVersion < 2) {
print("Migration block running")
DispatchQueue(label: self.realmDispatchQueueLabel).async {
autoreleasepool {
let realm = try! Realm()
let options = realm.object(ofType: Options.self, forPrimaryKey: self.optionsKey)
do {
try realm.write {
if let morningTime = options?.morningStartTime {
options?.morningHour = self.getHour(date: morningTime)
options?.morningMinute = self.getMinute(date: morningTime)
}
if let afternoonTime = options?.afternoonStartTime {
options?.afternoonHour = self.getHour(date: afternoonTime)
options?.afternoonMinute = self.getMinute(date: afternoonTime)
}
if let eveningTime = options?.eveningStartTime {
options?.eveningHour = self.getHour(date: eveningTime)
options?.eveningMinute = self.getMinute(date: eveningTime)
}
if let nightTime = options?.nightStartTime {
options?.nightHour = self.getHour(date: nightTime)
options?.nightMinute = self.getMinute(date: nightTime)
}
}
} catch {
print("Error with migration")
}
}
}
}
})
// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
_ = try! Realm()
}
This only worked if I queued it on another thread asynchronously. I imagine if this data had been necessary for my initial view controller, then it probably would have created a race condition that caused the app to crash. Luckily this only appears in a secondary view, so it had time to complete before these values are needed. I guess I'll have to remove the unused properties with an updated Realm schema in a future version of my app.

RealmCollectionChange modified parameter has multiple indexes when only one object is modified

Issue: When a single object is modified in Realm, the modified parameter in a RealmCollectionChange is passed multiple indexes
Details: Given a Person object which has a list of Dog Objects and a Dog object that has an owner property of type Person
class Dog: Object {
#objc dynamic var name = ""
#objc dynamic var owner: Person?
}
class Person: Object {
#objc dynamic var name = ""
let dogs = List<Dog>()
}
then a single person is created with two dogs
let person0 = Person()
person0.personName = "Leroy"
let dog0 = Dog()
dog0.name = "Spot"
dog0.owner = person0
let dog1 = Dog()
dog1.name = "Rex"
dog1.owner = person0
person0.dogs.append(dog0)
person0.dogs.append(dog1)
//write person0 to Realm which creates the person and two dogs
and the observe function
func doObserve() {
let realm = try! Realm()
let results = realm.objects(Dog.self)
notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
switch changes {
case .initial:
case .update(_, let deletions, let insertions, let modifications):
for index in deletions {
print("deleted object at index: \(index)")
}
for index in insertions {
print("inserted object at index: \(index)")
}
for index in modifications {
print("modified object at index: \(index)")
let object = results[index]
print(object) //prints dog0 and dog1 when dog1 is mod
}
case .error(let error):
fatalError("\(error)")
}
}
}
then, if the Realm file is opened with RealmBrowser (or using code in the App) and Dog1 (Rex) name is changed to Fluffy, the modified parameter within the RealmCollectionChange contains two indexes, 0 and 1 even though only dog1 (index 1) was modified.
Is this behavior correct and if so, is there another option to just get the (index to the) object that was modified?
(or am I totally missing something obvious?)
When you modify dog1, dog0.owner.dogs[1] has changed, so Realm reports that dog0 is modified as well.
There's currently no way to manually specify how deep of a path you want Realm to check for changes to an object, but you may be able to sidestep this behavior by changing Person's List<Dog> to LinkingObjects<Dog>, as inverse relationships are not followed when checking for changes. This also would remove the need to set the relationship in two places.

Swift diff realm.io without fetching it in advance

I was wondering if there is a possibility in realm.io (swift) to select all items from one "table" that are not in the other one.
Lets say you have 2 classes:
class A: Object {
dynamic var id: Int = 0
dynamic var text: String = ""
}
class B: Object {
dynamic var id: Int = 0
dynamic var value: Bool = false
}
Is it possible to get an result of items from A who's id is not present in B?
There is actually a very simple way to do this using NSPredicate on Realm filter API.
func fetch() throws -> [A] {
do {
// Create Realm
let realm = try Realm()
// Get B objects from Realm and put their IDs to [Int] array
let IdB: [Int] = realm.objects(B).map { $0.id }
// Create predicate
// Filter all items where property id is not present in array IdB
let predicateFilter = NSPredicate(format: "NOT (id IN %#)", IdB)
// Get all A objects from array using predicateFilter
let objectsA = realm.objects(A).filter(predicateFilter)
// Return the [A] array
return objectsA.map { $0 }
} catch {
// Throw an error if any
throw error
}
}
Also note that all objects from fetched using Realm are lazy loaded which means that this method is also very fast. From the documentation:
All queries (including queries and property access) are lazy in Realm. Data is only read when the properties are accessed.

Realm Swift Migration to add primary key - Not writing data

I am using RealmSwift and I want to add a new Primary Key to one of my objects.
I have updated the Realm Object to this
class Trip: Object {
dynamic var id = ""
dynamic var start = ""
dynamic var startAddress = ""
dynamic var end = ""
dynamic var endAddress = ""
dynamic var purpose = ""
dynamic var distance = 0.0
dynamic var tripDate = NSDate()
dynamic var month = 0
dynamic var year = 0
dynamic var isWalking = false
dynamic var isReturn = false
func primaryKey() -> String {
return id
}
}
And now I want to migrate to the new version and insert a UUID String as the primary key for any existing record.
The migration works in that the new 'id' field is created, but the UUID strings are not written into the record. No errors displayed on console.
This is what I have added to my AppDelegate application(application:didFinishLaunchingWithOptions:)
I must be missing something, but I cannot determine what, from the documentation or examples on the Realm site.
let config = Realm.Configuration(
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
schemaVersion: 1,
// Set the block which will be called automatically when opening a Realm with
// a schema version lower than the one set above
migrationBlock: { migration, oldSchemaVersion in
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
})
// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
let realm = try! Realm()
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
// The enumerate(_:_:) method iterates
// over every Trip object stored in the Realm file
migration.enumerate(Trip.className()) { oldObject, newObject in
let id = NSUUID().UUIDString
newObject!["id"] = id
}
}
})
The code you've provided sets the default configuration with your migration block after you've already opened the Realm. You need to set the default configuration prior to opening the default Realm in order for the migration block to be used.