I have been hitting an issue when copying managed objects to a new Realm. I have a legacy Realm app being converted to MongoDb Realm. I know to use ‘.create()’ for copying to a new Realm. In legacy Realm there were no partition values for Realms, so when a copy was made the nested referenced Objects or List<>s (aka: relations via the ‘foreign_key’) were not a problem. I am finding that unless I update the partition values (deep) the local data will not sync to the cloud. Of course, the data writes fine locally but does not sync to the cloud.
My issue is regarding Objects that are nested with Objects or List<>s - that would be related (by use of the foreign_key relationship). Also, this is for Object type not EmbeddedObjects. Since each related Object or Array of Objects would have a partition key and value. Upon the copy (i.e., the target) such related objects do not have the new partition value, rather it holds the old (source) value.
To solve this, I have created some services and an Extension to Realm’s Object in order to unmanage them from Realm before writing the copy. This seems to work so far, though I have not completed all thorough testing yet. I want to make sure that I am not missing something (i.e., a better way to handle this) or do I need build more for this idea to work -please advise. If I am on the right path, then this code can help others.
I am including the code for my Swift Extension. The code is somewhat re-created for this post (so not exact) and I am not including the related services I built to complete what I need (since it doesn’t matter for the issue). FYI - I have a design to “further partition” (due to the data denormalization) when I need to create duplicate objects across Realms that would cause a primary-key duplication issue (within same Collection) for MongoDB.
/// NOTE: Code shown is not actual code, rather recreated for this post.
//
// Object+Ext(DeepUnManageRealm).swift
// SAMPLE CODE
//
// Created by Reveel on 5/12/22.
// Copyright © 2022 Reveel® All rights reserved.
//
import Foundation
import RealmSwift
/// A Protocol for Unmanaging Realm Objects (Deep)
protocol UnmanageRealmObject: AnyObject {
func unmanageDeep(_ newPartitionValue: String?, _ furtherPartitioning: Bool) -> Self
// END
// END
} // END of Protocol for 'UnmanageRealmObject'
// NOTE: Extension for Unmanaging Realm Objects (Deep) - For 'Object'
extension Object: UnmanageRealmObject {
/// This method will unmanage a Realm object deep and update partitioning value WITHOUT forcing to further partitioning.
/// - Parameters:
/// - newPartitionValue: pass 'nil' to unmanage deep without changing partition value.
internal func unmanageAndUpdatePartitionsDeep(_ newPartitionValue: String?) -> Self {
if let haveSourcePartition = self.realm?.configuration.syncConfiguration?.partitionValue {
if haveSourcePartition.stringValue != newPartitionValue {
return unmanageDeep(newPartitionValue, false)
}
else {
return unmanageDeep(haveSourcePartition.stringValue, false)
}
}
else {
fatalError("During DEV - crashing bc NO Realm or Partition on source object. Check method: '\(#function)'", file: #file, line: #line)
}
} // End of 'unmanageAndUpdatePartitionsDeep' method
/// This method will unmanage a Realm object deep and update partitioning value -AND- will force apply Further Partition practice.
/// - Parameters:
/// - newPartitionValue: pass 'nil' to unmanage deep without changing partition value.
internal func unmanageAndUpdateFurtherPartitioningDeep(_ newPartitionValue: String?) -> Self {
// Custom Code for needs to further partition and do it auto/conditionally
return unmanageDeep(newPartitionValue, true)
} // End of 'unmanageAndUpdateFurtherPartitioningDeep' method
/// This method will unmanage a Realm object deep
/// - Parameters:
/// - newPartitionValue: pass 'nil' to unmanage deep without changing partition value.
/// - furtherPartitioning: pass 'true' to force apply Further Partition practice.
internal func unmanageDeep(_ newPartitionValue: String?, _ furtherPartitioning: Bool = false) -> Self {
let unmanaged = type(of: self).init()
let partitioningKey = "partitioningKey"
let furtherPartitionPropertyName = "YOUR-NAME"
for property in objectSchema.properties {
guard var propertyValue = value(forKey: property.name) else { continue; }
var processedFurtherPartitioning: Bool = false
if property.isArray {
// NOTE: For Realm's List<>.
let doUnmanage = propertyValue as? UnmanageRealmObject
unmanaged.setValue(doUnmanage?.unmanageDeep(newPartitionValue, furtherPartitioning), forKey: property.name)
}
else if property.isMap || property.isSet {
// NOTE: For Realm's Map (aka: Dictionary) -OR- Set.
unmanaged.setValue(propertyValue, forKey: property.name)
}
else if property.type == .object {
// TODO: Test if this handles EmbeddedObjects by Reference (i.e. 'related' by 'foreign_key') when not fully embedded.
// NOTE: For Realm's Object and assuming it handles EmbeddedObject (when such is a reference via foreign_key in Schema).
let doUnmanage = propertyValue as? UnmanageRealmObject
unmanaged.setValue(doUnmanage?.unmanageDeep(newPartitionValue, furtherPartitioning), forKey: property.name)
}
else {
if property.name == partitioningKey, let haveNewPartition = newPartitionValue, partitioningKey != haveNewPartition {
let oldPartition = propertyValue
propertyValue = haveNewPartition
processedFurtherPartitioning = RealmPartitionServices.shared.checkToApplyFurtherPartition(property: property)
} // End of IF-"have New Partition Value"
if furtherPartitioning, property.name == furtherPartitionPropertyName {
let oldValue = propertyValue
if oldValue is String {
if !processedFurtherPartitioning {
propertyValue = RealmPartitionServices.shared.createFurtherPartitioning(value: propertyValue as! String)
}
}
else {
// means non-standard type for further-partitioning for Reveel, which for DEV we crash but for PROD we Error-Log
fatalError("During DEV - crashing bc UN-KNOWN OLD-Type for Further-Partitioning. Check method: '\(#function)'", file: #file, line: #line)
}
} // End of IF-"Update for Further Partitioning"
unmanaged.setValue(propertyValue, forKey: property.name)
}
} // End of for-in-LOOP
return unmanaged
} // End of internal-method 'unmanageDeep', For: overrided method
// END
// END
} // END of 'Object' EXTENSION', For: 'UnmanageRealmObject'