Realm: Update/Set relationship without updating existing child object - ios

Given the following data model, is there a way to update or set a child relationship without updating the (existing) child object?
Data Model
class Person: Object {
dynamic var id = 0
dynamic var dog: Dog?
override static func primaryKey() -> String? {
return "id"
}
}
class Dog: Object {
dynamic var id = 0
dynamic var name = ""
override static func primaryKey() -> String? {
return "id"
}
}
Code
let realm = Realm()
// query existing dog
let existingDog = realm.objectForPrimaryKey(Dog.self, key: 42)
print(existingDog) // id: 42, name: Rex
// create new dog and person
let newDog = Dog(value: ["id": 42, "name": "Pluto"])
let person = Person()
realm.write {
// this shouldn't update the existing dog
// i.e. the name of the dog with id: 42 in the database should still be "Rex"
person.dog = newDog
realm.add(person, update: true)
}
I've also tried to do this via realm.create but had no success either. Any ideas?

The id property in your Dog class is a primaryKey. Primary keys have the purpose to mark an entry in a Database as unique. So if you create an Object using the same primary key, you will override any existing data in the Database, which has the primary key.
I don't understand, what you exactly want to achieve, but there should be no reason for your Dog Table to have multiple Objects with the same primary key. That's not how Relational Databases are meant to work.
If you need however to have a Dog named Pluto and a Dog named Rex with the same id value, then should probably create a new attribute which is not unique (-> not marked as primary key).

Related

How Query and Count entries in a Realm Database

I'd like to build a search on which the user can filter down the results step-by-step. So with no choice set, there is a button which says e.g. "1,234,567 Results" and if you choose a color for example the results set shrinks... we all know this kind of search. I did build it many times, but this is the first time in Realm (and swift).
Lets Say I have 5 Persons in my Person Table, then there are about 145,224 Dog entries per Person and about 2,507,327 Cat entries per Dog. How do I query and Count nested Objects in Realm?
class Person: Object {
#objc dynamic var name = ""
let dogs = List<Dog>()
// ...other Properties
}
extension Person {
static func all(in realm: Realm = try! Realm()) -> Results<Person> {
return realm.objects(Person.self)
}
}
// counts -> 145,224 db entries per person
class Dog: Object {
#objc dynamic var name = ""
dynamic var Person: Person?
let cats = List<Cats>()
// ...other Properties as well
}
extension Dog {
static func all(in realm: Realm = try! Realm()) -> Results<Dog> {
return realm.objects(Dog.self)
}
}
// counts -> 2,507,327 db entries per dogs
class Cat: Object {
#objc dynamic var name = ""
dynamic var Cat: Cat?
}
extension Cat {
static func all(in realm: Realm = try! Realm()) -> Results<Cat> {
return realm.objects(Cat.self)
}
}
// Get the default Realm
let realm = try! Realm()
// Query Realm for all dogs
let dogs = Person.all(in: realm).flatMap { $0.dogs }
dogs.count // => takes ~20 seconds to count
In other words, what is the fastest way to get (count) all Dog entries of all Persons (let the cats by side for now).
I tried to workaround the problem by limit the results to 1000. If the results are >1000, then label the button like so "> 1000 Results". But even than it takes very long (I guess the get all count anyway).
So what did I do wrong?
They way you were computing the count required all Dog objects to be loaded into memory which is really inefficient. This is why you were seeing such poor performance. You want to take advantage of Realm's lazy loading features. You may want to read up on that.
I would update your Dog object by getting rid of your managed Person property and replace it with LinkingObjects. If you store a Dog in a Person.dogs List, then realm will create a back link to the Person for you. You will likely want to do the same thing with Cat. That way you can set up some really powerful nested queries.
For convenience you can add a computed Person property to index into the LinkingObjects. Just know that you won't be able to use that property in any of your queries.
class Dog: Object {
#objc dynamic var name = ""
let persons = LinkingObjects(fromType: Person.self, property: "dogs")
var person: Person? { persons.first }
}
One way to compute the count is to query of all Person objects and sum the count of dogs for each Person.
let count = realm.objects(Person.self).reduce(0) { result, person in
return result + person.dogs.count
}
Another options is to query for Dog objects that have a corresponding Person. If a Dog is not in a Person.dogs List, then it won't show up the query.
let realm = try! Realm()
let count = realm.objects(Dog.self).filter("persons.#count > 0").count
Either option is going to be much, much more efficient than what you had.
Hope this helps.

Ambiguous reference to member 'create(_:value:update:)'

I'm using RealmSwift in my project.
When trying to update one of the parameters of an existing object, I get the following error:
Ambiguous reference to member 'create(_:value:update:)'
And here is the code I'm working on:
let newProduct = ShoppingBagObject(value: [product.id, product.name!, product.price!, product.oldPrice!, product.weight!, count])
try? realm?.write {
realm?.create(newProduct, value: ["count": 3], update: .modified)
}
I want to update the 'count' parameter only.
And here is the reference from realm.io official documentation:
try! realm.write {
realm.create(Book.self, value: ["id": 1, "price": 9000.0], update: .modified)
// the book's `title` property will remain unchanged.
}
EDIT:
Here is my model class:
class ShoppingBagObject: Object {
#objc dynamic var id = 0
#objc dynamic var name = ""
#objc dynamic var price = 0
#objc dynamic var oldPrice = 0
#objc dynamic var weight = 0
#objc dynamic var count = 1
override static func primaryKey() -> String? {
return "id"
}
}
What am I doing wrong ?
Can you try
try? realm?.write {
realm?.create(ShoppingBagObject.self, value: ["id":product.id, "count":3], update: .modified)
}
From the docs:
If a Book object with a primary key value of ‘1’ already exists in the
database, then that object will simply be updated. If it does not
exist, then a completely new Book object will be created and added to
the database.
You can also partially update objects with primary keys by passing
just a subset of the values you wish to update, along with the primary
key

Attempting to create an object of type 'Item' with an existing primary key value '1'

I have three classes, Folder, Object & Item.
For every Folder there are a list of objects, and for every Object there should be a list of items. However when creating an Object with a default list of items I am getting the following error;
*** Terminating app due to uncaught exception 'RLMException', reason: 'Attempting to create an object of type 'Item' with an existing
primary key value '1'.'
For each class I have a function that counts all the objects and increments the ID, this works fine but there must be something wrong with how I am adding Item to an Object.items list property.
Item
class Item: Object {
#objc dynamic var id: Int = 0
#objc dynamic var object: Object?
#objc dynamic var title: String = ""
override static func primaryKey() -> String? {
return "id"
}
func IncrementaID() -> Int{
let realm = try! Realm()
var num = 1
let allItems = realm.objects(Milestone.self)
if allItems.count >= 1 {
num = allItems.count + 1
}
return num
}
}
When creating an Object I am also trying creates Item's to append to the objects items property like so
func addObjectToFolder(_ title: String, folder: Folder) {
let object = Object()
object = object.IncrementaID()
object.title = title
let defaultList = ["1", "2", "3", "4", "5"]
for i in defaultList {
let item = Item()
item.id = item.IncrementaID()
item.title = i
object.items.append(item)
}
try! realm.write {
folder.objects.append(object)
}
}
Does an body know how I am going wrong when creating the default values for the Objects.items list ?
The value returned by your IncrementaID function only changes when the number of Milestone objects stored in your Realm changes. Your code contains a loop that creates multiple Item instances and initialized their id property to the value returned by IncrementaID. No Milestone objects are created or stored in the Realm between calls, so all of the Item objects end up with the same ID.
My suggestion: use the string representation of a UUID for your primary key, rather than trying to manage unique integers like this.

Proper way to query objects with Inverse relationships in Realm

I'm currently having trouble querying data that I stored in an Inverse relationship. I have the following code
class Form: Object {
dynamic var id: String = NSUUID().uuidString
...
var answers = List<FormAnswer>()
override static func primaryKey() -> String? {
return "id"
}
...
}
and
class FormAnswer: Object {
dynamic var key = ""
dynamic var answer = ""
let form = LinkingObjects(fromType: Form.self, property: "answers")
override static func primaryKey() -> String? {
return "key"
}
}
When I create a FormAnswer object I do the following:
try! realm.write {
let answer = FormAnswer(value: ["key": key, "answer": answer, "form" : parentForm!]) // parentForm is of type "form"
realm.add(answer, update: true)
}
And when I try to query it, I get nothing!
let previousValue = realm.objects(FormAnswer.self).filter("key == %# AND ANY form.id == %#", key, parentForm!.id).first?.answer
I've checked the realm file with Realm Browser, and there's an entry for FormAnswer. But there are only 2 fields (key and answer) and there doesn't appear to be a link to my Form object.
Does anyone have any ideas on how I can fix this?
Thanks
LinkingObjects is a computed property and cannot be mutated directly. Instead you modify its values by changing the other side of the relationship.
Try:
try! realm.write {
parentForm.answers.add(FormAnswer(value: ["key": key, "answer": answer]))
}
This adds the new answer directly to the Form's answer list, and will result in the answer's form property containing parentForm.

Realm Swift iOS - Can't Set Primary Key

I'll try to explain my scenario as short as possible, I have read some comments on Realm GitHub Repo about this issue:
Terminating app due to uncaught exception 'RLMException', reason:
'Can't set primary key property 'id' to existing value 'xxxxxxx'.
Here's my issue:
I got two classes.
Appointment Model Class
import Foundation
import RealmSwift
class Appointment: Object {
dynamic var id = 0
dynamic var user_id: String?
dynamic var profile_id: String?
let mainMeeting = List<Meeting>()
let meetingsWithOtherInfo = List<Meeting>()
override static func primaryKey() -> String? {
return "id"
}
}
Meeting Model Class
import Foundation
import RealmSwift
class Meeting: Object {
dynamic var id = 0
dynamic var name: String?
dynamic var created_at: String?
// other info
dynamic var restaurant_venue: String?
override static func primaryKey() -> String? {
return "id"
}
}
I am fetching the Appointments from my server API like this
for fetchedAppointment in allAppointmentsFromAlamofire {
let existingAppointment: Results<(Appointment)>! = realm.objects(Appointment).filter("id = \(fetchedAppointment["id"]!)")
let newAppointment: Appointment = Appointment()
newAppointment.id = fetchedAppointment["id"]! as! Int
....
// add data to Meeting connected to Appointment
let newMeeting = Meeting()
newMeeting.id = fetchedAppointment["meetings"]["id"]! as! Int
...
// update or add new entry
try! realm.write {
print("NEW APPOINTMENT: \(newAppointment)")
realm.add(newAppointment, update: existingAppointment.count == 0 ? false : true)
}
}
The error comes out whenever the program is trying to update existing entry in realm - whenever the existingAppointment is 1. the workaround here, from what I've read from Github Realm is to delete the override static func primaryKey() in Meeting Class.
There is no issue if I am just adding new entries to Appointment, but again, the issue comes out if I will be updating, and the issue goes away if I remove the primaryKey() in Meeting Class ---- BUT, in other screens of my app, I really need to have that primaryKey() in Meeting Class.
My wild guess here is that every time that I need to update entries in Appointment, I should update too the Meeting.
So, the question is: why is this happening? Is my wild guess correct? Any other way to solve this?
It looks like you are trying to update the newAppointment object with a value that is not its primary key.
realm.add(newAppointment, update: existingAppointment.count == 0 ? false : true)
Instead, Realm is expecting you to provide the key for that object so that it can update the specified object.
It looks like you are setting the key value here, which is what you should use for your update.
let newAppointment: Appointment = Appointment()
newAppointment.id = fetchedAppointment["id"]! as! Int
Realm documentation on updating with keys.
You don't need to set the update argument of add(:_, update: _) to false if you provide a new object. If your model has a primary key and you want to create or update, you can pass true and Realm will automatically figure out whether an object with the same primary key is already managed in the database or not.

Resources