Select all records of a List<T> relationship into Results<T>? - ios

I'm binding a table view to a collection of Results<Person> this result set can be filtered but normally it is not. The problem is that when I try to bind - say; person.children to the table view which is of type List<Person>. If I don't filter it, I don't get a Results<Person> type, so I need to have two different properties to store the dataset for the table. One for Results, one for List.
My question is... is there a way to return all records from a List as Results? Something similar to .all()? I have tried to use an empty NSPredicate, but this is not allowed and a property name must be specified. I want my function to be generic enough that it can be called on any List to obtain all Results. I don't want to have to specify say... .filter("firstName != ''") just to get all the results.
I have currently implemented something like this... But I'm curious if there's a better way.
extension List {
func all() -> Results<T> {
let primaryKey = T.primaryKey() ?? "id"
return self.filter("\(primaryKey) != ''")
}
}
EDIT: Looks like this implementation of All will not work for Objects with a primary key of type Int. Is there anyway to check an object's primary key type?

Using TRUEPREDICATE seems to work!
extension List {
/// Returns an Results object for all Objects in the List.
func all() -> Results<T> {
return self.filter("TRUEPREDICATE")
}
}

Related

Filtering Realm with nested subqueries

My app has data that looks like this.
class ShelfCollection: Object {
let shelves: List<Shelf>
}
class Shelf: Object {
let items: List<Item>
}
class Item: Object {
var name: String
let infos: List<String>
}
I'm trying to get all shelves in a shelf collection where any items match the query either by name or by an element in their infos list. From my understanding this predicate should be correct, but it crashes.
let wildQuery = "*" + query + "*"
shelfResults = shelfCollection.shelves.filter(
"SUBQUERY(items, $item, $item.name LIKE[c] %# OR SUBQUERY($item.infos, $info, info LIKE[c] %#).#count > 0).#count > 0",
wildQuery, wildQuery
)
It complies as a NSPredicate, but crashes when Realm is attempting to parse it, throwing me
'RLMException', reason: 'Object type '(null)' not managed by the Realm'
I suspect the nested subquery might be what fails, but I don't know enough about NSPredicate to be sure. Is this an acceptable query, and how can I make it.. work?
This is an answer and a solution but there's going to be a number of issues with the way the objects are structured which may cause other problems. It was difficult to create a matching dataset since many objects appear within other objects.
The issue:
Realm cannot currently filter on a List of primitives
EDIT: Release 10.7 added support for filters/queries as well as aggregate functions on primitives so the below info is no longer completely valid. However, it's still something to be aware of.
so this Item property will not work for filtering:
let infos: List<String>
However, you can create another object that has a String property and filter on that object
class InfoClass: Object {
#objc dynamic var info_name = ""
}
and then the Item class looks like this
class Item: Object {
var name: String
let infos = List<InfoClass>()
}
and then you filter based on the InfoClass object, not it's string property. So you would have some objects
let info0 = InfoClass()
info0.info_name = "Info 0 name"
let info1 = InfoClass()
info1.info_name = "Info 1 name"
let info2 = InfoClass()
info2.info_name = "Info 2 name"
which are stored in the Item->infos list. Then the question
I'm trying to get all shelves in a shelf collection...
states you want to filter for a collection, c0 in this case, shelves whose items contain a particular info in their list. Lets say we want to get those shelves whose items have info2 in their list
//first get the info2 object that we want to filter for
guard let info2 = realm.objects(InfoClass.self).filter("info_name == 'Info 2 name'").first else {
print("info2 not found")
return
}
print("info2 found, continuing")
//get the c0 collection that we want to get the shelves for
if let c0 = realm.objects(ShelfCollection.self).filter("collection_name == 'c0'").first {
let shelfResults = c0.shelves.filter("ANY items.infoList == %#", info2)
for shelf in shelfResults {
print(shelf.shelf_name)
}
} else {
print("c0 not found")
}
I omitted filtering for the name property since you know how to do that already.
The issue here is the infos could appear in many items, and those items could appear in many shelf lists. So because of the depth of data, with my test data, it was hard to have the filter return discreet data - it would probably make more sense (to me) if I had example data to work with.
Either way, the answer works for this use case, but I am thinking another structure may be better but I don't know the full use case so hard to suggest that.

Sorting Realm results collection on computed property?

I'm using Realm in my Swift iOS app and I have a model that I'm trying to add a computed property to. Here's what the class looks like:
class Conversation: Object {
#objc dynamic var conversationId: String = generateConversationId()
#objc dynamic var createdTime = Date()
#objc dynamic var title: String = ""
let messages = List<Message>()
var lastUpdated: Date {
if let newestMessage = self.messages.sorted(byKeyPath: "timestamp", ascending: false).first {
return newestMessage.timestamp
} else {
return Date(timeIntervalSince1970: 0)
}
}
}
As you can see, this object represents a conversation which has several properties one of which is a list of messages that belong to the conversation. The computed property that I've added (lastUpdated) should just return the timestamp of the most recent message in the messages list or, when the list is empty, it would return a default date value.
In one of my view controllers, I'm creating a results collection to access the Conversation objects like this:
var conversations: Results<Conversation> = realm.objects(Conversation.self)
This works and I can access the lastUpdated property on each Conversation as expected. However, I'd like to have this collection sorted by the lastUpdated property so I tried modifying my results collection like this:
var conversations: Results<Conversation> = realm.objects(Conversation.self).sorted(byKeyPath: "lastUpdated", ascending: true)
When I do this, the app throws the following exception:
Terminating app due to uncaught exception 'RLMException', reason: 'Cannot sort on key path 'lastUpdated': property 'Conversation.lastUpdated' does not exist.'
I think this might be happening because the lastUpdated computed property isn't persisted on the Conversation object (I would use Realm Studio to quickly verify this but my Realm is encrypted). I'm not explicitly ignoring this property but I assume this is the case because lastUpdated is not decorated with #objc dynamic like all of my other properties. I tried adding that, wiping my app install, recompiling and testing again but I still end up with the same exception.
Maybe it's not possible to persist a computed property (because, well, it's computed!) but is there another way to specify that my Conversation results collection should be sorted by this property?
As you suspect, you cannot persist a computed property and Realm's sorted(byKeyPath:,ascending:) method only works with persisted properties. There is no way to sort a Results instance based on a computed property while keeping the auto-updating Results.
However, there is a workaround if you don't need the auto-updating nature of Results. You can use Swift's sorted method, which takes a closure as its sorting parameter, which will result in a return value of type [Conversation]:
let conversations = realm.objects(Conversation.self).sorted(by: {$0.lastUpdated < $1.lastUpdated})

Swift understand generics with enums

I have following code written by other person:
public enum MSEntity<T : Metable> {
case meta(MSMeta)
case entity(T)
public func value() -> T? {
guard case let MSEntity.entity(value) = self else {
return nil
}
return value
}
public func objectMeta() -> MSMeta {
switch self {
case .entity(let entity): return entity.meta
case .meta(let meta): return meta
}
}
}
I have following questions:
1) what is the point of doing case entity(T)? What is "value" of that enum case and for what it may be used for?
2) I can't understand public func value() func.
What is that guard checking? What is value? Can someone provide an example where similar code may be useful?
The enum seems to represent an object that can either only contain metadata or a entity with metadata.
It has 2 cases: meta represents an object with only metadata and that metadata is this case's associated value, entity represents an object that has a entity and metadata. This case's associated value is the object's entity, which contains metadata (so the constraint on T is Metable)
what is the point of doing case entity(T)? What is "value" of that enum case and for what it may be used for?
Well, for this you have to look at the docs or ask the person who wrote the code. As far as I'm concerned, MSEntity is an object that has 2 states:
only metadata (meta case)
entity and metadata (entity case)
So the entity case is there to represent that.
I can't understand public func value() func. What is that guard checking? What is value?
The value function seems to return the object's entity, but what if this object has no entity (i.e. self is actually in the meta case)? The author of this code decided to return nil. That is why there is a check: if self is in the meta case. The word value is part of the pattern matching. It might be clearer if I wrote it like this:
guard case let MSEntity.entity(let value) = self else {
Whatever is in the associated value of entity case is put into a variable called value, so that you can return it later.
The (MSMeta) and (T) associated values of the meta and entity cases, respectively.
The value() method (which IMO should be a computed property of type T?, rather than a function of type () -> T?) returns the associated value of the entity case, if self is entity, otherwise it returns nil (when the self is meta).

How to convert Observable array of a type to Observable array of a different type with RxSwift

I'm new to RxSwift and I came across the next situation:
self.viewModel!.country
.flatMapLatest { country -> Observable<City> in
country!.cities!.toObservable()
}
.map { city -> SectionModel<String, String> in
SectionModel(model: city.name!, items: city.locations!)
}
.toArray()
.bindTo(self.tableView.rx_itemsWithDataSource(dataSource))
.addDisposableTo(self.disposeBag)
Seems to me like toArray() does nothing and I don't know why. On the other hand this code does what I want. I would like to know why the previous code does not work the same way:
self.viewModel!.country
.flatMapLatest { country -> Observable<[SectionModel<String, String>]> in
var models = [SectionModel<String, String>]()
for city in country!.cities! {
models.append(SectionModel(model: city.name!, items: city.locations!))
}
return Observable.just(models)
}
.bindTo(self.tableView.rx_itemsWithDataSource(dataSource))
.addDisposableTo(self.disposeBag)
Thanks in advance.
EDIT:
View model implementation is as follow:
class LocationViewModel {
let country = BehaviorSubject<Country?>(value: nil)
}
Country has a property 'cities' which is an array of City.
#solidcell You must be right, if I put debug() before and after toArray() I get 2 subscriptions, one for each debug(), and 1 next event for each array item only for the before toArray() debug().
But then, why isn't it completing?
It's probably because country!.cities for some countries doesn't terminate. toArray() will not emit any elements until the Observable completes. Only once the Observable completes does it package all of the elements into one single element as an Array. However, I can't say for certain if this is your issue, since I don't know how you've implemented country!.cities.
Try putting a .debug() in there and see if you can spot if it's because an Observable of cities isn't completing.

Grails querying model containing an enum set

My domain class is
class RoomWantedAd{
Set<MateAgeRange> mateAgeRanges
static hasMany=[mateAgeRanges :MateAgeRange]
}
Her MateAgeRange is :
enum MateAgeRange {
TWENTIES('18-29')
,THIRTIES('30-39')
,FOURTIES("40-49")
,FIFTIES("50-59")
,SIXTIES("60+")
final String value
private MateAgeRange(String value) {
this.value = value
}
String toString() { value }
String getKey() { name() }
static belongsTo=[roomWanted:RoomWanted]
}
My problem is searching. In the search page, a person can select 0 or more values in [18-29, 30-39, 40-49, 50-59, 60+]. In the db, 0 or more values among [18-29, 30-39, 40-49, 50-59, 60+] are stored in field 'mateAgeRanges'.
Let db contains [30-39, 50-59] in 'mateAgeRange' field. Let in the search page, the user selects [18-29, 50-59, 60+]. Then the Ad corresponding to the above list must be returned. This is because at least one value in user's selection is present in the db list. How is it possible. Is it possible using an SQL query or grails GORM query.
you have to use exactly the same <g:select/> tag as you are using for create/update. Thus, you will see human readable values like THIRTIES in browser, but in the background the '30-39' values will be used.

Resources