How to structure a Sharing Schema

Basically, my schema contains users, who have recipes.
Recipes have authors, denoted by a string id.
So, I’m partitioning synch on the author field.

Now I want users to be able to share their recipes with friends.

Here’s how I have it setup so far.
A little confusing, but it works in some ways.
Users have a watchedRecipes key in customData, which is an array of recipe id’s. When an author shares a recipe with another user, the recipe id is added to the recipient’s watchedRecipes array.

This actually works great from the web side. When I query for recipes to show on the users’s homescreen, I just augment the query to include recipes with id’s in the watchedRecipes list. And I adjust the UI to show the difference.

But I don’t know how to approach finding these recipes on my mobile apps.
Considering that these watchedRecipes do not maintain the user id as their author, they don’t conform to my partition. Is there a straightforward way to query for the id’s in the customData? Or do I need to think about creating a new real with a different partition?

Note: In the future, I’m planning to do this for a chat feature. I plan to create a new realm, and partition on a field containing combinations of user id’s. But I’d prefer to keep this mess cleanly divided from the recipe list code.

There are some restrictions on how syncing and partitioning works (some upcoming enhancements some more flexibility, but I’m not sure that they’ll fix everything for you).

When you open a synced realm, you must provide the exact value for the partitioning key in every document that you want to sync. A couple of high-level options:

  1. You create duplicate copies of the shared recipes (a copy for each subscriber) and use Realm triggers to keep them up to date
  2. For your user, store a list of recipe titles and ids, you can then show a list of them and open a new synced realm for that recipe when the title is clicked on (so the partitioning key would be something like partition: "recipe=83bnjsd8923bnjf8".

This post might give you some clues… Realm Partitioning Strategies | MongoDB

As I said, there’s some new sync functionality coming out soon that might make things a little simpler.

Are you referring to Flexible Sync? I’m just coming back to check out Realm again after 6 months or so away; can you say what the status of Flexible Sync is? Thanks!

I did find a solution. It is not pretty, but it works and delivers what I need for now.

I’m just calling directly and storing the returned data to a RealmList.

Some challenges I ran into here are…

  1. Using the RealmList, so I’m not taking advantage of RealmResults and I have to do some extra gymnastics.
  2. Because I want my nested documents, I have to do multiple calls.
  3. BSON is kinda confusing. I’d probably be better served to read the existing and very dry documentation. Against my better judgement, I just played with it for hours until it gave me what I wanted.

Here’s the code. It’s a dump. I haven’t refactored at all and I’m no genius in general, let alone when it comes to the specifics of Swift. There’s a lot to be desired here. Still, it works on my machine. So if anyone else ever needs to do something like this. But it probably doesn’t actually make sense, and there are probably better ways to accomplish it.

 func importWatchedRecipes(recipes: AnyBSON, onSuccess: @escaping(RealmSwift.List<Recipe>) -> Void, onFailure: @escaping(Error) -> Void) throws {
        let client = app.currentUser!.mongoClient("mongodb-atlas")
        let database = client.database(named: "funky_radish_db")
        let collection = database.collection(withName: "Recipe")

        let queryFilter: Document = ["_id": ["$in": recipes]]
        
        var recipeList = RealmSwift.List<Recipe>()
        
        collection.find(filter: queryFilter) { result in
            switch result {
                case .failure(let error):
                    onFailure(error)
                case .success(let document):
                    for rec in document {
                        var watchedRecipe: Recipe = Recipe()
                        watchedRecipe.title = rec["title"]??.stringValue
                        
                        let ingFilter: AnyBSON = rec["ingredients"]! ?? []
                        
                        do {
                            try self.importWatchedIngredients(
                                ingredients: ingFilter,
                                onSuccess: { returnedIngredients in
                                    watchedRecipe.ingredients = returnedIngredients
                                                                        
                                    let dirFilter: AnyBSON = rec["directions"]! ?? []
                                    
                                    do {
                                        try self.importWatchedDirections(
                                            directions: dirFilter,
                                            onSuccess: { returnedDirections in
                                                watchedRecipe.directions = returnedDirections
                                                recipeList.append(watchedRecipe)
                                                                                                
                                                if (recipeList.count == recipes.arrayValue?.count) {
                                                    onSuccess(recipeList)
                                                }                                         
                                            },
                                            onFailure: { error in
                                                onFailure(error)
                                            })
                                    }
                                    catch {
                                        print("catch on the watched direction getter")
                                    }
                                },
                                onFailure: { error in
                                    onFailure(error)
                                })
                        }
                        catch {
                            print("catch on the watched ingredient getter")
                        }
                    }
            }
        }
    }

    // TODO: Consolidate these 2 functions that are nearly identical
    func importWatchedIngredients(ingredients: AnyBSON, onSuccess: @escaping(RealmSwift.List<Ingredient>) -> Void, onFailure: @escaping(Error) -> Void) throws {
        
        let client = app.currentUser!.mongoClient("mongodb-atlas")
        let database = client.database(named: "funky_radish_db")
        let ingCollection = database.collection(withName: "Ingredient")
        
        var embeddedIngredients = RealmSwift.List<Ingredient>()
        let ingFilter: Document = ["_id": ["$in": ingredients]]
        
     
        ingCollection.find(filter: ingFilter) { ingResult in
            switch ingResult {
                case .failure(let error):
                    onFailure(error)
                case .success(let ingredientDocument):
                    
                    for returnedIngredient in ingredientDocument {
                        let ing = Ingredient()
                        ing._id = returnedIngredient["_id"]??.stringValue
                        ing.name = returnedIngredient["name"]??.stringValue ?? ""
                        embeddedIngredients.append(ing)
                    }
                    
                    onSuccess(embeddedIngredients)
            }
        }
    }
    
    func importWatchedDirections(directions: AnyBSON, onSuccess: @escaping(RealmSwift.List<Direction>) -> Void, onFailure: @escaping(Error) -> Void) throws {
        
        let client = app.currentUser!.mongoClient("mongodb-atlas")
        let database = client.database(named: "funky_radish_db")
        let dirCollection = database.collection(withName: "Direction")
        
        var embeddedDirections = RealmSwift.List<Direction>()
        let dirFilter: Document = ["_id": ["$in": directions]]
        
        dirCollection.find(filter: dirFilter) { dirResult in
            switch dirResult {
                case .failure(let error):
                    onFailure(error)
                case .success(let directionDocument):
                    
                    for returnedDirection in directionDocument {
                        let dir = Direction()
                        dir._id = returnedDirection["_id"]??.stringValue
                        dir.text = returnedDirection["text"]??.stringValue ?? ""
                        embeddedDirections.append(dir)
                    }
                    
                    onSuccess(embeddedDirections)
            }
        }
    }

Now struggling to replicate on Android. @Andrew_Morgan

I can get this far, but I’m having trouble figuring out how to handle the iterator.

       val client = realmApp.currentUser()?.getMongoClient("mongodb-atlas")
        val database = client?.getDatabase("funky_radish_db")
        val collection = database?.getCollection("Recipe")

        val queryFilter = Document("_id", "617ee621638d87c7faf71c9f")

        val findTask = collection?.find(queryFilter)?.iterator()
        
        Log.d("API", "task?: $findTask")