gwenoleR
gwenoleR

Reputation: 90

Swift Realm - Iterate an array of items to update their relationships

I have a basic many-to-many relation with a relation table on my Realm Schema. So I have Recipe, Ingredient and RecipeIngredient model.

When I try to create a Recipe, I have to iterate an array of Ingredient to create RecipeIngredient. Once the recipeIngredient is create I want to edit my Recipe and my Ingredient to add a relation to the new RecipeIngredient. But the edition of the Ingredient throws an exception, and I have no idea why...

Here some code

Recipe Model

class RecipeRealm: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var _id: ObjectId
    @Persisted var name: String = ""
    @Persisted var lastEditedTime: Date = Date()
    
    @Persisted var recipeIngredients: List<RecipeIngredient>
    
    convenience init(name: String){
        self.init()
        self.name = name
    }
}

Ingredient Model

class IngredientRealm: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var _id: ObjectId
    @Persisted var name: String = ""
    @Persisted var unit: Unit = Unit.none
    @Persisted var lastEditedTime: Date = Date()
    
    @Persisted var recipeIngredients: List<RecipeIngredient>
    
    convenience init(name: String){
        self.init()
        self.name = name
    }
    
}

RecipeIngredient Model

class RecipeIngredient: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var _id: ObjectId
    @Persisted var qty: Int = 0
    @Persisted var lasEditedTime: Date = Date()
    
    @Persisted(originProperty: "recipeIngredients") var recipe: LinkingObjects<RecipeRealm>
    @Persisted(originProperty: "recipeIngredients") var ingredient: LinkingObjects<IngredientRealm>
}

The recipe form model

struct QuantifiedIngredient {
    var ingredient: IngredientRealm
    var qty: Int16
}


class RecipeForm: ObservableObject {
    @Published var name = ""
    @Published var ingredients: [QuantifiedIngredient] = []
    var recipeID: String?
    
    var updating: Bool {
        recipeID != nil
    }
    
    init(){}
    
    init(_ recipe: Recipe){
        name = recipe.name ?? ""
        ingredients = []
        recipeID = recipe.id
    }
}

The create Recipe logic

private func createRecipe() {
        withAnimation {
            do {
                let realm = try Realm()
                // Create Recipe on RealmDB
                let realmRecipe = RecipeRealm(name: form.name)
                
                try realm.write{
                    realm.add(realmRecipe)
    
                    for ingredient in form.ingredients{
                        let recipeIngredient = RecipeIngredient()
                        recipeIngredient.qty = Int(ingredient.qty)
                        realm.add(recipeIngredient)
                        
                        realmRecipe.recipeIngredients.append(recipeIngredient)
                        ingredient.ingredient.recipeIngredients.append(recipeIngredient) // <--- this line crash
                    }
                }
                
            
                
            } catch {
                print(error.localizedDescription)
            }
            
            presentationMode.wrappedValue.dismiss()
        }
    }

An exception is throw by this line on createRecipe method: ingredient.ingredient.recipeIngredients.append(recipeIngredient).

Here the error message: Terminating app due to uncaught exception 'RLMException', reason: 'Cannot modify managed RLMArray outside of a write transaction.' But I have the write block...

Any ideas ?

Upvotes: 0

Views: 597

Answers (1)

gwenoleR
gwenoleR

Reputation: 90

It seems that when I tried to add recipeIngredient on the ingredient, I actually tried to put a realm object on a copy of the realm ingredient. So is obviously not working, but the error message is not very clear.

To fix it I used the thaw()function that returns me the good realm object, so I can edit it.

try realm.write{
                    realm.add(realmRecipe)
    
                    for ingredient in form.ingredients{
                        let recipeIngredient = RecipeIngredient()
                        recipeIngredient.qty = Int(ingredient.qty)
                        realm.add(recipeIngredient)
                        
                        
                        realmRecipe.recipeIngredients.append(recipeIngredient)
                        
                        guard let rIngredient = ingredient.ingredient.thaw() else {
                                return
                            }
                        rIngredient.recipeIngredients.append(recipeIngredient)
                    }
                }

I don't know if this is the best way to do it, so if someone had a better option I'm listening :)

Upvotes: 1

Related Questions