Skip to content

Instantly share code, notes, and snippets.

@samrayner
Forked from krodak/Realm+CascadeDeleting.swift
Last active June 24, 2021 10:38
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samrayner/44d34fdc77e7d8d766fd467d557753a9 to your computer and use it in GitHub Desktop.
Save samrayner/44d34fdc77e7d8d766fd467d557753a9 to your computer and use it in GitHub Desktop.
Cascading deletion for RealmSwift
import Foundation
import RealmSwift
import Realm
//Forked from: https://gist.github.com/krodak/b47ea81b3ae25ca2f10c27476bed450c
internal protocol CascadingDeletable: RealmSwift.Object {
static var propertiesToCascadeDelete: [String] { get }
}
extension Realm {
internal func cascadingDelete(_ object: RealmSwift.Object) {
var toBeDeleted: Set<RLMObjectBase> = [object]
while let element = toBeDeleted.popFirst() as? RealmSwift.Object {
guard !element.isInvalidated else { continue }
if let cascadingDeletable = element as? CascadingDeletable {
cascade(into: cascadingDeletable, toBeDeleted: &toBeDeleted)
}
delete(element)
}
}
private func cascade(into object: CascadingDeletable, toBeDeleted: inout Set<RLMObjectBase>) {
let objectType = type(of: object)
guard let schema = objectType.sharedSchema() else { return }
let primaryKey = objectType.primaryKey()
let primaryKeyValue = primaryKey.flatMap(object.value(forKey:))
let properties = (schema.properties + schema.computedProperties)
.filter { objectType.propertiesToCascadeDelete.contains($0.name) }
for property in properties {
switch object.value(forKey: property.name) {
case let realmObject as RLMObjectBase:
toBeDeleted.insert(realmObject)
case let list as RLMListBase:
for index in 0 ..< list._rlmArray.count {
guard let realmObject = list._rlmArray.object(at: index) as? RLMObjectBase else { continue }
toBeDeleted.insert(realmObject)
}
default: //LinkingObjects
if let linkOriginPropertyName = property.linkOriginPropertyName,
let linkOriginTypeName = property.objectClassName,
let primaryKey = primaryKey,
let primaryKeyValue = primaryKeyValue {
dynamicObjects(linkOriginTypeName)
.filter("%K == %@", "\(linkOriginPropertyName).\(primaryKey)", primaryKeyValue)
.forEach { toBeDeleted.insert($0) }
}
}
}
}
}
@AdAvAn
Copy link

AdAvAn commented Feb 1, 2021

Perhaps you have an error in the filtering block:

let properties = (schema.properties + schema.computedProperties).filter { objectType.propertiesToCascadeDelete.contains($0.name) }

You are trying to apply a filter to CascadingDeletable.Type, but the type does not have the propertiesToCascadeDelete property, it might be more correct to do this using object.

let properties = (schema.properties + schema.computedProperties).filter { object.propertiesToCascadeDelete.contains($0.name) }

@samrayner
Copy link
Author

samrayner commented Feb 1, 2021

internal protocol CascadingDeletable: RealmSwift.Object {
    static var propertiesToCascadeDelete: [String] { get }
}

Note the static on the protocol requirement. It's a var on the type not the instance. I don't think your example would compile.

@iSevenDays
Copy link

Please feel free to test the version with replaced RLMListBase

internal protocol CascadingDeletable: RealmSwift.Object {
	static var propertiesToCascadeDelete: [String] { get }
}

extension Realm {
	internal func cascadingDelete(_ object: RealmSwift.Object) {
		var toBeDeleted: Set<RLMObjectBase> = [object]
		while let element = toBeDeleted.popFirst() as? RealmSwift.Object {
			guard !element.isInvalidated else { continue }
			if let cascadingDeletable = element as? CascadingDeletable {
				cascade(into: cascadingDeletable, toBeDeleted: &toBeDeleted)
			}
			delete(element)
		}
	}

	private func cascade(into object: CascadingDeletable, toBeDeleted: inout Set<RLMObjectBase>) {
		let objectType = type(of: object)

		guard let schema = objectType.sharedSchema() else { return }

		let primaryKey = objectType.primaryKey()
		let primaryKeyValue = primaryKey.flatMap(object.value(forKey:))

		let properties = (schema.properties + schema.computedProperties)
			.filter { objectType.propertiesToCascadeDelete.contains($0.name) }

		for property in properties {
			switch object.value(forKey: property.name) {
			case let realmObject as RLMObjectBase:
				toBeDeleted.insert(realmObject)
			case let list as RLMSwiftCollectionBase:
				for index in 0 ..< list._rlmCollection.count {
					guard let realmObject = list._rlmCollection.object(at: index) as? RLMObjectBase else { continue }
					toBeDeleted.insert(realmObject)
				}
			default: // LinkingObjects
				if let linkOriginPropertyName = property.linkOriginPropertyName,
				   let linkOriginTypeName = property.objectClassName,
				   let primaryKey = primaryKey,
				   let primaryKeyValue = primaryKeyValue {
					dynamicObjects(linkOriginTypeName)
						.filter("%K == %@", "\(linkOriginPropertyName).\(primaryKey)", primaryKeyValue)
						.forEach { toBeDeleted.insert($0) }
				}
			}
		}
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment