Realm: Query with variable in Swift 2 - swift

absolute newbie-question: I have a model that consists of two objects in a Realm-database: Person and Dog. Person contains a list of Dogs. How can I query/filter for a list of dogs when the name of the Person is being handed over from a previous VC? For a tableView I need the query at the top of my VC-code. To access a single Person, I tried
personName: String! (derived from previous VC)
let predicate = NSPredicate(format: "name = %#", personName)
let currentPerson = try! Realm().objects(Person).filter(predicate)
but I get
Instance member 'personName' cannot be used on type 'dogsVC'
What I want is simply list all the dogs of a specific person in a tableView.
Thanks in advance!

You will need to instantiate your predicate in viewDidLoad for example.
Or make it a computed property:
var predicate:NSPredicate {
return NSPredicate(format: "name = %#", personName)
}
And that's because you need to wait for your class to initialize before using self.

Related

Query MongoDB Realm array only at first index

I need to query MongoDB Realm from synced iOS and Android app. In Swift I can write something like this:
let dictionary = realm?.objects(myReamlObject.self)
let results = dictionary?.where {
$0.senses.glosses.term == "the term I want"
}
or using predicate:
let results = dictionary?.filter("ANY senses.glosses.term == %#", "the term I want")
Both work well, but I don't want to check ALL senses.glosses.term.
Every entry has (or could have) many senses and many glosses.
I would like to check term of first senses in first glosses only.
Something I would write like this:
let results = dictionary?.where {
$0.senses[0].glosses[0].term == "the term I want"
}
But it gives error:
Referencing subscript 'subscript(_:)' on 'Query' requires that
'List<myRealmObject_senses>' conform to 'RealmKeyedCollection'
Any suggestion on how to query only first index of an array in MongoDB Realm? Thank you
Let me re-state the question
How to query a Realm objects' List property - but only query on the first
element in the List.
The answer is going to depend on the amount of results being worked with.
Here's how to do it in a way that's O.K. for small datasets but NOT RECOMMENDED
Your models were not included in the question so let me use a simplified model of a PersonClass that as a List of DogClass objects
class PersonClass: Object {
#Persisted var name = ""
#Persisted var dogList = List<DogClass>()
}
class DogClass: Object {
#Persisted var name = ""
}
The idea here is to use Swift high-level functions to only test the first item in each persons doglist for a match (this can be applied to other languages as well)
//get all the people
let peopleResults = realm.objects(PersonClass.self)
//use the high-level Swift function compactMap to return all of
// the people whose first dog is named "Spot"
let persons = peopleResults.compactMap { person -> PersonClass? in
if person.dogList.first?.name == "Spot" {
return person
}
return nil
}
The downside is that this code overrides a fundamental advantage of Realm - that Realm objects are lazily loaded if Realm functions are used e.g. as soon as a high-level Swift function is used, ALL of the objects are loaded into memory, potentially overwhelming the device.
A better option is to simply add a managed property to the PersonClass that also points to the 0'th element in the list.
class PersonClass: Object {
#Persisted var name = ""
#Persisted var dogList = List<DogClass>()
#Persisted var mainDog: DogClass?
func addMainDog(withDog: DogClass) {
self.dogList.append(withDog)
self.mainDog = withDog
}
}
as you can see, there's also a function to add that first dog to the list and also populates the mainDog property which points to the same object. It's one property so the overall impact is very low but the advantages for simple queries are very high.
From there the query becomes trivial
let peopleResults = realm.objects(PersonClass.self).where { $0.mainDog.name == "Spot" }
Expanding on this, you could save the 0th element of each List object in the Parent object or even have a property in each child object that points to the first element in it's respective list.

Convert filter expression to NSPredicate format

I would like to perform this filter method using NSPredicate, but I'm having trouble converting the syntax.
objectsCollection.filter { $0.stringIds.contains(id) }
Sample class
class Object {
let stringIds: [String]
}
let objectsCollection: [Object]
let id = "The id we want to look for"
objectsCollection.filter { $0.stringIds.contains(id) }
My attempt with NSPredicate
This is how I thought it would work, but seems like it doesn't.
let filter = NSPredicate(format: "%# IN stringIds", id)
objectsCollection.filter(filter)
Error
reason: 'Expected object of type (null) for property 'stringIds' on object of type 'Object', but received: 6011ea4dda6853a3af97376e'
There are a few issues that need to be addressed.
This
let stringIds: [String]
is not a realm object and will not be persisted in Realm.
That needs to be a List property and the object type of the list is another Realm object. Realm Lists do not support primitives (very well). Also, don't name your objects the same name as another object.
class MyClass: Object {
let myStringIdList = List<MyStringIdClass>
}
and
class MyStringIdClass: Object {
#objc dynamic var myId = ""
}
to then get all of the MyClass objects that had a certain stringId in their list
let results = realm.objects(MyClass.self).filter("ANY myStringIdList.myId == %#", idToFind)
The string inside .filter can also be an NSPredicate if needed.
You can also use LinkingObjects to navigate back to the MyClass objects as well.
One other thing, when you cast realm objects to an array, they loose their connection to realm and are no longer live updating objects.
Also, Realm objects are lazily loaded meaning that thousands of objects have very little memory impact. Casting them to an array however, loads ALL of that data and can overwhelm the device with a large data set.

Delete specific object from LinkingObjects list - Realm Swift

I am currently trying out Realm on a test project and I have been struggling with removing a specific object from a List. LensDBObject and ListDBObject. LensDBObject contains a list of lenses and ListDBObject are lists of existing lenses. A lens can be in multiple lists and I'd like to remove a specific lens from a specific list but not remove if from the other lists.
Below are my two classes:
#objcMembers class LensDBObject: Object {
dynamic var id = UUID().uuidString
dynamic var manufacturer = ""
dynamic var series = ""
dynamic var serial = ""
dynamic var isSelected = false
override static func primaryKey() -> String? {
return "id"
}
let objects = LinkingObjects(fromType: ListDBObject.self, property: "lensList")
}
#objcMembers class ListDBObject: Object {
dynamic var listName = ""
let lensList = List<LensDBObject>()
}
Below is my code to find a specific lens in the list I want. The values returned are what I expect.
let listToSearch = realm.objects(ListDBObject.self).filter("listName == %#", "List 542")
print(listToSearch)
let filteredResults = listToSearch[0].lensList.filter("manufacturer == %# AND series == %# AND serial == %#", "Panavision" , "Primo Prime", "407")
print(filteredResults)
However, when I try to delete filteredResults, it deletes it from the lensDBOject altogether. I just want to be able to delete this specific lens from this specific list.
try! realm.write {
realm.delete(filteredResults)
}
I tried using for loops to get the index of the lens in the list and then delete it directly from that. But it still deletes the lens everywhere.
Am I missing something? Should I be using a one-to-many relationship as opposed to a LinkingObject?
Thanks for you help!
Try something like this. You only want to remove the lens from the list, not delete it from the Realm.
try! realm.write {
filteredResults.forEach { lens in
if let index = listToSearch[0].lensList.index(of: lens) {
listToSearch[0].lensList.remove(at: index)
}
}
}
Note that this will remove from that one specific list all lenses that match your filter.
Edit: Updated to reflect Realm's custom List class.
The if let is required, because index(of:) could potentially return nil if the object is not found in the list. Additionally, we must do it one item at a time rather than getting all the indexes first, since removing an item would cause the index array to be wrong.

How to filter using NSPredicate based on if the to many relationship set contains a specific value

I have to two entities. One entity Person, the other Message. For every one Person, there are many messages (so there is a one to many relationship). I need to populate my tableView with Persons, but only Persons which have a set of messages that has at least one message with the attribute sent equalling success.
If what I said is not clear, here is basically what I want:
(obviously this does not compile, I completely made it up for the sake of the question) NSPredicate(Person.messages.contains (sent == "success")
Edit:
Forgot to mention that I'm using Core - Data, not just a regular array. I need that NSPredicate for fetched results controller.
"ANY" can be used with a to-many relationship to find the
objects for which at least one of the related objects satisfies
a condition. In your case:
NSPredicate(format: "ANY messages.sent == %#", "success")
You could use filter function on swift array like so:
struct Person {
var name: String?
var meessages = [Message]()
}
struct Message {
var sent: Bool = false
}
let arr: [Person] = [
Person(name: "person1", meessages: [Message(sent: true), Message(sent: false)]),
Person(name: "person2", meessages: [Message(sent: false), Message(sent: false)]),
Person(name: "person2", meessages: [Message(sent: true), Message(sent: true)])
]
let filtered = arr.filter({ ($0.meessages.filter({ $0.sent == true })).count > 0 })
Thats because your Persons Array should be NSArray , NSPredicates works only with Foundation objects not swift types.in Swift arrays
you have filter method that you can call and pass it the filtering closure.

Filtering through CD relationships in swift

How can an array of person objects be extracted from an array of memberships that have person.personId != self.id?
For an array of memberships, each has a person object. I would like to get all the person objects directly for all other persons.
If getting the first one like this
if let memberships = self.memberships.allObjects as? [Membership],
let person = memberships.filter({$0.person.personId != userId}).first?.person {
How can every person be extracted and returned in an array using swifts collection functions?
You could try something like this:
if let memberships = self.memberships.allObjects as? [Membership] {
// Filter to remove the membership with userID,
// and then map to an array of people
let people = memberships.filter({$0.person.personId != userId}).map { $0.person }
}
Somewhere it looks like you are going to need to map an array of Memberships to an array of person objects. Hopefully if the above isn't exactly right it will point you in the right direction.