Realm filter not always finding data? - swift

I have a Swift app that uses Realm as its database. In the app, I have a couple of models defined like this:
class Child: Object {
#objc dynamic var id: Int = generateChildId()
//other properties removed
}
class Parent: Object {
#objc dynamic var id: Int = generateParentId()
let children = List<Child>()
//other properties removed
}
Elsewhere in my app, I have a method that deletes a Child object like this:
static func deleteChild(parentId: Int, childId: Int) {
do {
let realm = try Realm()
realm.refresh()
guard let parent = realm.objects(Parent.self).filter("id == %#", parentId).first else {
print("Parent with ID: \(parentId) was not found!")
return
}
guard let child = parent.children.filter("id == %#", childId).first else {
print("Child with ID: \(childId) was not found!")
print("This parent object has \(parent.children.count) children:")
for child in parent.children {
print("child ID: \(child.id)")
}
return
}
realm.delete(child)
} catch {
log.error(error)
}
}
The Child objects are displayed in a UITableView. When I attempt to delete a Child object, I print out the ID to the Xcode console and then I call the deleteChild(parentId:childId) method above. Most of the time, this works as expected. But sometimes, the code that filters the Child objects by ID fails to find the matching object and prints "Child with ID: X was not found!" to the console. When this happens, I iterate through the .children property on the Parent object and print out all of the Child objects. The thing that's really confusing me is that when I do this, the Child object that I tried to find and delete appears to be there. For example:
Child with ID: 1559835636225 was not found!
This parent object has 5 children:
child ID: 1559835626285
child ID: 1559835628608
child ID: 1559835636225
child ID: 1559835643522
child ID: 1559835653041
Any idea what I'm doing wrong here?
---UPDATE---
My original post contained incorrect property names in the predicates (parentId and childId instead of just id). In my actual code, which I attempted to sanitize before posting, the property names are correctly specified as just "id". Sorry for the confusion.

Your models contain a property called id. But your predicate checks for a property called parentId or childId.
Apart from that, it might be easier to just delete a child based on its id, without retrieving its parent first. Another option would be to use primary keys, which allows you to directly fetch the child with realm.object(ofType: Child.self, forPrimaryKey: childId).

As it turns out, this was a bug in Realm and has been addressed since 3.17.1.

Related

CoreData - Only fetch specific child managed Objects using a generic method

I have a strange problem:
I have a NSManagedObject called ItemTemplate. It has many child managed objects, e.g. CustomItemTemplate or SpecialItemTemplate.
Now I have a list viewController that is supposed to show these child managed objects. For example only "CustomItemTemplaze" or only "SpecialItemTemplate". I wrote this generic method to fetch all ItemTemplates and filter out the desired child-objects (I haven't found a better way yet).
private func loadTemplates<T: ItemTemplate>(ofType type: T.Type) -> [ModelObject] {
// ModelObject is just a model for my managed objects
var templates = [ModelObject]()
do {
let request: NSFetchRequest<ItemTemplate> = ItemTemplate.fetchRequest()
let result = try mainViewContext.fetch(request)
for item in result {
if item is T { // this is somehow always true
templates.append(item.modelObject) // add the converted item to the array
}
}
} catch let error as NSError {
print("Error: ", error.debugDescription)
}
return templates
}
I call it like this:
enum Category {
case custom
case special
public var templateClass: ItemTemplate.Type {
switch self {
case .custom:
return CustomItemTemplate.self
case .special:
return SpecialItemTemplate.self
}
}
}
loadTemplates(ofType: currentCategory.templateClass)
However, it's not filtering. if item is T seems to be true for every item. It thus returns every ItemTemplate, instead of only certain child objects.
Why is that? I can't explain it.
Thanks for any help!

Swift - Get the parent of a Realm object; Always empty

I've got a relationship where:
A Parent has many Children
ie:
class Factory: Object {
public let engines = List<Engine>()
}
class Engine:Object {
private let parents:LinkingObjects<Factory> = LinkingObjects(fromType: Factory.self, property: "engines")
var parent:Factory? {
return self.parents.first
}
}
I read the factories via JSON and create the children (Engine) manually in a for-loop, similar to this:
var engines:[Engine] = [Engine]()
for _ in stride(from:0, to: 3, by: 1) {
let engine: Engine = Engine.init()
engines.append(engine)
}
return engines
In my test I want to query the parent of a given engine to ensure that the parent is correct; or perhaps get a parent attribute.
However, whenever I try to grab an attribute via the parent its always empty;
for (_, element) in (factories.enumerated()) {
for (_, eng) in element.engines.enumerated() {
print (eng.parent ?? "N/A" as Any) // Always prints out N/A
}
}
Ideally I want to be able to access the parent's data; like the name of the parent, perhaps costs, etc.
I've tried resetting simulator and also deleting derived data; but regardless of what I do the results are always N/A or empty.
How can I query the given element and ensure that I can grab the parent data?
Many thanks
Turns out there were a number of issues that I had to do to resolve this.
I was using XCTest and Realm was causing issues where there were multiple targets.
Make all my model classes' public
Remove the models from the test target, this included a file where the JSON data was being loaded into memory
I had to write my data into Realm, which I had not done;
let realm = try! Realm()
try! realm.write {
for parent:EYLocomotive in objects {
for _ in stride(from:0, to: parent.qty, by: 1) {
let engine : EYEngine = EYEngine.init()
parent.engines.append(engine)
}
realm.add(parent)
}
}

Realm - Adding children to a parent, then querying results on parent

I'm working on how to add children to a Realm (Swift) parent and I want to query the results.
However, I'm coming up with a crash
do {
let realm = try Realm()
try realm.write {
for locomotive in locomotives
{
realm.add(locomotive, update: true)
}
let locomotives = realm.objects(Locomotive.self)
for locomotive in locomotives {
print (locomotive.name)
for _ in stride(from: 0, to: locomotive.qty, by: 1) {
let engine : Engine = Engine.init()
locomotive.engines.append(engine)
}
}
}
} catch let error as NSError {
//TODO: Handle error
print(error.localizedDescription as Any)
}
I want to create a certain number of children, add it to the relationship
Then when I try to query it;
let locomotives = realm.objects(Locomotive.self)
print(locomotives.count)
// Find all children that are linked to this specific parent
for loco in locomotives {
let engines = realm.objects(Engine.self).filter("parent == \(loco)")
print("listing engines")
for engine in engines {
print ("engine: \(engine.parent)")
}
}
My parent class is (at its most basic minus any mapping code)
class Locomotive: Object, Mappable {
dynamic var engineid: String = ""
var engines = List<Engine>()
}
My child class is: (at its most basic minus any mapping code)
class Engine: Object {
let parent = LinkingObjects(fromType: Locomotive.self, property: "engines")
}
This causes a crash:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unable to parse the format string "parent == Locomotive {
I'd like to get a list of all parent names for a given child; usually I'd do this like:
for each child in parent.array
{
print child.parent.name
}
But in realm, I do not get access to the parent's name.
How can I make queries on parent-child relationships and also a similar command to the above (get the parent's name attribute)?
Many thanks
Realm LinkingObjects objects don't represent a single object; they represent an array of potentially multiple objects. As such, it's necessary to query to see if your object exists in that array, instead of querying for equality.
let engines = realm.objects(Engine.self).filter("%# IN parent", loco)
Additionally, since Realm queries conform to NSPredicate, it's necessary to use the old-school %# notation, instead of Swift's inline code syntax.

How can you create Results after creating records?

I have a method that should return Results, either by successfully querying, or by creating the records if they don't exist.
Something like:
class MyObject: Object {
dynamic var token = ""
static let realm = try! Realm()
class func findOrCreate(token token: String) -> Results<MyObject> {
// either it's found ...
let tokenResults = realm.objects(MyObject.self).filter("token = '\(token)'")
if !tokenResults.isEmpty {
return tokenResults
}
// ... or it's created
let newObject = MyObject()
newObject.token = token
try! realm.write {
realm.add(newObject)
}
// However, the next line results in the following error:
// 'Results<_>' cannot be constructed because it has no accessible initializers
return Results(newObject)
}
}
Maybe I should just be returning [MyObject] from this method. Is there any benefit to trying to keep it as Results instead of Array? I guess I'd lose any benefit of postponed evaluation since I'm already using isEmpty within the method, correct?
Results is an auto-updating view into underlying data in a Realm, which is why you can't construct it directly. So instead of return Results(newObject), you should return tokenResults, which will contain your newly added object, again because Results is an auto-updating view.

How can I easily duplicate/copy an existing realm object

I have a Realm Object which has several relationships, anyone has a good code snippet that generalizes a copy method, to create a duplicate in the database.
In my case i just wanted to create an object and not persist it. so segiddins's solution didn't work for me.
Swift 3
To create a clone of user object in swift just use
let newUser = User(value: oldUser);
The new user object is not persisted.
You can use the following to create a shallow copy of your object, as long as it does not have a primary key:
realm.create(ObjectType.self, withValue: existingObject)
As of now, Dec 2020, there is no proper solution for this issue. We have many workarounds though.
Here is the one I have been using, and one with less limitations in my opinion.
Make your Realm Model Object classes conform to codable
class Dog: Object, Codable{
#objc dynamic var breed:String = "JustAnyDog"
}
Create this helper class
class RealmHelper {
//Used to expose generic
static func DetachedCopy<T:Codable>(of object:T) -> T?{
do{
let json = try JSONEncoder().encode(object)
return try JSONDecoder().decode(T.self, from: json)
}
catch let error{
print(error)
return nil
}
}
}
Call this method whenever you need detached / true deep copy of your Realm Object, like this:
//Suppose your Realm managed object: let dog:Dog = RealmDBService.shared.getFirstDog()
guard let detachedDog = RealmHelper.DetachedCopy(of: dog) else{
print("Could not detach Dog")
return
}
//Change/mutate object properties as you want
detachedDog.breed = "rottweiler"
As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our realm object. Just make sure all your Realm Model Classes conform to Codable.
Though its NOT an ideal solution, but its one of the most effective workaround.
I had a similar issue and found a simple workaround to get a copy of a realm object. Basically you just need to make the object conform to the NSCopying protocol, something like:
import RealmSwift
import Realm
import ObjectMapper
class Original: Object, NSCopying{
dynamic var originalId = 0
dynamic var firstName = ""
dynamic var lastName = ""
override static func primaryKey() -> String? {
return "originalId"
}
init(originalId: Int, firstName: String, lastName: String){
super.init()
self.originalId = originalId
self.firstName = firstName
self.lastName = lastName
}
func copy(with zone: NSZone? = nil) -> Any {
let copy = Original(originalId: originalId, firstName: firstName, lastName: lastName)
return copy
}
}
then you just call the "copy()" method on the object:
class ViewController: UIViewController {
var original = Original()
override func viewDidLoad() {
super.viewDidLoad()
var myCopy = original.copy()
}
}
The nice thing about having a copy is that I can modify it without having to be in a realm write transaction. Useful when users are editing some data but didn't hit save yet or simply changed their mind.
Since this problem is still alive I post my solution which works but still needs to be improved.
I've created an extension of Object class that has this method duplicate that takes an object objOut and fills the flat properties by looking at self. When a non-flat property is found (aka a nested object) that one is skipped.
// Duplicate object with its flat properties
func duplicate(objOut: Object) -> Object {
// Mirror object type
let objectType: Mirror = Mirror(reflecting: self);
// Iterate on object properties
for child in objectType.children {
// Get label
let label = child.label!
// Handler for flat properties, skip complex objects
switch String(describing: type(of: child.value)) {
case "Double", "Int", "Int64", "String":
objOut.setValue(self.value(forKey: label)!, forKey: label)
break
default:
break
}
}
return objOut
}
Inside the Manager class for my Realms I have the method copyFromRealm() that I use to create my copies of objects.
To give you a practical example this is the structure of my Appointment class:
Appointment object
- flat properties
- one UpdateInfo object
- flat properties
- one AddressLocation object
- flat properties
- one Address object
- flat properties
- one Coordinates object
- flat properies
- a list of ExtraInfo
- each ExtraInfo object
- flat properties
This is how I've implemented the copyFromRealm() method:
// Creates copy out of realm
func copyFromRealm() -> Appointment {
// Duplicate base object properties
let cpAppointment = self.duplicate(objOut: Appointment()) as! Appointment
// Duplicate UIU object
cpAppointment.uiu = self.uiu?.duplicate(objOut: UpdateInfo()) as? UpdateInfo
// Duplicate AddressLocation object
let cpAddress = self.addressLocation?.address?.duplicate(objOut: Address()) as? Address
let cpCoordinates = self.addressLocation?.coordinates?.duplicate(objOut: Coordinates()) as? Coordinates
cpAppointment.addressLocation = self.addressLocation?.duplicate(objOut: AddressLocation()) as? AddressLocation
cpAppointment.addressLocation?.address = cpAddress
cpAppointment.addressLocation?.coordinates = cpCoordinates
// Duplicate each ExtraInfo
for other in self.others {
cpAppointment.others.append(other.duplicate(objOut: ExtraInfo()) as! ExtraInfo)
}
return cpAppointment
}
I wasn't able to find out a good and reasonable way to work with nested objects inside my duplicate() method. I thought of recursion but code complexity raised too much.
This is not optimal but works, if I'll find a way to manage also nested object I'll update this answer.
Swift 5+
Creates a Realm managed copy of an existing Realm managed object with ID
extension RLMObject {
func createManagedCopy(withID newID: String) -> RLMObject? {
let realmClass = type(of: self)
guard let realm = self.realm, let primaryKey = realmClass.primaryKey() else {
return nil
}
let shallowCopy = realmClass.init(value: self)
shallowCopy.setValue(newID, forKey: primaryKey)
do {
realm.beginWriteTransaction()
realm.add(shallowCopy)
try realm.commitWriteTransaction()
} catch {
return nil
}
return shallowCopy
}
}