How to get Realm Objects by ID only? - swift

I set up a Realm model (in Swift) with a children relationship:
import Foundation
import RealmSwift
class MyObject1: RealmSwift.Object, RealmSwift.ObjectKeyIdentifiable {
#Persisted(primaryKey: true) var _id: RealmSwift.ObjectId
#Persisted var childrenIDs = RealmSwift.List<String>()
}
I added the relationship as a RealmSwift.List of Strings, because I intent to create other model classes, each with an ID, that can be added as children. In other words, the children might not be of a single type/class. For example:
import Foundation
import RealmSwift
class MyObject2: RealmSwift.Object, RealmSwift.ObjectKeyIdentifiable {
#Persisted(primaryKey: true) var _id: RealmSwift.ObjectId
#Persisted var title: String
}
Now at some point, I have to fetch all the children by their ID, for example to show them in a list. But I do not know how. I know the realm.objects(Class.self).filter but this expects a single class type. So my question is, how can I fetch objects from a Realm only by their ID (ie without their class/type)?

As mentioned in a comment, ObjectId's are generic and have no correlation to the object class they are tied to. So you can't do exactly what you want.
But... there are options.
TL;DR
create another object with properties to hold the id and the object type, defined by an enum or
Use AnyRealmValue to store the objects themselves
Long answers:
Long answer #1
Create a class to store the id and type. Suppose we have a database that stores wine grape types; some are white and some are red but you want to store them all in the same list. Let me set up an example
enum GrapeTypesEnum: String, PersistableEnum {
case white
case red
}
class Grape: Object {
#Persisted(primaryKey: true) var _id: RealmSwift.ObjectId
#Persisted var grape_name = ""
#Persisted var grape_type: GrapeTypesEnum
}
class GrapeObjects: Object { //contains the grape id string & grape type
#Persisted var child_id = ""
#Persisted var child_type: GrapeTypesEnum
}
so then the model would be
class MyObject1: RealmSwift.Object {
#Persisted(primaryKey: true) var _id: RealmSwift.ObjectId
#Persisted var childGrapes = RealmSwift.List<GrapeObjects>()
}
You could then create a couple of grapes
let whiteGrape = Grape()
whiteGrape.name = "Chenin Blanc"
whiteGrape.grape_type = .white
let redGrape = Grape()
redGrape.name = "Cabernet Franc"
redGrape.grape_type = .red
let grape0 = GrapeObjects()
grape0.grape_id = whiteGrape._id.stringValue()
grape0.grape_type = whiteGrape.grape_type
let grape1 = GrapeObjects()
grape1.grape_id = redGrape._id.stringValue()
grape1.grape_type = redGrape.grape_type
then finally store those objects in your class. Those can then be sorted, filtered and you will know which ID goes with what type of object
let anObject = MyObject1()
anObject.childGrapes.append(objectsIn: [grape0, grape1])
Long answer #2
Another option is to not store the objectID string but store the actual objects by leveraging AnyRealmValue
class MyClass: Object {
#Persisted var myList = List<AnyRealmValue>()
}
let red = WhiteGrape()
let white = RedGrape()
let obj0: AnyRealmValue = .object(red) //both of these are objects,
let obj1: AnyRealmValue = .object(white) // even through they are different objects
let m = MyClass()
m.myGrapeList.append(obj0)
m.myGrapeList.append(obj1)
Then you can take action based on the the objects type
for grape in m.myGrapeList {
if let white = grape.object(WhiteGrape.self) {
print("is a white grape")
} else if let red = grape.object(RedGrape.self) {
print("is a red grape")
}
}

Related

Covert ObjectId to String SwiftUI Realm object

I have my model called Restaurant:
realm object on kotlin:
class Restaurant : RealmObject {
#PrimaryKey
var _id: ObjectId = ObjectId.create()
var name: String = ""
var adress: String? = null
}
I want to use the _id property. But for that I need to convert to a String in SwiftUI
I tried: restaurant_id as! String, it does not work,
Is is related to this post: https://www.mongodb.com/community/forums/t/swift-convert-objectid-to-string/121829
No real answers there
Any other solutions?
the error when using .toHexString(): Value of type 'any Library_baseObjectId' has no member 'toHexString':
the type of _id in this case:
The error when trying: "\(restaurant._id.stringValue)"
I solved this by adding a getter to the class:
class Restaurant : RealmObject {
#PrimaryKey
var _id: ObjectId = ObjectId.create()
var name: String = ""
var adress: String? = null
fun getID() : String{
return _id.toString()
}
}
ObjectID's in Swift have a stringvalue property which returns the ObjectID as a string
You could do this
let idAsAsString = someRealmObject._id.stringValue
Or for kotlin and other SDK;s
let idAsString = object._id.toString()
In SwiftUI, the id can be accessed in a similar way, and then use the value to init a new string
let x = someRealmObject._id.stringValue
let y = String(stringLiteral: x)
let _ = print(x, y)
Text("\(x) \(y)")
and the output will be
6376886a1dbb3c142318771c 6376886a1dbb3c142318771c
in the console and the same in the Text in the UI
Here's some SwiftUI showing the use
List {
ForEach(myModel) { model in
NavigationLink(destination: SecondView(navigationViewIsActive: $navigationViewIsActive, selectedEntryToShow: model))
{
Text("\(model._id.stringValue)") //ormodel._id.toString()
}
}
}
This Swift model works perfectly and the string of ObjectId can be read per above.
class Restaurant: Object {
#Persisted(primaryKey: true) var _id: ObjectId
#Persisted var name: String = ""
#Persisted var adress: String? = null
}

How to map a Firestore DocumentID to a RealmObject attribute?

I'm trying to provide some data in the cloud with Firestore that can be downloaded and stored in a Realm database on an iOS device.
The structure of my object that I want to store is:
import Foundation
import RealmSwift
import FirebaseFirestore
import FirebaseFirestoreSwift
#objcMembers class Flashcard: Object, Codable{
#objc dynamic var id: String? = NSUUID().uuidString
#objc dynamic var character: String?
#objc dynamic var title: String?
#objc dynamic var translation: String?
#objc dynamic var created: Date = Date()
let deck = LinkingObjects<FlashcardDeck>(fromType: FlashcardDeck.self, property: "cards")
override class func primaryKey() -> String? {
return "id"
}
private enum CodingKeys: String, CodingKey {
case id
case character
case translation
case created
case title
}
If I try to map the documentID to my id attribute with
#DocumentID #objc dynamic var id: String? = NSUUID().uuidString
If get the following error:
'Primary key property 'id' does not exist on object 'Flashcard'
How can I solve this problem?
EDIT: To make it more understandable here is a screenshot of my Firestore database:
The collection "PredifinedDecks" will store many decks. For example the id = DF59B1B3-BD22-47CE-81A6-04E7A274B98F represents one deck. Each deck will store an array/List with cards in it.
Not sure I fully understand the question but let me address this at a high level.
It appears there is a PredefinedDecks (a collection) that contains documents. Each document has a field (an array) of cards and some other field data. If the goal is to read in all of the documents (the decks) and their child data and store them as Realm objects, here's one solution. Start with a Realm object to hold the data from Firestore
class DeckClass: Object {
#objc dynamic var deck_id = ""
#objc dynamic var created = ""
#objc dynamic var title = ""
let cardList = List<CardClass>()
convenience init(withDoc: QueryDocumentSnapshot) {
self.init()
self.deck_id = withDoc.documentID
self.title = withDoc.get("title") as? String ?? "no title"
self.created = withDoc.get("created") as? String ?? "no date"
let cardArray = withDoc.get("cards") as? [String]
for card in cardArray {
let card = CardClass(withCard: card) {
self.cardList.append(card)
}
}
}
}
With this, you simply pass the documentSnapshot from Firestore for each document and the class will populate its properties accordingly.
and the code to read Firestore
func readDecks() {
let decksCollection = self.db.collection("PredefinedDecks")
decksCollection.getDocuments(completion: { documentSnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
for doc in documentSnapshot!.documents {
let deck = DeckClass(withDoc: doc)
self.decksList.append(deck) //a Realm List class object? Something else?
}
})
}

Make Realm Loop Generic and Reusable in Swift

Swift 4, Xcode 9.2
Here are a few Realm classes I have:
class Dog: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var updated = Date()
//Other properties...
}
class Cat: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var updated = Date()
//Other properties...
}
class Horse: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var updated = Date()
//Other properties...
}
Let's say I have some code like this where I compare two realms and create an object with a specific Dog class:
let remoteDogs = remoteRealm.objects(Dog.self)
for remoteDog in remoteDogs{
if let localDog = realm.objects(Dog.self).filter("id = %#",remoteDog.id).first{
// Update
if localDog.updated < remoteDog.updated{
//Remote is newer; replace local with it
realm.create(Dog.self, value: remoteDog, update:true)
}
}
}
This works great, but I need to do this same stuff on a whole bunch of Realm classes that I have. So I'm trying to make it more generic like this:
let animals = [Dog.self, Cat.self, Horse.self]
for animal in animals{
let remoteAnimals = remoteRealm.objects(animal)
for remoteAnimal in remoteAnimals{
if let localAnimal = realm.objects(animal).filter("id = %#",remoteAnimal.id).first{
// Update
if localAnimal.updated < remoteAnimal.updated{
//Remote is newer; replace local with it
realm.create(animal, value: remoteAnimal, update:true)
}
}
}
}
This sort of works, but anytime I want to reference the property of an object (like with remoteAnimal.id and remoteAnimal.updated) then the compiler complains because it doesn't know what kind of object a remoteAnimal is.
Has anyone done something like this before? Any ideas how I can do this so that I don't have to write this same code over and over for each of my Realm classes? Thanks!
Realm Object does not have id or updated. You can have your Dog,
Cat and Horse classes inherit from an Animal class that is a subclass of Object and that does have id or updated. Since these properties are defined on Animal they will be usable in all of the subclasses (Dog, Cat, Horse).
class Animal: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var updated = Date()
//Other properties...
}
class Dog: Animal {
//Other properties...
}
EDIT you can also abuse Objective C's NSObject setValue:forKey: to set the property by name. This is very sloppy typing and not good object oriented design but it does work. Here is a playground:
import UIKit
class A: NSObject {
#objc var customProperty = 0
}
let a = A()
a.setValue(5, forKey: "customProperty")
print(a.customProperty)

Remove duplicated in a Struct Array

I am filtering an array that can have a value where there are multiple Models of the same name, only they have different model numbers.
Variables
var modelArray = [model]()
Struct
struct model {
var modelName = String();
var modelNumber = String();
var manufacturer = String();
var phiTypeCode = String();
var phiTypeDesc = String();
}
Filter
var filteredArray = self.modelArray.filter { $0.manufacturer.range(of: manufacturerVar, options: .caseInsensitive) != nil }
This produces the correct filtered Array, only due to the possibility of similar models with different model numbers, I am trying to remove duplicates from filteredArray.
Fairly new to swift I don't have a great deal of experience making the struct hashable to be able to use the suggested solutions.
Hopefully this is more clear
First off, I tried making a sample in my PlayGround.
Conform your model model to the protocal Equatable, like so:
struct Car: Equatable {
var modelName = String()
var manufacturer = String()
init(modelName: String, manufacturer: String) {
self.modelName = modelName
self.manufacturer = manufacturer
}
static func == (lhs: Car, rhs: Car) -> Bool {
return lhs.modelName == rhs.modelName
}
}
In the code above, we're assuming that the modelName is the primary key of your model.
Now make a function that enumerates your data source and returns a new data source after checking the element one by one, like so:
// returns unique array
func unique(cars: [Car]) -> [Car] {
var uniqueCars = [Car]()
for car in cars {
if !uniqueCars.contains(car) {
uniqueCars.append(car)
}
}
return uniqueCars
}
Finally, you now have the function to generate a new unique data source.
Example:
// Variable
var cars = [Car]()
// Adding data models to data source
let car1 = Car(modelName: "Kia Picanto", manufacturer: "Kia")
let car2 = Car(modelName: "Honda XX", manufacturer: "Honda")
let car3 = Car(modelName: "Honda XX", manufacturer: "Honda")
cars.append(car1)
cars.append(car2)
cars.append(car3)
// Now contains only two elements.
let uniqueCars = unique(cars: cars)

Function to return object's property

I'd like to change an object's parameter, but I like to decide later about what's the parameter that should change. I'm thinking to make a function for that, but don't know how. Here's a sample:
class Car {
var color = "green"
var brand = "Zastava"
var mechanic = "Mark Gaia"
}
struct Change {
var property: String
var newValue: String
}
let car = Car()
let change = Change(property: "brand", newValue: "Fiat")
car.propertyFromChange(change) = change.newValue // ??? How to implement this line
How to make a function that returns what's the parameter of an object that needs to be changed?
Alternatively, how to choose which parameter to change?
(...I thought about switch statement. Is a good direction?)
I'm pretty sure that's something that has already been discussed on internet. I've read everything about functions and couldn't find the solution, so I guess I'm searching using wrong keywords.
You don't need to create a function you can use KVC like this after you inherit from NSObject, or you can make a new func that guards again bad property names.
class Car: NSObject {
var color = "green"
var brand = "Zastava"
var mechanic = "Mark Gaia"
}
struct Change {
var property: String
var newValue: String
}
let car = Car()
let change = Change(property: "brand", newValue: "Fiat")
car.setValue(change.newValue, forKey: change.property)
Keep in mind that this code will crash if you enter bad property names.
Hope this answers your question.
If Carinherits from NSObject you get key-value-coding for free and can change the value with setValue:forKey
class Car : NSObject {
var color = "green"
var brand = "Zastava"
var mechanic = "Mark Gaia"
}
struct Change {
var property: String
var newValue: String
}
let car = Car()
let change = Change(property: "brand", newValue: "Fiat")
car.setValue(change.newValue, forKey: change.property)
You can take advantage of KVC setValue(value: AnyObject?, forKey key: String) method by making Car a subclass of NSObject.
class Car: NSObject {
var color = "green"
var brand = "Zastava"
var mechanic = "Mark Gaia"
}
struct Change {
var property: String
var newValue: String
}
let car = Car()
let change = Change(property: "brand", newValue: "Fiat")
car.setValue(change.newValue, forKey: change.property)
You can simplify your approach a bit by doing this
If you store all your properties in the struct itself, you wouldnt need to worry about that at all.
:) Hope this helps
struct CarProperties {
var color : String = ""
var brand : String = ""
var mechanic : String = ""
}
class Car {
var carProperties = CarProperties()
}
let car = Car()
car.carProperties.color = "Green"
car.carProperties.brand = "yourFavBrand"
car.carProperties.mechanic = "whatever"
let change = "this"
// Suppose you want to change the brand
car.carProperties.brand = change
print(car.carProperties.brand)