Realm Swift Migration to add primary key - Not writing data - swift

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.

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.

Primary Key is not defined in model, but primary key error is shown

I'm getting this error...
2017-03-26 17:34:49.104919 Mobile[518:254067] ***
Terminating app due to uncaught exception 'RLMException',
reason: 'Only 'string' and 'int' properties can be designated the primary key'
In the prepopulated realm I have all string columns, except for 2 Double columns for lat,lng.
Here's my model:
import Foundation
import RealmSwift
class Destination: Object{
dynamic var destinationSlackChannelName = ""
dynamic var destinationSlackChannelId = ""
dynamic var destinationName = ""
dynamic var destinationType = ""
dynamic var destinationCode = ""
dynamic var destinationRegionCode = ""
dynamic var destinationSiteSlackChannelName = ""
dynamic var destinationCity = ""
dynamic var destinationCountry = ""
dynamic var destinationStatus = ""
dynamic var destinationLastUpdated = ""
dynamic var lat:Double = 0.0
dynamic var lng:Double = 0.0
}
Here's how I'm configuring and querying realm in a singleton called RealmManager....
func getHebronDestinations() -> Results<Destination> {
let bundleUrl = Bundle.main.url(forResource: "default", withExtension: "realm")
let config = Realm.Configuration(
fileURL: bundleUrl,
readOnly: true,
schemaVersion: 0,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
Log.info?.message("\(oldSchemaVersion)")
Log.info?.message("wtf")
}
if (oldSchemaVersion < 2) {
Log.info?.message("\(oldSchemaVersion)")
}
Log.info?.message("Realm migration did run") // Log to know migration was executed
}
)
let realm = try! Realm(configuration: config)
let naoHebronResults = realm
.objects(Destination.self)
//.filter("destinationRegionCode == 'nao' AND destinationCode == 'heb'")
Log.info?.message("\(naoHebronResults)")
for res in naoHebronResults{
Log.info?.message(res.destinationName)
}
return naoHebronResults
}
And here's the function call in a ViewController...
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad")
let realmManager = RealmManager.shared
let hebDevices = realmManager.getHebronDestinations()
print(hebDevices)
}
Why am I still getting that primary key error, if there is no primary key in the model?
I made this realm file from a csv via realm browser...it worked once before.
It doesn't matter if you don't have a Primary Key on models that are "in play". I had models that weren't inside the pre populated realm yet, and THOSE models had Primary Keys on them.
I thought only models "in play/inside the realm" counted.
Any Object subclasses with a Primary Key will cause the error...
Terminating app due to uncaught exception 'RLMException',
reason: 'Only 'string' and 'int' properties can be designated the
primary key'
thanks to https://github.com/bdash
and his push
Can you do a search in your project for a function named primaryKey to
see if you've overlooked one?
and also https://stackoverflow.com/users/3736093/fahim

How to get unique value from Realm database in swift

I do news application in swift using Realm database. In my database have same news categories. How to get unique value from Realm database?
I use primary key
class News: Object {
dynamic var newsID: String = ""
dynamic var newsTitle: String = ""
dynamic var newsFullText: String = ""
dynamic var newsImage: String = ""
dynamic var newsAutor: String = ""
dynamic var newsCommentCount: String = ""
dynamic var newsSeenCount: String = ""
dynamic var newsDate: String = ""
dynamic var newsCategory: String = ""
override static func primaryKey() -> String? {
return "newsID"
}
}
I'm try to get
let realm = try! Realm()
let menuName = realm.objects(News)
for i in menuName.filter("newsCategory") {
nameLabel.text = i.newsCategory
}
But it is not work.
Starting from Realm 3.10 it's now possible to
Add Results.distinct(by:) / -[RLMResults
distinctResultsUsingKeyPaths:], which return a Results containing only
objects with unique values at the given key paths.
Old response - before Realm 3.10
It is not possible yet to obtain a "distinct"-like functonality from a Realm query (track the open issue here)
However, there are some workarounds suggested in the thread I mentioned above (please read it to get the full context), by user apocolipse :
// Query all users
let allUsers = Realm().objects(User)
// Map out the user types
let allTypes = map(allUsers) { $0.type }
// Fun part: start with empty array [], add in element to reduced array if its not already in, else add empty array
let distinctTypes = reduce(allTypes, []) { $0 + (!contains($0, $1) ? [$1] : [] )
Or better yet, a different approach using Sets (by user jpsim):
let distinctTypes = Set(Realm().objects(User).valueForKey("type") as! [String])
Obviously the workarounds aren't as efficient as a direct DB query, so use with care (and testing under realistic load).

Realm Migration: Migrate an object into a List

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"]]
})