What is the proper code for Realm inverse connection? - swift

Realm demo database has sample objects with inverse connection: objectReference has a link to RealmTestClass1.
class RealmTestClass1: Object {
dynamic var integerValue = 0
let arrayReference = List<RealmTestClass0>()
}
class RealmTestClass2: Object {
dynamic var boolValue = false
dynamic var objectReference: RealmTestClass1?
}
How to re-create that same structure? What code is needed for objectReference property to create that same link to parent object property?

Links in Realm are unidirectional. So if a to-many property Person.dogs links to a Dog instance and a to-one property Dog.owner links to Person, these links are independent from one another. Appending a Dog to a Person instance’s dogs property, doesn’t automatically set the dog’s owner property to this Person. Because manually synchronizing pairs of relationships is error prone, complex and duplicates information, Realm exposes an API to retrieve backlinks described below.
With inverse relationships, you can obtain all objects linking to a given object through a specific property. For example, calling Object.linkingObjects(_:forProperty:) on a Dog instance will return all objects of the specified class linking to the calling instance with the specified property. It’s possible to simplify this pattern by defining a read-only (computed) owners property on Dog:
class Person: Object {
// ... other property declarations
let dogs = List<Dog>()
}
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
var owners: [Person] {
// Realm doesn't persist this property because it only has a getter defined
// Define "owners" as the inverse relationship to Person.dogs
return linkingObjects(Person.self, forProperty: "dogs")
}
}
This was taken from Realm's docs on relationships, which I encourage you to read.

Related

Swift Realm - one to many filter query

class Dog: Object {
#objc dynamic var name = ""
let owners = List<Owner>()
}
Here, dogs can belong to more than one owner. So, say I have a particular Owner object. How can I set up a query to get all dogs that have that owner in their owners list?
func allDogsFor(owner: Owner) -> [Dog]? {
let query = realm.objects(Owner.self).filter???
}
You're looking for inverse relationship. The way it works (from docs):
With linking objects properties, you can obtain all objects that link
to a given object from a specific property. A Dog object can have a
property named owners that contains all of the Person objects that
have this exact Dog object in their dogs property. Make the owners
property of type LinkingObjects and then specifying the relationship
that it has with the Person object.
where Person it's your Owner.
I recommend you rewrite little bit your code, because it makes more sense when the owner has dogs. Basic example could be:
final class Owner: Object {
let dogs = List<Dog>()
}
final class Dog: Object {
let owners = LinkingObjects(fromType: Owner.self, property: "dogs")
}
let dogsForOwner = owner.dogs
let ownersOfADog = dog.owners
This is an answer to your specific question.
Using the same Dog class you have shown in the question, you are asking how to query the Dogs' owners property for a specific owner and you want to return all of the dogs that have that owner.
There are a couple of ways to go about it so here's one way:
Suppose you have an owner with the last_name of Smith - first we want to get that specific owner object and then query all of the Dog Lists for it
let ownerResults = realm.objects(OwnerClass.self).filter("last_name == 'Smith'")
if let smithOwner = ownerResults.first {
let dogResults = realm.objects(DogClass.self).filter("ANY owners == %#", smithOwner)
for dog in dogResults {
print(dog.dog_name)
}
}
And then a second option (as mentioned in another answer) is to use LinkingObjects. Linking objects 'automatically' create a link (inverse relationship) back to another object.
So take a look at these classes
class OwnerClass: Object {
let dogList = List<DogClass>()
}
and
class DogClass: Object {
let owners = LinkingObjects(fromType: OwnerClass.self, property: "dogList")
}
the cool thing here is that when we create a dog class and add it to the owner
let owner = OwnerClass()
owner.dogList.append(aDog)
then write it to realm
let realm = try Realm()
try! realm.write {
realm.add(owner)
}
that dog is also added to realm and the link established.
To revisit the question;
say I have a particular Owner object. How can I set up a query to get
all dogs that have that owner in their owners list
To re-state that
I want to know all the dogs that are in a particular owners dogList
In this scenario, you don't need a query. An owner has dogs any dogs added to an owner are part of that owners dogsList
let ownerResults = realm.objects(PersonClass.self).filter("last_name == 'Smith'")
if let smithOwner = ownerResults.first {
let smithsDogs = smithOwner.dogs //no query! this is all
// of the dogs that have smith as an owner
for dog in smithsDogs {
print(dog.dog_name)
}
}
In fact, we don't even need a LinkingObjects property in each dog since if we add those dogs to an owner
owner.dogList.append(objectsIn: [dog0, dog1, dog2])
it's pretty clear there are three dogs in that owners dogList. The LinkingObjects however, will allow us to traverse back to the owner from each dog.

Querying for a matching array property in Realm

Consider the following, using Realm Swift:
class Shelf : Object {
let products = List<Product>()
}
and
class Product : Object {
let string: String = ""
let Shelves = LinkingObjects(fromType: Shelf.self, property: "products")
}
Now the question is, is it possible to perform a query like:
"What are all the shelves that match EXACTLY a specific product list?" (no more, no less).
I could not find a way to compare an array.
EDIT:
If I try doing this:
let results = realm.objects(Shelf.self).filter("%# == products",products)
I get the error:
Invalid predicate:
Key paths that include an array property must use aggregate operations
Thank you.
Each Realm model class inheriting from Object conforms to the Equatable protocol by default due to the fact that Object inherits from NSObject, which conforms to Equatable. If the elements of a List conform to the Equatable protocol, the List itself conforms to it as well.
Hence, you should be able to compare List<Product> instances by using the == function and you can also directly compare Shelf objects.
However, be aware that Realm overrides the == function, see Realm object's Equatable is implementation.
If you want a custom comparison, you can override the == function yourself.
Edit: I don't think this can be done using Realms filter method. However, if you don't mind getting an Array back as a result and not a Result collection, the below method can help you.
let searchedProducts = List([product1, product2, product3]) //these are Product instances
let results = Array(realm.objects(Shelf.self)).filter{
if $0.products.count != searchedProducts.count {
return false
}
for product in $0.products {
if searchedProducts.index(of: product) == nil {
return false
}
}
return true
}
The above code has been tested in Playground with a similar class and returned correct results.

Storing an array of custom objects in Realm

I have an object called List which subclasses Realm's Object class:
class List: Object {
dynamic var brandListItems: [BrandListItem] = []
}
and another object, BrandListItem which also subclasses to Object:
class BrandListItem: Object {
dynamic var brandID: String?
dynamic var name: String?
}
My app is crashing with the following error
'Property 'brandListItems' is declared as 'NSArray', which is not a supported RLMObject property type. All properties must be primitives, NSString, NSDate, NSData, NSNumber, RLMArray, RLMLinkingObjects, or subclasses of RLMObject.
I tried doing something like RLMArray<BrandListItem>() with no luck. How do I successfully save these objects to Realm?
You need to use Realm's List<T> property. Note that it's not marked dynamic
https://realm.io/docs/swift/latest/#to-many-relationships
class List: Object {
let brandListItems = RealmSwift.List<BrandListItem>()
}
Note that it's necessary to qualify Realm Swift's List<T> with its module to disambiguate it from your newly-declared List class.

How should we initialize stored properties in Structs and Classes?

Apple's documentation states:
Classes and structures must set all of their stored properties to an
appropriate initial value by the time an instance of that class or
structure is created. Stored properties cannot be left in an
indeterminate state.
I have also seen this question, but still wasn't able to find an answer.
// struct
struct FlickrObj { // No Error ????
var title : String
var photographer : String
}
// class
class FlickrObj { // will create error: Class 'FlickrObj' has no initializers
var title : String
var photographer : String
}
Apple says both classes and structs must set their stored properties but why doesn't the Struct give any compile time errors?‌
It's because, in the absence of any explicitly declared initializers, the struct itself declares the implicit memberwise initializer init(title:photographer:).
But classes don't do that.
So the class is failing to fulfill the initialization contract (all instance properties must be initialized before the instance itself finishes initializing), but the struct is not failing the contract.

Implementing a class structure for one-to-many relationship. Swift

Basically I am trying to implement a class for a Person and Person's pets. In it's very simple form Person class and Pet class would be like this:
class Person {
var email : String!
var name : String!
var pets : [Pet]!
init(userName: String) {
email = userName
// load Person from CloudKit database
}
}
class Pet {
var name : String!
var breed : String?
}
Now I want to be able to load all Pets related to the specific userName. If I have the above the class, that only holds one Pet. If I do an array like in the Person class, there is no Load method. The question is, should I create a new class Pets like below:
class Pets {
var userName : String
var pets : [Pet]
init(email: String) {
// load all pets related to user here
}
}
Does this make sense? Or is there a better way to structure the class? Is my question clear enough? Thanks for any help in advance.
I'd be using structs not classes for my data model. Structs and Classes
I wouldn't use optionals unless I really had to, and there are a few use cases where you have to. I also wouldn't be using implicitly unwrapped optionals at all... i.e. ! It's an 'exclamation' for a reason. You should read up on when to use the different types of optionals.Why create implicitly unwrapped optionals
I wouldn't have a Pets class like you created. All this would be stored in your database to be queried ... possibly core data, or if you had some other class to hold the data it would be simply a dictionary/map of [String: [Pet]] and you could look up all the pets for a username from that dictionary/map