Many-to-many relationship where one entity has multiple properties of the same type - swift

I'm not sure how to phrase this, which may be why I couldn't find any information, but I have a many-to-many relationship where one of the entities has multiple properties of many of the other.
For instance, take the Artist and Song relationship. One artist can have many songs, and one song can have many artists. BUT, a song can have (many) main artists, (many) featured artists and (many) album artists. So these all come from the same table, but I'm not sure how to model this.
In code, I am using GRDB in Swift, so I follow the docs with:
import GRDB
struct Artist: TableRecord {
let id: Int
let name: String
...
static let artistSong = hasMany(ArtistSong.self)
static let songs = hasMany(Song.self, through: artistSong, using: ArtistSong.song)
}
struct ArtistSong: TableRecord {
static let artist = belongsTo(Artist.self)
static let song = belongsTo(Song.self)
}
struct Song: TableRecord {
let id: Int
let name: String
...
static let artistSong = hasMany(ArtistSong.self)
static let artists = hasMany(Artist.self, through: artistSong, using: ArtistSong.artist)
}
I imagine this would be fine if there were simply only "artists" on a song. But I have 3 different types of artists for a song (main, featured, album artist) but they all come from the same Artist table.
What would be the best way to tackle this?

For Many to Many relationship you need additional tables to define the relationship. Like with addition to Song and Artist tables you need additional table to define main, featured, album artist relationships.
These are join table which contains common fields from two or more other tables. In this way, it creates a many-to-many relationship between data.
You can go ahead and have only one extra table with a column defining relationship between song and artist. But this will create redundancy and several anomalies. For which you need Normalization. And need to have additional tables.
Normalization is the process of minimizing redundancy from a relation
or set of relations.
Note: Minimum of three tables are required in the Many to Many relationships.
If we try to merge it will create redundant data.

But I have 3 different types of artists for a song (main, featured, album artist) but they all come from the same Artist table.
What would be the best way to tackle this?
You can filter the Song.artists relationship. And you can define filtered associations as well:
extension Song {
static let artists = hasMany(...)
static let mainArtists = artists
.filter(...) // Column("kind") == "main", maybe
.forKey("mainArtists")
}
In the above example, I changed the key of the mainArtists association, so that:
you can use both artists and mainArtists associations in the same request, when needed.
the mainArtists association is decoded in the mainArtists property of a compound record type when you fetch from requests that include(all: Song.mainArtists), for example.

I was interested in this too, because that's a great example for HasManyThrough associations and for an implementation of edges in a graph-like data structure. I thought you had this schema in mind:
I first tried to solve it using the answer by Gwendal Roué but didn't quite get it to work. I opened a GRDB issue to ask for more help. He answered within hours, solved all my problems and explained the solutions in detail.
This is what i learned:
Distinct has-many- and has-many-through-associations are necessary to make sure requests treat them independently. Otherwise it would, for example, not be possible to fetch main artists and feature artists using the same request. Without distinct associations the request would combine the filters and only return artists that are defined main and feature using the same SongArtist (which is not possible, so empty arrays are returned).
That's why my code defines associations for each ArtistRelation. My Song struct has five has-many- and an additional five has-many-through-associations.
When dealing with joined requests it is better to include the entire row of the closure table. It is possible to fetch structs that contain isolated columns only (for example when we are only interested in the relation) but that would make the code more complex. Gwendal Roué does not recommend this.
That's why my struct in code example 6 contains an entire SongArtist row although i only need its relation.
The following code contains a full scenario and these fetch requests to test the implementation:
All songs and the corresponding artists, but without ArtistRelation info
All songs and the corresponding artists, grouped in separate arrays according to their ArtistRelation
All songs with their number of relationships
All songs that have no feature artists
Direct access to the closure table to get all SongArtist rows that define feature artists
All songs with their artists and their relationships
import GRDB
struct Artist: Codable, Hashable, FetchableRecord, MutablePersistableRecord {
mutating func didInsert(with rowID: Int64, for column: String?) { id = rowID }
var id: Int64?
var name: String
static let songArtists = hasMany(SongArtist.self)
static let songs = hasMany(Song.self, through: songArtists, using: SongArtist.song)
}
struct Song: Codable, Hashable, FetchableRecord, MutablePersistableRecord {
mutating func didInsert(with rowID: Int64, for column: String?) { id = rowID }
var id: Int64?
var name: String
// Distinct has-many-associations are necessary to make sure requests treat them independently. See https://github.com/groue/GRDB.swift/issues/1068#issuecomment-927801968 for more information.
static let songArtists = hasMany(SongArtist.self)
static func songArtists(forRelation relation: ArtistRelation) -> HasManyAssociation<Song, SongArtist> {
songArtists
.filter(Column("relation") == relation)
.forKey("\(relation.rawValue)SongArtists")
}
static let albumSongArtists = songArtists(forRelation: .album)
static let featureSongArtists = songArtists(forRelation: .feature)
static let mainSongArtists = songArtists(forRelation: .main)
static let partnerSongArtists = songArtists(forRelation: .partner)
// Distinct has-many-through-associations are necessary to make sure requests treat them independently. See https://github.com/groue/GRDB.swift/issues/1068#issuecomment-927801968 for more information.
static let artists = hasMany(Artist.self, through: songArtists, using: SongArtist.artist)
static func artists(forRelation relation: ArtistRelation) -> HasManyThroughAssociation<Song, Artist> {
hasMany(
Artist.self,
through: songArtists(forRelation: relation),
using: SongArtist.artist)
.forKey("\(relation.rawValue)Artists")
}
static let albumArtists = artists(forRelation: .album)
static let featureArtists = artists(forRelation: .feature)
static let mainArtists = artists(forRelation: .main)
static let partnerArtists = artists(forRelation: .partner)
}
enum ArtistRelation: String, Codable, DatabaseValueConvertible {
case album
case feature
case main
case partner
}
struct SongArtist: Codable, Hashable, FetchableRecord, PersistableRecord {
let songId: Int64
let artistId: Int64
let relation: ArtistRelation
static let song = belongsTo(Song.self)
static let artist = belongsTo(Artist.self)
}
let queue = DatabaseQueue()
try queue.write { db in
try db.create(table: "artist") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull()
}
try db.create(table: "song") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull()
}
try db.create(table: "songArtist") { t in
t.column("songId", .integer).notNull().indexed().references("song")
t.column("artistId", .integer).notNull().indexed().references("artist")
t.column("relation").notNull()
// We do not define primary keys here using `t.primaryKey(["songId", "artistId"])` because we allow multiple `SongArtist` rows with the same id combination, e.g. when the album artist is also the main artist of a song. See https://github.com/groue/GRDB.swift/issues/1063#issuecomment-925735039 for an example that defines primary keys for a closure table.
}
// Testing real song data from https://music.apple.com/de/album/magnet/1102347168
var missK8 = Artist(name: "Miss K8")
try missK8.insert(db)
var mcNolz = Artist(name: "McNolz")
try mcNolz.insert(db)
var radicalRedemption = Artist(name: "Radical Redemption")
try radicalRedemption.insert(db)
var scream = Song(name: "Scream (feat. Mc Nolz)")
try scream.insert(db)
try SongArtist(songId: scream.id!, artistId: missK8.id!, relation: .album).insert(db)
try SongArtist(songId: scream.id!, artistId: mcNolz.id!, relation: .feature).insert(db)
try SongArtist(songId: scream.id!, artistId: radicalRedemption.id!, relation: .main).insert(db)
try SongArtist(songId: scream.id!, artistId: missK8.id!, relation: .partner).insert(db)
var raidersOfRampage = Song(name: "Raiders of Rampage")
try raidersOfRampage.insert(db)
try SongArtist(songId: raidersOfRampage.id!, artistId: missK8.id!, relation: .album).insert(db)
try SongArtist(songId: raidersOfRampage.id!, artistId: missK8.id!, relation: .main).insert(db)
try SongArtist(songId: raidersOfRampage.id!, artistId: mcNolz.id!, relation: .partner).insert(db)
}
// 1: All songs and the corresponding artists, but without `ArtistRelation` info
try queue.read { db in
struct SongInfo: FetchableRecord, Decodable, CustomStringConvertible {
var song: Song
var artists: Set<Artist>
var description: String { "\(song.name) → artists:[\(artists.map(\.name).joined(separator: ", "))]" }
}
let request = Song.including(all: Song.artists)
let result = try SongInfo.fetchAll(db, request)
print("1: \(result)")
// > 1: [Scream (feat. Mc Nolz) → artists:[Radical Redemption, McNolz, Miss K8], Raiders of Rampage → artists:[Miss K8, McNolz]]
}
// 2: All songs and the corresponding artists, grouped in separate arrays according to their `ArtistRelation`
try queue.read { db in
struct SongInfo: FetchableRecord, Decodable, CustomStringConvertible {
var song: Song
var albumArtists: Set<Artist>
var featureArtists: Set<Artist>
var mainArtists: Set<Artist>
var partnerArtists: Set<Artist>
var description: String { "\(song.name) → albumArtists:\(albumArtists.map(\.name)), featureArtists:\(featureArtists.map(\.name)), mainArtists:\(mainArtists.map(\.name)), partnerArtists:\(partnerArtists.map(\.name))" }
}
let request = Song
.including(all: Song.albumArtists)
.including(all: Song.featureArtists)
.including(all: Song.mainArtists)
.including(all: Song.partnerArtists)
let result = try SongInfo.fetchAll(db, request)
print("2: \(result)")
// > 2: [Scream (feat. Mc Nolz) → albumArtists:["Miss K8"], featureArtists:["McNolz"], mainArtists:["Radical Redemption"], partnerArtists:["Miss K8"], Raiders of Rampage → albumArtists:["Miss K8"], featureArtists:[], mainArtists:["Miss K8"], partnerArtists:["McNolz"]]
}
// 3: All songs with their number of relationships
try queue.read { db in
struct SongInfo: FetchableRecord, Decodable, CustomStringConvertible {
var song: Song
var albumSongArtistCount: Int
var featureSongArtistCount: Int
var mainSongArtistCount: Int
var partnerSongArtistCount: Int
var description: String { "\(song.name) → album:\(albumSongArtistCount), feature:\(featureSongArtistCount), main:\(mainSongArtistCount), partner:\(partnerSongArtistCount)" }
}
let result = try Song
.annotated(with: Song.albumSongArtists.count)
.annotated(with: Song.featureSongArtists.count)
.annotated(with: Song.mainSongArtists.count)
.annotated(with: Song.partnerSongArtists.count)
.asRequest(of: SongInfo.self)
.fetchAll(db)
print("3: \(result)")
// > 3: [Scream (feat. Mc Nolz) → album:1, feature:1, main:1, partner:1, Raiders of Rampage → album:1, feature:0, main:1, partner:1]
}
// 4: All songs that have no feature artists
try queue.read { db in
let result = try Song
.having(Song.featureArtists.isEmpty)
.fetchAll(db)
print("4: \(result.map(\.name))")
// > 4: ["Raiders of Rampage"]
}
// 5: Direct access to the closure table to get all SongArtist rows that define feature artists
try queue.read { db in
struct SongArtistInfo: FetchableRecord, Decodable, CustomStringConvertible {
var song: Song
var artist: Artist
var relation: ArtistRelation
var description: String { "\(song.name) → \(relation):\(artist.name)" }
}
let request = SongArtist
.including(required: SongArtist.song)
.including(required: SongArtist.artist)
.filter(Column("relation") == ArtistRelation.feature)
let result = try SongArtistInfo.fetchAll(db, request)
print("5: \(result)")
// > 5: [Scream (feat. Mc Nolz) → feature:McNolz]
}
// 6: All songs with their artists and their relationships
try queue.read { db in
// It is possible to fetch structs that only contain `relation` as an isolated column but that would make the code more complex. It is easier to fetch the entire `SongArtist` row and get the relation from there. See https://github.com/groue/GRDB.swift/issues/1068#issuecomment-927815515 for more information.
struct SongInfo: Decodable, FetchableRecord, CustomStringConvertible {
struct ArtistInfo: Decodable, Hashable, CustomStringConvertible {
var songArtist: SongArtist
var artist: Artist
var description: String { "\(songArtist.relation):\(artist.name)" }
}
var song: Song
var artists: Set<ArtistInfo>
var description: String { "\(song.name) → \(artists)" }
}
let result = try Song
.including(all: Song.songArtists
.including(required: SongArtist.artist)
.forKey("artists"))
.asRequest(of: SongInfo.self)
.fetchAll(db)
print("6: \(result)")
// > 6: [Scream (feat. Mc Nolz) → [feature:McNolz, main:Radical Redemption, album:Miss K8, partner:Miss K8], Raiders of Rampage → [album:Miss K8, main:Miss K8, partner:McNolz]]
}

Related

Retrieving records on a Reference List (hierarchical records) - Swift Concurrency & CloudKit

I am using CloudKit (the default public database) to store information about matches between two teams. I am trying to use the Swift Concurrent approach with async/await to retrieve the match and team data.
The matches are stored as “Match” RecordType with some metadata, like the year the match was played (“matchYear”). The “Match” Record Type also contains a Reference List to a RecordType called “Team” (two teams for each match). The team names are stored in the “Team” Record Type (field “teamName”)
Rough description of my CloudKit Schema
“Match” Record Type
Fields:
matchYear: Int64
teams: Reference (List)
…
“Team” Record Type
Fields:
teamName: String
…
With the following code I am able to read all “Match” records from the CloudKit and store that list to an array of MatchRecords, which I can then use to display a list of matches (using SwiftUI, if that makes any difference). The resulting array also contains references to the teams for each match.
struct MatchRecord {
var recordID: CKRecord.ID
var matchYear: Int
var teams: [CKRecord.Reference]
…
}
…
private lazy var container = CKContainer.default()
private lazy var database = container.publicCloudDatabase
…
#MainActor func loadMatchList() async throws -> [MatchRecord] {
let predicate = NSPredicate(value: true)
let sortDescriptors = [NSSortDescriptor(key: “matchYear", ascending: false),NSSortDescriptor(key: "___createTime", ascending: false)]
let query = CKQuery(recordType: “Match”, predicate: predicate)
query.sortDescriptors = sortDescriptors
let (matchResults, _) = try await database.records(matching: query)
let allMatches: [MatchRecord] = matchResults
.compactMap { _, result in try? result.get() }
.compactMap {
let match = MatchRecord(
recordID: $0.recordID,
matchYear: $0[“matchYear"] as! Int,
teams: $0["teams"] as! [CKRecord.Reference])
// Potentially team record retrieval goes here…
return match
}
return allMatches
}
How do I retrieve the Team records as part of this async function so that I will have also the names of the teams available for the list view?
(I could potentially first fetch the list of matches and then loop through that array and retrieve the detail data for each match but that seems wasteful. I am guessing there should be a way to insert this in the compactMap closure marked down in the code sample above, but my map/reduce and async/await skills fail me…)
The data structure could be something along as described below.
struct MatchRecord {
var recordID: CKRecord.ID
var matchYear: Int
var teams: [TeamRecord]
…
}
struct TeamRecord {
var recordID: CKRecord.ID
var teamName: String
…
}
FWIW I know that as there are only two teams for each game, I could also store the team names as part of the Match record, but In the future I am planning to also include the roster information for each team, so I need to come up with a clean and scalable method to retrieve this type of hierarchical data from CloudKit…
Using CoreData with CloudKit is not an option here.
After some thinking I came up with one somewhat ugly solution using async properties. The Match and Team struct definitions are roughly these
struct MatchRecord {
var recordID: CKRecord.ID
var matchYear: Int
var teams: [TeamRecord]
…
}
struct TeamRecord {
var referenceID: CKRecord.Reference
var name: String {
get async {
try! await CKContainer.default().publicCloudDatabase.record(for: referenceID.recordID)["teamName"] as! String
}
}
}
The compactMap gets small for loop in there to populate the teams structure (only storing the ID as the names are retrieved only later)
.compactMap {
let match = MatchRecord(
recordID: $0.recordID,
matchYear: $0[“matchYear"] as! Int,
teams: [])
for item in ($0["teams"] as! [CKRecord.Reference]) {
match.teams.append(TeamRecord(referenceID: item))
}
return match
}
And when displaying the list, the list rows are defined in a separate view, where the async properties are pulled in using a task. Along these lines
struct MatchRow: View {
...
#State var team0: String = ""
#State var team1: String = ""
var body: some View {
...
Text(verbatim: "\(match.matchYear): \(team0) - \(team2)")
.task{
guard team0.isEmpty else { return }
let (team0Result, team1Result) = await (match.teams[0].name, match.teams[1].name)
team0 = team0Result
team1 = team1Result
}
}
}
}
I am sure there is a more elegant solution for this. I especially do not like the idea of adding the task in the list row view as that splits the logic in many places...

Swift Vapor Filter with field type Array containing a value

I am trying to create a method where Players can fetch their messages in a game.
The message object has a property receivers which is an array of UUID containing Player IDs (in case messages are sent to multiple players).
How do I use a .filter operation to query items where an array contains an id?
Here is my Message Model
final class UserMessage:Model, Content {
static let schema:String = "messages"
#ID() var id: UUID?
// sender
#Field(key: "sender")
var sender: UUID
// receivers
#Field(key: "receivers")
var receivers: [UUID]
#Field(key: "title")
var title: String
#Field(key: "body")
var body: String
#Field(key: "date")
var date: Date
}
And here is the RouteCollection method.
func readAllMessages(req:Request) throws -> EventLoopFuture<[UserMessage]> {
// Needs player ID
guard let pidString = req.parameters.get("pid"),
let pid = UUID(uuidString: pidString) else {
throw Abort(.methodNotAllowed)
}
// FIXME: - Needs to return ONLY where \.receivers:[UUID] contains pid
return UserMessage.query(on: req.db).all().map { messages in
// Is this a too expensive operation?
var validMessages = messages.filter({ $0.receiver.contains(pid)})
return validMessages
}
}
I initially tried using the ~~ filter, as described in Vapor Docs here
But that only works in the opposite direction (Collection on the RHS).
I am also avoiding the creation of multiple relational tables, to reduce the amount of Content objects needed by the app. However, if that cannot be accomplished I am also open to a different suggestion/solution.

How can I refer to properties from a struct within a struct/

I'm trying to get the hang of how to make my code the most efficient using Structs / Classes, and I'm trying to understand it via the following example.
I'd be really grateful if someone could correct me or guide me about the same:
Just as an example, I'll use Harry Potter. There are four houses, and each house has certain characteristics.
So now I have a struct for 2 of them:
struct Gryffindor {
let name = "Gryffindor"
let characteristic = "Brave"
let image = Image("Lion")
}
struct Slytherin {
let name = "Slytherin"
let characteristic = "Cunning"
let image = Image("Snake")
}
Now if I wish to have a wizard struct as follows, but I don't know how to include a House property within, such that when I try to create an instance of a wizard, I can call the properties from their respective houses.
struct Wizard {
let name: String
var house: ?
}
let harryPotter = Wizard(name: "Harry", house: Gryffindor)
Basically, I wish to be able to refer to harry's house using the harryPotter instance, as such:
print(harryPotter.characteristic) //should print "Brave"
Is what I'm trying to achieve even possible?
First of all you are mixing types with objects so you should have a type House
struct House {
let name: String
let characteristic: String
let image: Image
}
And then use that in the Wizard struct
struct Wizard {
let name: String
var house: House
}
And now you create first a House object for the Wizard and then the Wizard object
let gryffindor = House(name: "Gryffindor", characteristic: "Brave", image: Image("Lion"))
let harryPotter = Wizard(name: "Harry", house: gryffindor)
or all in one call
let harryPotter = Wizard(name: "Harry",
house: House(name: "Gryffindor", characteristic: "Brave", image: Image("Lion")))
Use protocol & generics, like below. Tested with Xcode 11.4.
protocol House {
var name: String { get }
var characteristic: String { get }
var image: Image { get }
}
struct Gryffindor: House {
let name = "Gryffindor"
let characteristic = "Brave"
let image = Image("Lion")
}
struct Wizard<H: House> {
let name: String
var house: H
}
let harryPotter = Wizard(name: "Harry", house: Gryffindor())

Realm Swift Filter Query Based On List Property

I need some help with a realm swift query. Here is the business context:
There are two user types:
User Type 1: Show Manager
A show manager can create shows. Below you’ll find the necessary details of a Show object. As you can see every show has a property artistResponses which is a list of ArtistResponse.
class Show: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var createdDate = Date()
#objc dynamic var showManager: ShowManager!
#objc dynamic var venueName = ""
#objc dynamic var city = ""
...
**let artistResponses = List<ArtistResponse>()**
override static func primaryKey() -> String? {
return "id"
}
...
}
User Type 2: Artists
An Artist object has a citiesOfInterest property and artists on the app should only be shown shows created by show managers where the show city is in the artist’s citiesOfInterest. The artist can then view these shows and respond, which creates an ArtistResponse object and adds it to the list seen above in the Show object. Below you’ll find the necessary details of an Artist as well as an ArtistResponse object.
class Artist: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var createdDate = Date()
**let citiesOfInterest = List<String>()**
}
class ArtistResponse: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var createdDate = Date()
#objc dynamic var show: Show!
#objc dynamic var artist: Artist!
**#objc dynamic var available = true**
override static func primaryKey() -> String? {
return "id"
}
}
The query I’m having trouble with is related to determining if it’s a new show (i.e. the artist does not have an ArtistResponse associated to the show listing themselves as available or unavailable).
Here is an example:
A show manager named Bob creates a show where the city = “Austin”
An artist named Jim has citiesOfInterest = [“Austin”, “Seattle”, “San Francisco”]
Jim, the artist, comes onto the app and should see this new show listing posted by Bob, the show manager, because Jim’s citiesOfInterest contains “Austin” and the show’s city is equal to “Austin” and there is no artistResponse related to Jim.
I’ve researched and found that there are limitations with lists in Realm.
https://forum.realm.io/t/predicate-to-search-in-field-of-type-list/733
https://github.com/realm/realm-cocoa/issues/5334
Please help with this query:
Realm.objects(Show.self).filter(“somehow determine that there is not an artistResponse in the artistResponses list that is related to Jim”)
Can someone please help me with this? I hope I have provided enough explanation.
Let me restate the question;
You want an artist to be able to query for all new shows in cities
they are interested in where they have not yet responded.
If that's the question, let me start with some simple sample data
Two artists that have each have two cities if interest
let a0 = Artist()
a0.name = "Jim"
a0.citiesOfInterest.append(objectsIn: ["Austin", "Memphis"])
let a1 = Artist()
a1.name = "Henry"
a1.citiesOfInterest.append(objectsIn: ["Austin", "Memphis"])
and then two shows, one in each city
let s0 = Show()
s0.name = "Austin Show"
s0.city = "Austin"
let s1 = Show()
s1.name = "Memphis Show"
s1.city = "Memphis"
but artist a0 (Jim) has responded to the s0 (Austin) show
let r0 = ArtistResponse()
r0.show = s0
r0.artist = a0
r0.available = true
s0.artistResponses.append(r0) //jim responded to the Austin show
The result we want is that when Jim the artist logs on, he will see the Memphis show because he already responded to the Austin show.
let realm = try! Realm()
if let jim = realm.objects(Artist.self).filter("name == 'Jim'").first {
let myCities = jim.citiesOfInterest
let showResults = realm.objects(Show.self)
.filter("city IN %# AND !(ANY artistResponses.artist.name == 'Jim')", myCities)
for show in showResults {
print(show.name)
}
}
And the output is
Memphis Show
We first get the Jim's cities of interest as an array, then we filter the shows that match those cities of interest but where there are no artist responses associated with Jim. This filter was based on the name but you could use the Artist id or even the Artist object itself as the comparator.

Filtering Realm objects with Swift

I always get the following error when trying to filter my Realm database using NSPredicate:
Property 'text' is not a link in object of type 'getType'
I want to filter my Realm database to show only the items that have some specific text in them. This is what I've tried:
let realm = try! Realm()
let predicate = NSPredicate(format: "typez.text.filter = 'special'")
let filterThis = realm.objects(Publication).filter(predicate)
print(filterThis)
The relevant portion of my model classes is:
class Publication: Object, Mappable {
dynamic var id: Int = 0
var typez = List<getType>()
dynamic var url: String?
}
class getType: Object, Mappable {
dynamic var text: String = ""
}
You mentioned that the relevant portions of you model classes look like so:
class Publication: Object, Mappable {
dynamic var id: Int = 0
var typez = List<getType>()
dynamic var url: String?
}
class getType: Object, Mappable {
dynamic var text: String = ""
}
If I understand you correctly, you want to find Publication instances that have an entry in their typez list with text equal to special. You can express that as:
let realm = try! Realm()
let result = realm.objects(Publication).filter("ANY typez.text = 'special'")
print(result)
I was not liking the accepted answer here because it doesn't actually answer the question... but then it helped me more than I realized. I will now be using closures instead of NSPredicates whenever possible. The actual answer to this question should be a slightly modified version of #NSGangster's answer:
let realm = try! Realm()
//Array of publications
let realmObjects = realm.objects(Publication)
//any publication where .text property == special will be filtered. and filter out empty array
let filterThis = realmObjects.filter({ $0.typez.filter({ $0.text == "special" } != [] ) })
print(filterThis)
.. or something close to that.
But what I was looking for was a bit different. I needed a way to filter on exact words of a multi-word string, and using an NSPredicate with "CONTAINS" would match any containing substring, e.g. a search for "red" would match "fred". Realm doesn't support "LIKE" or regex yet, so using a closure was the only thing I could get to work:
//I was going for a "related terms" result for a dictionary app
let theResults = terms.filter(
{
//Looking for other terms in my collection that contained the
//title of the current term in their definition or more_info strings
$0.definition.components(separatedBy: " ").contains(term.title) ||
$0.more_info.components(separatedBy: " ").contains(term.title)
}
)
With as much of the day as I spent searching, hopefully this helps someone else with a similar issue.
I don't usually use NSPredicate's directly, instead I do an inline predicate closure within the filter paramter.
let realm = try! Realm()
//Array of publications
let realmObjects = realm.objects(Publication)
//any publication where .text property == special will be filtered. and filter out empty array
let filterThis = realmObjects.filter({ $0.getType.filter({ $0.text == "special" } != [] ) })
print(filterThis)