Changing primary key property name on Realm Swift object model - ios

I have a Realm (Swift) object that's existed in my app for about a year now. The model for this object looks like this:
class ChatMessage: Object {
#objc dynamic var messageId: Int = generateId()
// other properties omitted
override static func primaryKey() -> String? {
return "messageId"
}
}
Now, I find myself needing to effectively rename the "messageId" property to "id" and make "id" the primary key. I need to do this because I need this object to conform to a particular protocol (from a framework that I don't control) and that protocol specifies that I need a "messageId" property of type String. Renaming seems to be the most straight forward explanation for what I'm trying to do but I realize that this might also be explained by saying that I want to:
create a new "id" property (of type Int)
copy the existing value of "messageId" to the "id" property (during migration)
change the primary key to use "id" instead of "messageId"
In effect, what I want the object to look like after the change is:
class ChatMessage: Object {
#objc dynamic var id: Int = generateId()
// other properties omitted
override static func primaryKey() -> String? {
return "id"
}
}
I have a migration block defined and I've tried a few different ways of accomplishing this but so far haven't had any luck. Without the migration block, I get the following in the Xcode debug console:
ERROR - Failed to open Realm! Error Domain=io.realm Code=10 "Migration is required due to the following errors:
- Property 'ChatMessage.messageId' has been removed.
- Property 'ChatMessage.id' has been added.
- Primary Key for class 'ChatMessage' has changed from 'messageId' to 'id'.
For what it's worth, I'm using Realm 5.50 and I've read through the docs at https://realm.io/docs/swift/latest/ and I see this statement:
Once an object with a primary key is added to a Realm, the primary key cannot be changed.
That makes it sound like this isn't possible to do with Realm anymore but I'm wondering if anyone can confirm that or if there is, in fact, a way to achieve this? Thanks!

You can actually change the primary key within a migration block. Suppose you have a object that looks like this, with 'localID' being the primary key
class TestClass: Object {
#objc dynamic var localID = UUID().uuidString
override static func primaryKey() -> String? {
return "localID"
}
}
and we want to change the primary key to _id. The migration block would look something like this
let vers = UInt64(3)
let config = Realm.Configuration( schemaVersion: vers, migrationBlock: { migration, oldSchemaVersion in
print("oldSchemaVersion: \(oldSchemaVersion)")
if (oldSchemaVersion < vers) {
print(" performing migration")
migration.enumerateObjects(ofType: TestClass.className()) { oldItem, newItem in
let oldId = oldItem!["localID"]
newItem!["_id"] = oldId
}
}
})
I would suggest using _id instead of id so it will be compatible with MongoDB Realm in the future. See Define a Realm Schema

One day someone will come looking for this in flutter:
The simple answer is to use the annotation #MapTo("_id")
class _Car {
#PrimaryKey()
#MapTo("_id")
late ObjectId id;
...
}

Related

Why do my linking objects not have a Realm?

In my iOS app, I get the following exception:
'Linking objects notifications are only supported on managed objects.'
when I try to add an observer block:
y.xxx.observe { ... }
to a property that is defined as such:
class Y: Object {
...
let xxx = LinkingObjects(fromType: X.self, property: "y")
...
}
I believe this means that y.xxx does not have a Realm, and indeed I can see in the debugger that y.xxx.realm is nil. However, y.realm is NOT nil.
How can the linking objects not have a Realm if the object I am linking to does have one?
For completeness, this is how Class X is defined:
class X: Object {
...
#Persisted var y: Y?
...
}
Realm version 10.11.0, RealmDatabase version 11.1.1.
Context: I am in the last phase of migrating an app that was originally written in ObjC to be purely in Swift. This means switching to the Swift version of Realm. I have not encountered this problem in the previous version of the app that is largely the same code base except that it uses a very old version of the Realm framework and the Realm objects are defined in ObjC.
You can add an observer to a linking objects property as long as the objects are managed. Let me set this up starting with the PersonClass who has a List property of DogClass objects
class PersonClass: Object {
#objc dynamic var _id = ObjectId.generate()
#objc dynamic var name = "Jay"
let dogList = List<DogClass>()
override static func primaryKey() -> String? {
return "_id"
}
}
and then our DogClass has an inverse relationship to the PersonClass objects
class DogClass: Object {
#objc dynamic var _id = ObjectId.generate()
#objc dynamic var name = ""
let linkingOwners = LinkingObjects(fromType: PersonClass.self, property: "dogList")
override static func primaryKey() -> String? {
return "_id"
}
}
Then suppose we want to observe Spot's linkingOwners - both Jay and Cindy had Spot added to their dogList previously
let spot = realm.objects(DogClass.self).filter("name == 'Spot'").first!
self.peopleToken = spot.linkingOwners.observe { changes in
switch changes {
case .initial(let dogs):
print(" spots owners have been loaded")
case .update(_, let deletions, let insertions, let modifications ):
print(" something changed in spots owners")
case .error(let error):
print(error.localizedDescription)
}
}
Running this section of code outputs this to console and adds the observer the linkingObjects
spots owners have been loaded
Then, lets make a change to one of the owners properties
let jay = realm.objects(PersonClass.self).filter("name == 'Jay'").first!
try! realm.write {
jay.name = "Jay, Spots owner"
}
that last piece of code will output this to the console
something changed in spots owners
The above code creates a PersonClass object and a DogClass object object with a inverse relationship. The code then adds an observer the linkingObjects (PersonClass) and fires when one of them changes.
Turns out the linking objects now have to be declared like so:
class Y: Object {
...
#Persisted(originProperty: "y") var xxx: LinkingObjects<X>
...
}
I am not sure if the declaration style I used in my question is still supposed to be valid and if this is a bug, but using the new style gets rid of the exception.

realm-cocoa save an empty object rather than object I passed

I met a problem in realm-cocoa 2.8.0(in 2.7.0 it works good) which is when I want to save an object into the realm file, I saw an empty object with default value is saved into the realm rather than the object I created(even the primary key is different.)
Eg.
class XXXRealmObject: RLMObject {
#objc dynamic var id: String = UUID().uuidString.lowercased()
#objc dynamic var name: String = ""
#objc init(name: String) {
self.name = name
super.init()
}
#objc override init() {
super.init()
}
override class func primaryKey() -> String {
return "id"
}
}
let obj = XXXRealmObject(name: "jojo")
let realm = try! RLMRealm(configuration: .default())
try? realm.transaction {
*breakpoint*
realm.addOrUpdate(object)
}
I add a before realm.addOrUpdate(object) and print the object, it show correct object, but after realm.addOrUpdate(object) get executed, in realm file, I can only see an object
{
id: 169e6bc2-9b34-44ae-8ac3-70e6b9145adc,
name: ""
}
and the id is also different from what I saw in break point. It looks like Realm create an object rather use the object I passed in. I am asking for some help here.
So what will cause realm create an empty object(maybe default value?) rather than save the object I passed. I just want to get some possible reasons here.
I think I got it, in my project, we have a category for NSObject which include a method called objectForKey, and in Realm's src, when we read value from RLMObject, we check if it can response to objectForKey, normally it should return false and keep executing following code to get the real value, but in my project, the code will return nil because it's not a dictionary.
So will close this

How can i have custom (Computed) property in objectmapper?

I am using Objectmapper and Realm for my project.
I have an object like following
class File
{
dynamic var name
dynamic var folder
dynamic var path // This is not coming from JSON // this should be combination of both name+folder
}
I thought of writing a computed property to achieve this but Realm does not support computed properties as primary key.
But I should use this as primary key. Is there any way I can manipulate to add that value after coming from server response.
Note: I am using AlamofireObjectMapper.
I am using the following method which parses the server response and gives me the model object.
Alamofire.request(router).responseObject{ (response: DataResponse<T>) in
{
let myModel = response.result.value // Parsed object
===== What can i do here to achieve my requirement=====
}
You should really consider having some kind of id as the primary key and not computing it from other properties (what happens if they are empty or the computation goes wrong? You'd be left without a valid primary key).
However, if you really need to, you could try
let realm = try Realm()
try realm.write {
items.forEach({ (item) in
item.path = item.name + item.folder
}
realm.add(items, update: true)
}
and don't forget to define path as the primary key in the File class:
class File
{
dynamic var name
dynamic var folder
dynamic var path // This is not coming from JSON // this should be combination of both name+folder
override static func primaryKey() -> String? {
return "path"
}
}

Use Realm Object Directly (i.e as CollectionView Data Source)

So i have this Realm Object class :
import Realm
import RealmSwift
class Realm_item: Object {
var item_ID : String!
required init() {
super.init()
}
// And this one too
required override init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}
// Now go nuts creating your own constructor
init(myCustomValue: String) {
self.item_ID = myCustomValue
super.init()
}
override class func primaryKey() -> String {
return "item_ID"
}
}
Than i am trying to initialize it, but it simply stuck, with no exception or error/crash.
let item = Realm_item(myCustomValue: "SampleString")
self.dataSource.append(item)
There is few comments I have on your code.
item_ID should be dynamic
it's better to define a default value for item_ID instead of making it optional
you should not to create or override init and only create custom init(s) as convenience
import Realm is not needed import RealmSwift is enough.
The code should look like this.
import RealmSwift
class Realm_item: Object {
dynamic var item_ID : String = ""
// You should only define init(s) as convenience and call self.init() inside it.
convenience init(myCustomValue: String) {
self.init()
self.item_ID = myCustomValue
}
override class func primaryKey() -> String {
return "item_ID"
}
}
Then you use it like the way you do.
let item = Realm_item(myCustomValue: "SampleString")
self.dataSource.append(item)
I hope this helps. Thanks.
Update:
What does dynamic keyword mean?? pleas see this Answer
Why do we use dynamic variables with realm? as mensioned in Realm
Swift Docs
Realm model properties need the dynamic var attribute in order for
these properties to become accessors for the underlying database data.
There are two exceptions to this: List and RealmOptional properties
cannot be declared as dynamic because generic properties cannot be
represented in the Objective-C runtime, which is used for dynamic
dispatch of dynamic properties, and should always be declared with
let.
Is it a good practice to use Realm objects as DataSource? The way
you are using in the code sample you have dataSource as
Array<Realm_Item> is a good way as the array size will not change
automatically while the objects will be updated automatically (if
there is other part of the code modifying it)

Realm primary key migration

I want to migrate my realm schema to a new version. Therefor the removal of my primary key is needed.
Old schema:
class StudyState : Object
{
dynamic var name = ""
dynamic var x = ""
dynamic var y = ""
override static func primaryKey() -> String? {
return "name"
}
}
New schema:
class StudyState : Object
{
dynamic var name = ""
dynamic var x = ""
dynamic var y = ""
}
Without migration, realm will fail with
'RLMException', reason: 'Migration is required for object type 'StudyState' due to the following errors:
- Property 'name' is no longer a primary key.'
I tried this migration block, which failed too:
migration.enumerate(StudyState.className()) { oldObject, newObject in
newObject?["deleted"] = false
newObject?["primaryKeyProperty"] = ""
}
'RLMException', reason: 'Invalid property name'
Is there a way to remove the primary key when migrating realm to a new schema version?
You do not need to do anything in migration block if you only remove the primary key annotation.
But there is a need to increase the schema version because schema definitions changed.
Like below:
// You have to migrate Realm BEFORE open Realm if you changed schema definitions
setSchemaVersion(1, Realm.defaultPath) { (migration, oldSchemaVersion) -> Void in
if oldSchemaVersion < 1 {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
}
let realm = Realm()
...

Resources