LinkingObjects questions and clarifications

I have a few questions/clarifications about LinkingObjects:

  1. The sdk ref docs for LinkingObjects states:

LinkingObjects can only be used as a property on Object models. Properties of this type must be declared as let and cannot be dynamic.

But when used with @Persisted it appears it must be defined as var. There are enough examples of this that I’m sure var must be the right way when using @Persisted, but it was a point of confusion in the docs that I wanted to doublecheck.

  1. Does originProperty for LinkingObjects support a key path to an embedded object, or must it be a top level property on the related object?

Example:

class User: Object {
	@Persisted(originProperty: "users.owner")
	var ownedItems: LinkingObjects<Items>
}

class Items: Object {
	@Persisted var users: Users
	
	class Users: EmbeddedObject {
		@Persisted var owner: User?
	}
}
  1. For modeling a one-to-many relationship, should it be preferred to use MutableSet vs LinkingObjects?

For example:

class ModelA: Object {
	@Persisted(originProperty: "toOne")
	var toMany: LinkingObjects<ModelB>
}

class ModelB: Object {
	@Persisted var toOne: ModelA?
}

as opposed to

class ModelA: Object {
	@Persisted var toMany: MutableSet<ModelB>
}

Thanks for the assistance!

2 Likes

yes! That paragraph is really tied back to @ObjC and legacy documentation. Var is the correct selection. If a PersonClass has a List of Dogs and you want to transverse the graph back to the Person From Dog:

@Persisted(originProperty: "dogList") var linkedPersons: LinkingObjects<Person>

I understand the question but that’s not the correct implementation. Let me explain.

In your code example, Users is an Embedded object in Items, but it’s not an Embedded Object to Realm. All Realm objects - all - must be declared at the top level of the app, not inside another class. Including EmbeddedObjects

Also, linking objects would not be used on an EmbeddedObject - as there is no reason to do that. Embedded objects are child objects of a specific parent and are not independently persisted - meaning to get to an embedded object, you need to go through the graph of the parent to the child. So if you want a specific child you would have to know the parent object.

Perhaps you can can clarify that part of the question a bit if my explanation doesn’t answer it.

You would not generally use LinkingObjects in a one-to-many relationship in that capacity. Let me dive a bit into a LinkingObject use case for clarity.

Relationships in databases can be Forward and Reverse and 1-1, 1-many, many-many. Forward takes you from a parent to a child object(s) and reverse transverses the graph from the child object back to the parent object.

A forward relationship would be done with a List (one example)

class Person: Object {
    @Persisted var dogList = RealmSwift.List<Dog>()
}

class Dog: Object {
   @Persisted var dogName = "spot"
}

In the above example we have a forward one to many relationship from Person to their Dogs. But: What if you find a lost dog and want to know it’s owner? While that could be achieved with a query, a reverse relationship takes you from the Dog right to the Person (

class Dog: Object {
   @Persisted var dogName = "spot"
   @Persisted(originProperty: "dogList") var linkedPersons: LinkingObjects<Person>
}

So List is Forward; Parent → Child and LinkingObjects is Reverse; Parent ← Child.

There are interesting things about LinkingObjects:

  1. LinkingObjects is “computed” - e.g. the relationship between the Child and Parent is computed when you ask for it - there’s nothing stored on Disk that defines that relationship. Very unlike a List where you can actually see (in the Realm Browser) the dogs in a Persons dogList.

  2. LinkingObjects is actually a reverse many to many relationship! It can actually point back to multiple parents. That’s incredibly powerful - suppose there’s a married couple that both have ownership of a dog - so that dog appears in both of their dogList properites. Well, the dogs linkedPersons will contains BOTH of those owners. If you are only every going to have one owner, you can simply use linkedPersons.first to get the one person

  3. LinkedObjects relationships self-destruct. e.g. If a Dog is removed from a Persons dogList, the reverse relationship goes away as well (this is why I call it computed).

*note the above is kinda at the 10,000’ level

Whew - hope that helps

Thanks for this clarification! I noticed this for Object, but I was hoping I could get away with it for modeling EmbeddedObject.

I do have some more clarifications on #2 and #3. Here’s a more real-life use case that I hope will help explain both.

I have an Attachment class that I want to persist as separate objects (not embedded for various reasons). These Attachments will be used with multiple other classes, but each Attachment will only be associated with one other object. Because these Attachments will be used across classes, I’d like to keep a reference to the “owning” object to easily understand the origin when viewing a list of attachments, and even though there will only be one related object, I’d prefer to have fully typed relationships.

Those considerations led me to the following, with the relationships persisted on Attachment, and LinkingObjects providing convenient access to the list of attachments for each of the other objects:

class Attachment: Object {
	// To-one relationships to all the different object types
	@Persisted var item: Item?
	@Persisted var list: List?
	@Persisted var person: Person?
	@Persisted var place: Place?
	// ...and there's a few more...
}

class Person: Object {
	@Persisted(originProperty: "person") var attachments: LinkingObjects<Attachment>
}

This use case is somewhat unusual, but it seems like a reasonable way to link the objects. Please let me know if there are any pitfalls I haven’t noticed!

Then I was curious if I could organize the structure a bit more to hide the clutter of the myriad relationships, so I wondered if they could be isolated in an embedded object, and then if LinkingObjects would be able to find populate itself if originProperty was a multi-component keypath, person vs ref.person in this example.

class Attachment: Object {
	// All relationships have been moved into `AttachmentRef` 
	@Persisted var ref: AttachmentRef
}

class AttachmentRef: EmbeddedObject {
	// To-one relationships to all the different object types
	@Persisted var item: Item?
	@Persisted var list: List?
	@Persisted var person: Person?
	@Persisted var place: Place?
	// ...and there's a few more...
}

class Person: Object {
	@Persisted(originProperty: "ref.person") var attachments: LinkingObjects<Attachment>
	//                         Is ^this^ valid?
}

Does that explain what I’m trying to do?

Thanks again for the detailed assistance!

Additional explanation was perfect.

Sure! You can do that. I am not aware of any pitfalls but the concept is the same - when a person is added to an attachment (forward) it will create a (computed) reverse link from the person back to the attachment.

In a nutshell, the parent object has an embedded object, and that embedded object has a forward to a Person object, which has a reverse relationship to the embedded object.

Really had to stretch my brain on that one but I don’t think that’s valid. The linking objects var is saying “Hey, link back to an Attachment object” and look for a property ref.person, but the Attachment object doesn’t have that property. e.g. ref.person resolves to a Person object, not an Attachment object.

But…

You could link back to the embedded object directly.

class AttachmentRef: EmbeddedObject {
   @Persisted var person: Person?
}

class Person: Object {
   @Persisted(originProperty: "person") var attachments: LinkingObjects<AttachmentRef>
}

the trouble there is the embedded object AttachmentRef has no idea who it’s parent is so if you need to go from Person to AttachmentRef to the Attachment, that won’t work.

To fix, add a property to AttachementRef to refer to it’s parent Attachment.

class AttachmentRef: EmbeddedObject {
   @Persisted var person: Person?
   @Persisted var parentAttachment: Attachment!
}

Then you could get data in both directions - going in reverse:

let results = realm.objects(Person.self)
for person in results {
    print(person.name, person.attachments.first!.parentAttachment.some_attachement_property)
}

and +1 for a super great question!

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.