Search code examples
swiftfor-looprealmcrud

What's the correct way to delete all future occurrences of a Realm object?


Currently, I am using this code to delete all future repeat transactions:

let futureRecurringTransactions = self.transactions.filter(NSPredicate(format: "transactionDescription == %@ && transactionDate >= %@ && transactionCategory == %@", transaction!.transactionDescription!, transaction!.transactionDate as CVarArg, transaction!.transactionCategory!))
            
            for transaction in futureRecurringTransactions {
                try! self.realm.write {
                    self.realm.delete(transaction)
                }
            }

Here is my Realm data model:

import RealmSwift

class Transaction: Object {
    
    @objc dynamic var transactionDescription: String? = nil
    @objc dynamic var transactionAmount = 0.0
    @objc dynamic var transactionDate = Date()
    @objc dynamic var transactionCategory: Category? = nil
    @objc dynamic var repeatInterval = ""
    @objc dynamic var isCleared = false
    @objc dynamic var transactionName = ""
    @objc dynamic var transactionID = UUID().uuidString
    
    convenience init(theDate: Date) {
        self.init()
        self.transactionDate = theDate
    }
    
    override static func primaryKey() -> String? {
        return "transactionID"
    }
        
}

The code works, but it feels like the way I'm doing it is amateur and clunky. Is there a better way that is more accurate and proper?

I thought it might be good to utilize the transactionID , but each transaction has a unique transactionID, so I'm not sure how that would work.


Solution

  • What I typically do is create a unique NSPredicate for each constraint you want to place on the collection. You can mix and match each predicate to form a unique set of filters. For example:

    extension NSPredicate {
        static func transactionDescriptionEqualTo(_ description: String?) -> NSPredicate {
            if let description = description {
                return .init(format: "transactionDescription == %@", description)
            }
            return .init(format: "transactionDescription == nil")
        }
    
        static func transactionCategoryEqualTo(_ category: Category?) -> NSPredicate {
            if let category = category {
                return .init(format: "transactionCategory == %@", category)
            }
            return .init(format: "transactionCategory == nil")
        }
    
        static func transactionDateIsAfter(_ date: Date) -> NSPredicate {
            return .init(format: "transactionDate >= %@", date as NSDate)
        }
    
        static func transactionIDNotEqualTo(_ id: String) -> NSPredicate {
            return .init(format: "transactionID != %@", id)
        }
    
        static func futureRepeats(of transaction: Transaction) -> NSPredicate {
            return NSCompoundPredicate(andPredicateWithSubpredicates: [
                .transactionDescriptionEqualTo(transaction.transactionDescription),
                .transactionCategoryEqualTo(transaction.transactionCategory),
                .transactionDateIsAfter(transaction.transactionDate),
                .transactionIDNotEqualTo(transaction.transactionID),
            ])
        }
    }
    

    Also note that you don't need to loop over the results. You can delete all objects in the results like this:

    let realm = try Realm()
    try realm.write {
        realm.delete(transactions.filter(.futureRepeats(of: transaction)))
    }
    

    Lastly, it's a good idea to include the transactionID as a constraint. Otherwise the original transaction will be included in the results.