Search code examples
iosswiftrealm

Realm add(_, update: true) removes existing relationships


I am facing an issue where I am unable to keep existing relationships after calling add(_, update: true) function.

I wrote a TaskSync class that is responsible for creating/updating Task objects:

class TaskSync: ISync {

    typealias Model = Task

    func sync(model: Task) {

        let realm = try! Realm()

        let inWrite = realm.isInWriteTransaction

        if !inWrite {
            realm.beginWrite()
        }

        let _task = realm.object(ofType: Task.self, forPrimaryKey: model.id)

        // Persist matches as they are not getting fetched with the task
        if let _task = _task {
            print("matches: \(_task.matches.count)")
            model.matches = _task.matches
        }

        realm.add(model, update: true)

        if _task == nil {
            var user = realm.object(ofType: User.self, forPrimaryKey: model.getUser().id)

            if (user == nil) {
                user = model.getUser()

                realm.add(user!, update: true)
            }

            user!.tasks.append(model)
        }

        if !inWrite {
            try! realm.commitWrite()
        }
    }

    func sync(models: List<Task>) {

        let realm = try! Realm()

        try! realm.write {
            models.forEach { task in
                sync(model: task)
            }
        }
    }
}

When a model is to be synced, I check if it already exists in the Realm and if so, I fetch it and try to include the matches property as this one is not included in the model.

Right before the call realm.add(model, update: true), model contains list of matches, however right after the realm.add is executed, the matches list is empty.

Here are the two models:

class Task: Object, ElementPreloadable, ElementImagePreloadable, ItemSectionable {

    dynamic var id: Int = 0
    dynamic var title: String = ""
    dynamic var desc: String = ""
    dynamic var price: Float = 0.0
    dynamic var calculatedPrice: Float = 0.0
    dynamic var location: String = ""
    dynamic var duration: Int = 0
    dynamic var date: String = ""
    dynamic var category: Category?
    dynamic var currency: Currency?
    dynamic var longitude: Double = 0.0
    dynamic var latitude: Double = 0.0
    dynamic var state: Int = 0
    dynamic var userId: Int = 0

    // Existing images
    var imagesExisting = List<URLImage>()
    // New images
    var imagesNew = List<Image>()
    // Images deleted
    var imagesDeleted = List<URLImage>()

    private let users = LinkingObjects(fromType: User.self, property: "tasks")
    var user: User?

    var matches = List<Match>()

    dynamic var notification: Notification?

    override static func ignoredProperties() -> [String] {
        return ["imagesExisting", "imagesNew", "imagesDeleted", "user", "tmpUser"]
    }

    override static func primaryKey() -> String? {
        return "id"
    }

    func getImageMain() -> URLImage? {
        for image in imagesExisting {
            if image.main {
                return image
            }
        }
        return imagesExisting.first
    }

    func getSection() -> Int {
        return state
    }

    func getSectionFieldName() -> String? {
        return "state"
    }

    func getId() -> Int {
        return id
    }

    func getURL() -> URL? {
        if let image = getImageMain() {
            return image.getResizedURL()
        }
        return nil
    }

    func getState() -> TaskOwnState {
        return TaskOwnState(rawValue: state)!
    }

    func getUser() -> User {
        return (user != nil ? user : users.first)!
    }
}

class Match: Object, ElementPreloadable, ElementImagePreloadable, ItemSectionable {

    dynamic var id: Int = 0
    dynamic var state: Int = -1
    dynamic var priorityOwnRaw: Int = 0
    dynamic var priorityOtherRaw: Int = 0
    dynamic var user: User!

    var messages = List<Message>()

    private let tasks = LinkingObjects(fromType: Task.self, property: "matches")
    var task: Task?

    dynamic var notification: Notification?

    override static func primaryKey() -> String? {
        return "id"
    }

    override static func ignoredProperties() -> [String] {
        return ["task"]
    }

    func getId() -> Int {
        return id
    }

    func getSection() -> Int {
        return 0
    }

    func getURL() -> URL? {
        if let image = user.getImageMain() {
            return image.getResizedURL()
        }
        return nil
    }

    func getPriorityOwn() -> PriorityType {
        if priorityOwnRaw == PriorityType.normal.rawValue {
            return PriorityType.normal
        }
        else {
            return PriorityType.favorite
        }
    }

    func getPriorityOther() -> PriorityType {
        if priorityOtherRaw == PriorityType.normal.rawValue {
            return PriorityType.normal
        }
        else {
            return PriorityType.favorite
        }
    }

    func getSectionFieldName() -> String? {
        return nil
    }

    func getTask() -> Task {
        return (task != nil ? task : tasks.first)!
    }
}

I spent hours trying to figure out why I am unable to keep the matches relationship when updating the task. Every advice will be highly appreciated!


Solution

  • This question was also asked upon Realm's GitHub issue tracker. For posterity, here is the solution.

    List properties should always be declared as let properties, as assigning to them does not do anything useful. The correct way to copy all objects from one List to another is model.tasks.append(objectsIn: _user.tasks).