Generating Unmanaged Realm Objects from Equivalent MongoDB Atlas BSON Documents

I’m working on a Swift iOS app using Realm Sync and MongoDB Atlas. It’s a photo editing app, and I want people to be able to create filters that they have write access to, and be able to share them, so that other users can have read-only access to them to download them on their phone.

I’m able to sign in, open a realm, create filters, store them, and access them.

However, I’d like to run a query for all filters available to download (i.e. those which aren’t owned by me). My data is partitioned by the user_id property.

Here is the schema for my filters:

{
  "title": "DFFilter",
  "bsonType": "object",
  "required": [
    "_id",
    "user_id",
    "name"
  ],
  "properties": {
    "_id": {
      "bsonType": "objectId"
    },
    "user_id": {
      "bsonType": "string"
    },
    "name": {
      "bsonType": "string"
    },
    "edits": {
      "bsonType": "objectId"
    }
  }
}

And here is my equivalent swift Object:

@objc public class DFFilter : Object {
    @objc dynamic public var _id: ObjectId = ObjectId.generate()
    
    // Partition Key
    @objc dynamic var user_id: String = ""

    @objc dynamic public var name: String = ""
    @objc dynamic public var edits: DFEdits? = nil
    
    override public static func primaryKey() -> String? {
        return "_id"
    }
    // ...

And here is how I’m performing the query:

        let collection = database.collection(withName: "DFFilter")

        let filterDocument : Document = [
            "user_id": [
                "$ne": AnyBSON(forUser.id)
            ]
        ]

        collection.find(filter: filterDocument) { result in
            switch result {
            case .failure(let error):
                print(error)
            case .success(let documents):
                print(documents)
                completion(documents.map({ document in
                    let dfFilter = DFFilter(value: document)
                    print (dfFilter)
                    return "foo" // just a placeholder for now
                }))
            }
        }

However, the initializer is failing to create a local unmanaged DFFilter object from the BSON Document I’m getting from Realm:

Terminating app due to uncaught exception 'RLMException', reason: 'Invalid value 'RealmSwift.AnyBSON.objectId(6089b6e38c3fafc3e01654b1)' of type '__SwiftValue' for 'object id' property 'DFFilter._id'.'

Here’s what the BSON document looks like when I print it in the console:

(lldb) po document
▿ 4 elements
  ▿ 0 : 2 elements
    - key : "_id"
    ▿ value : Optional<AnyBSON>
      ▿ some : AnyBSON
        - objectId : 6089b6e38c3fafc3e01654b1
  ▿ 1 : 2 elements
    - key : "name"
    ▿ value : Optional<AnyBSON>
      ▿ some : AnyBSON
        - string : "Hey Hey"
  ▿ 2 : 2 elements
    - key : "user_id"
    ▿ value : Optional<AnyBSON>
      ▿ some : AnyBSON
        - string : "6089b62f9c0f6a24a1a5794b"
  ▿ 3 : 2 elements
    - key : "edits"
    ▿ value : Optional<AnyBSON>
      ▿ some : AnyBSON
        - objectId : 6089b6e38c3fafc3e01654b2

I’ve tried search around for answers but I’m coming up blank. This indicates to me that potentially my whole approach to this problem might be mistaken?

It is worth pointing out that the edits property of DFFilter which you see in the schema is a different object of type DFEdits. I’m not entirely sure how MongoDB can resolve these links?

Hi @Majd_Taby, welcome to the community forum!

I’d like to suggest a slightly different strategy (feel free to ignore if it doesn’t fit your use case)…

It would be nice if the filters for all users were synced to the mobile app (so that you can still browse them while offline).

Your current partitioning key (user_id) doesn’t allow that and so I’d suggest replacing it with one named partition. For the documents that should only be visible to their owner, you’d set partition: "user=7567365873487465783" and then you can bind a function to your sync permissions to check that the requesting user is the only one that accesses those docs. The filters (either the originals or a copy created using Realm database triggers) can then be stored in documents where you set partition: "anyone=read"). The sync rules would then prevent non-owners from updating other people’s filters.

The downside to this approach is that all of the filters (from all users) are stored on the mobile device and so you’d need to assess whether the storage impact is justified by being able to access them when offline.

I’ve a new article that will hopefully go live tomorrow that covers various partitioning strategies – I’ll try to remember to circle back here with the link once it’s live (but if I forget, then it will appear in this list: https://www.mongodb.com/learn/?products=Mobile).

Thanks for the response, @Andrew_Morgan.

I don’t think it makes sense for us to store every single shared feature globally on every customer’s device.

Let me try to ask the question more directly:

Should I safely assume that I can query Documents from MongoDB with a schema of DFFilter, and initialize an object of type DFFilter locally in my client SDK?

This really sounds like what init(value:) is intended to be for in the Object class definition, but I’m really surprised that it’s failing when trying to populate the object_id.

Is this just a bug in RealmSwift? Or do I have incorrect expectations? Do I need to convert the BSON document to a more direct JSON representation?

I’ve moved this question to the Mobile SDKs forum since I wasn’t aware of that forum’s existence when I decided to post here. Apologies for the confusion: (New Thread)[Generating Unmanaged Realm Objects from Equivalent MongoDB Atlas BSON Documents (Cross-Post)]

Just comparing your Atlas schema with your Swift class. In Atlas, edits is an objectId. In your Swift class, edit is an optional DFEdits (so in Atlas it looks like your trying to work with a reference, whereas in Swift, you’re trying to embed the DFEdits within DFFilter).

If you want to use embedding (as seems to be the case on the Swift side) then your schema should show the edit data fields being embedded within the DFFilter collection (i.e. there’s no DFEdits collection). Note that on the Swift side, the DFEdits needs to conform to EmbeddedObject rather than Object.

Thanks for the follow-up, @Andrew_Morgan.

DFEdits documents can exist on their own. DFFilter objects however, must contain a DFEdits reference. I did not want to embed the edits within the filter due to the fact that they can exist as their own top-level Objects, so they couldn’t extend EmbeddedObject.

It’s hard to remember exactly why I made it optional in DFFilter, but I believe the SDK complained to me about making a custom reference required? I’ll have to validate that.

Makes sense, but I believe you need to be consistent between the schema and the Object definition to either embed or use a reference. atm Atlas is using a reference but the Realm Object is embedding. Perhaps your Realm Object should contain an ObjectId for edits too?

Noted! Makes sense. I think ObjectId is the solution here