Difficulties working with RealmSwift.List to implement "To-Many Relationship"

Hello everybody !

I’m working on an iOS app with Realm and I’m having some difficulties to work with the RealmSwift.List type. I’m trying to implement the “To-Many Relationship” described here: https://realm.io/docs/swift/0.102.0/

I have 2 different collections “Items” & “QuoteInformation” in 2 different databases. In “QuoteInformation” I want to have a var (called “quoteItems”) which is a list of “Items”.

Here is how I defined the quoteItems var in the QuoteInformation schema:

"quoteItems": {
      "bsonType": "array",
      "items": {
        "bsonType": "objectId"
      }
}

And I added the following dependency between quoteItems and the “_id” property of Items:

{
  "quoteItems": {
    "foreign_key": "_id",
    "ref": "#/relationship/mongodb-atlas/Libraries/Items",
    "is_list": true
  }
}

In my iOS app, I append some Items in my quoteItems list but when I try to upload the quoteItems with realm.add() I got the following error:
"Attempting to create an object of type ‘Items’ with an existing primary key value ‘5f0a27e2bf392975530711d3’ "

I don’t understand why Realm is thinking that i’m adding a new Items while i’m just trying to save it in a list.

Thanks for you help ! :slight_smile:

@Julien_Chouvet What does your Realm Schema look like? I think you will want to use the .append() method - https://realm.io/docs/swift/latest/api/Classes/List/append(objectsIn:).html

The question is a bit confusing as the question includes two totally separate definitions for quoteItems and we don’t know what quoteItems are - is that a Realm object?

As Ian, mentioned we also don’t know what QuoteInformation looks like.

Lastly, the question states “quoteItems is a var which is a list of items”, but within quoteItems, there’s an items (list?) inside that?

Can you clarify the question and show your actual objects and explain the relationship?

Hi all!

Thanks for your help! I find my mistake and now it’s working.
However, I have another question: I’m trying to get a specific data that is in my Quoteinformation collection. To do so, I want to filter the objects by _id which is of type ObjectId but:

  • When I do: self .realm?.objects(QuoteInformation. self ).filter("_id == $0", self ._id)
    I got the following error: Unable to parse the format string "_id == $0"Unable to parse the format string “_id == $0”

  • When I do: let result = self .realm?.objects(QuoteInformation. self ).filter("_id == “$0"”, self ._id)
    I got the following error: “Expected object of type object id for property ‘_id’ on object of type ‘QuoteInformation’, but received: $0”

Do you know what is the good syntax?

Thanks!

$0 is used in swift filters.

I think you want to use a placeholder

"_id == %@", some_var

Have a look at Realm Filtering for some additional reading

1 Like

I come back to this topic because I still have a problem.
To clarify, here are the realm schemas of my 2 collections “Items” and “QuoteInformation”:

***** Items *****

{
  "title": "Items",
  "bsonType": "object",
  "required": [
    "_id",
    "_parentId"
  ],
  "properties": {
    "_id": {
      "bsonType": "objectId"
    },
    "_parentId": {
      "bsonType": "string"
    },
    "name": {
      "bsonType": "string"
    }
  }
}

***** QuoteInformation Schema *****

{
  "title": "QuoteInformation",
  "bsonType": "object",
  "required": [
    "_id",
    "_parentId"
  ],
  "properties": {
    "_id": {
      "bsonType": "objectId"
    },
    "_parentId": {
      "bsonType": "string"
    },
    "title": {
      "bsonType": "string"
    },
    "quoteItems": {
      "bsonType": "array",
      "items": {
        "bsonType": "objectId"
      }
    }
  }
}

***** QuoteInformation Relationship *****

{
    "quoteItems": {
        "ref": "#/relationship/mongodb-atlas/Libraries/Items",
        "foreign_key": "_id",
        "is_list": true
    }
}

In my iOS app, I tried to append an instance of Items in the quoteItems, like this:

let result = self.realm?.objects(QuoteInformation.self).filter("_id == %@", self._id)
if let quoteInformation = result?.first{
        try! self.quoteInformationRealm?.write{
            quoteInformation.quoteItems.append(item)
        }
}

But when I do that I have the following error:

Sync: Connection[2]: Session[2]: Received: ERROR(error_code=212, message_size=22, try_again=0)

Tell me if you need more information.
Thanks for your help!

Two things.

It’s much easier for us to understand what you’re doing when we can see the actual Realm objects as they are defined in code.

Secondly, the included section of code is a bit unclear. We don’t know what

try! self.quoteInformationRealm?.write

is and it looks like you trying to append an item

quoteInformation.quoteItems.append(item)

but we don’t know what item is as it’s not shown, and it appears quoteItems is an array, not a realm object (?)

Can you update your question with the actual Realm Object models as code as well a providing a bit more info about that section of code?

Yes sorry there is a mistake in the section of code, I wanted to simplify it so I replaced self.quoteInformationRealm by self.realm. Anyway, here is the good one:

let result = self.realm?.objects(QuoteInformation.self).filter("_id == %@", self._id)
if let quoteInformation = result?.first{
    try! self.realm?.write{
        quoteInformation.quoteItems.append(item)
    }
}

The item is of type Items and created with the convenience init() function (see below) like:

Items(_parentId: "parent_id", name: "item_name")

Here below are the Realm objects as they are defined in code (coming from the SDKs -> Data Models of the Realm app):

***** Items *****

class Items: Object {
    @objc dynamic var _id: ObjectId = ObjectId.generate()
    @objc dynamic var _parentId: String = ""
    @objc dynamic var name: String? = nil
    override static func primaryKey() -> String? {
         return "_id"
     }

    convenience init(_id: ObjectId = ObjectId.generate(), _parentId: String = "", name: String? = nil) {
        self.init()
        self._id = _id
        self._parentId = _parentId
        self.name = name
    }
}

***** QuoteInformation *****

class QuoteInformation: Object {
    @objc dynamic var _id: ObjectId = ObjectId.generate()
    @objc dynamic var _parentId: String = ""
    let quoteItems = RealmSwift.List<Items>()
    @objc dynamic var title: String? = nil
    override static func primaryKey() -> String? {
        return "_id"
    }
}

Not sure why you’re defining a class var that will automativally populate but you’re also populating it within the init. So here

@objc dynamic var _id: ObjectId = ObjectId.generate()

Will generate an object id when the object is initialized.

but then this

convenience init(_id: ObjectId = ObjectId.generate()

will auto generate an object id when initialized? I don’t think you want that. This would be more appropriate when a new object is created as self.init will populate the ObjectId

convenience init(_parentId: String = "", name: String? = nil) {
     self.init()
     self._parentId = _parentId
     self.name = name
}

Yes you’re right! However this does not solve my problem with the quoteItems list

Your initial description of the issue was this

I try to upload the quoteItems with realm.add() I got the following error:

Your current code however, is showing this

quoteInformation.quoteItems.append(item)

so we need some clarification; you are using realm.add or no? Why are you using ObjectId here?

Also, your code essentially works for me. I modified the two classes to use UUID instead of ObjectId

class Items: Object {
    @objc dynamic var _id = UUID().uuidString
    @objc dynamic var _parentId: String = ""
    @objc dynamic var name: String? = nil
    override static func primaryKey() -> String? {
         return "_id"
     }

    convenience init(_parentId: String = "", name: String? = nil) {
        self.init()
        self._parentId = _parentId
        self.name = name
    }
}

class QuoteInformation: Object {
    @objc dynamic var _id = UUID().uuidString
    @objc dynamic var _parentId: String = ""
    let quoteItems = RealmSwift.List<Items>()
    @objc dynamic var title: String? = nil
    override static func primaryKey() -> String? {
        return "_id"
    }
}

then build two objects

        let item = Items(_parentId: "1", name: "some name")
        let qi = QuoteInformation()

then added the item to the QuoteInformation()

        qi.quoteItems.append(item)

then print(qi) shows

QuoteInformation {
    _id = 6EB9A252-0D8B-4AAA-BE58-66A2531A8962;
    _parentId = ;
    quoteItems = List<Items> <0x60000177d780> (
        [0] Items {
            _id = 1AC4E456-0707-41ED-9EB6-735750D83124;
            _parentId = 1;
            name = some name;
        }
    );
    title = (null);
}

So as you can see, it works correctly.

Hello :slight_smile:

I’m using ObjectId because this is what is used in the SDKs/Data Models of the Realm app website.

Regarding quoteInformation.quoteItems.append(item), if I print my quoteInformation instance I also have the same good result but when I look at my QuoteInformation collection online nothing changed.

Instead of trying to append an Items in quoteItems, I tried to modify the title like in the following code and it is working:

let result = self.realm?.objects(QuoteInformation.self).filter("_id == %@", self._id)
if let quoteInformation = result?.first{
    try! self.realm?.write{
        //quoteInformation.quoteItems.append(item)
        quoteInformation.title = "abcd"
    }
}

However, I noticed that for the data stored online in my QuoteInformation collection, the quoteItems attribute is not present, but I don’t know why it is not initialized like the others are.

Well, actually no. The current Realm Guide suggests using UUID as shown in the Models section on their website under the Auto-Incrementing section.

Also note the documentation linked in your original question is pretty outdated so don’t go by that.

That being said, the ObjectId is part of the MongoDB Realm BETA SDK. As with any BETA software, you never know! In this case however, it’s probably not causing any issues as it’s function is similar.

Now that we know you’re using MongoDB Ream Sync BETA (per the screenshot) there’s more to this.

The issue here is there are three versions of the documentation. The current Realm documentation as I linked above, the BETA MongoDB Realm Documentation which is where you got ObjectId from, and then the third is the BETA MongoDB Realm Sync documentation. All three are slightly different depending on what you’re doing; Local Current Realm, Local Beta MongoDB Realm, or Sync Beta MongoDB Realm.

Assuming your schema is created in code with your Realm Objects and Atlas is in Development mode, you’ll need to go through the Sync guide because Sync’d objects need to include a partition property key, which needs to match the partition key you set up when configuring your Realm within Atlas. See Partition Atlas Data.

Then when writing to a sync’d realm you can do this

// Write to the realm. No special syntax required for synced realms.
try! realm.write {
  realm.add(Task(partition: partitionValue, name: "My task"))
}

Note this comment No special syntax required for synced realms. in the guide is inaccurate as writing to a sync’d realm DOES require special syntax - it must include the partitionValue which is different than when writing to a local Realm with either the current Realm or Beta MongoDB Realm (not sync)

Thanks for clarifying!

Assuming your schema is created in code with your Realm Objects and Atlas is in Development mode, you’ll need to go through the Sync guide because Sync’d objects need to include a partition property key, which needs to match the partition key you set up when configuring your Realm within Atlas. See Partition Atlas Data.

As I mentionned previously (post #6), my shcema is defined in the Realm web app (and I’m not using the development mode).
In my Swift code I use the sync (with _parentId as partition key) in others situations and everyting is working well. I just have a problem here with the quoteItems list. Do you think it can come from the fact that the quoteItems attribute is not initialized like the others attributes are in the QuoteInformation collection (cf post #12)

Thanks!

The Items object is being initialized correctly with the partition key

class Items: Object {
    @objc dynamic var _id = UUID().uuidString
    @objc dynamic var _parentId: String = ""
    @objc dynamic var name: String? = nil
    override static func primaryKey() -> String? {
         return "_id"
     }

    convenience init(_parentId: String = "", name: String? = nil) {
        self.init()
        self._parentId = _parentId
        self.name = name
    }
}

but I don’t see your QuoteInformation class being initialized in the same way _parentId is just an empty string, so it won’t be synced.

class QuoteInformation: Object {
    @objc dynamic var _id = UUID().uuidString
    @objc dynamic var _parentId: String = "" //<-- this needs to be populated
    let quoteItems = RealmSwift.List<Items>()
    @objc dynamic var title: String? = nil
    override static func primaryKey() -> String? {
        return "_id"
    }
}

My QuoteInformation is init (with a not empty _parentId) and saved in Realm in another part of my app, that’s why I first retrieve it before trying to append in quoteItems. Moreover, I know that it is synced because as I said in the post #12 I’m able to edit others attributes like the title:

As I mentioned in an above comment - the code in that comment works correctly for me. At this point the only thing we don’t know is how/what the ‘item’ you’re attempting to append looks like

quoteInformation.quoteItems.append(item)

I assume it’s an Items object but if it’s not properly initialized, it will not be sync’d. So in this section of code, print the item and let’s see what it looks like

let result = self.realm?.objects(QuoteInformation.self).filter("_id == %@", self._id)
if let quoteInformation = result?.first{
    print(quoteInformation) //<---------- Add this
    try! self.realm?.write{
        print(item) // <---------- Add this
        quoteInformation.title = "Hello, World" // <- see if this is sync'd
    }
}

Here is the result:

QuoteInformation {
    _id = 5f170ba9f83817673abb764f;
    _parentId = 5f0c6b003a8a4b66dbacf15e;
    quoteItems = List<Items> <0x6000019603c0> (

    );
    title = (null);
}

Items {
    _id = 5f170bc1f83817673abb76cf;
    _parentId = 5f0a1b534699cbf17d4536b3;
    name = name of the item;
}

And the title of the QuoteInformation has been correctly edited to “Hello, World”.

Well, as I mentioned a few times previously, if the partitionValue of _parentId are different it’s not going to work. These objects are not in the same app partition:

QuoteInformation {
    _parentId = 5f0c6b003a8a4b66dbacf15e;

and

Items {
    _parentId = 5f0a1b534699cbf17d4536b3;

This all comes back to how your item object is being initialized

quoteInformation.quoteItems.append(item) // <--- HERE we need to see how this item is init'ed

can you please show is the code of that process?

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.