Update Realm relationship with primary ids - ios

Is it possible to update a Realm object's relationships by passing only the ids of the relationships?
If I have a Library class that has a List<Books> relationship and I wanted to merge two Libraries together, I would presume that could be done like:
let bookIds = (firstLibrary.books.toArray() + secondLibrary.books.toArray).map { $0.id }
Then I use ObjectMapper & SugarRecord:
let changes = ["books": bookIds]
Mapper<T>().map(changes, toObject: secondLibrary)
let realm = self.realm
realm.add(secondLibrary, update: true)
But the list of books doesn't get updated.
I assume this is because ObjectMapper doesn't know anything about primary ids and therefore trying to map them into an object doesn't do anything.
Does Realm have the capability to update via primary id? If it does, I'd gladly rewrite my persistence stack.

For future reference: one workaround, which was discussed on this Github issue, is to modify the object creation code in the example as such:
let books = ["1", "2"].map { realm.objectForPrimaryKey(Book.self, key: $0) }
realm.create(Library.self, values: ["id": "4321", "books": books], update: true)
It's not possible to directly pass an list of primary key values as part of the value passed into the create() API to create a Library, and have Realm automatically create or update the Book objects associated with those primary keys.
Instead, this workaround first retrieves or creates the Book objects for each primary key value using objectForPrimaryKey(), and then creates the Library using those Book objects using create().

Related

Append element to Firebase Array

How I could append an element to an array like that:
Using this code I'm overriding the old data:
let toUpdate = [book.id]
self.refUsers.child(localUser.key!).child("booksPurchased").setValue(toUpdate, withCompletionBlock: { (error, _) in
You could use this method: firebase.firestore.FieldValue.arrayUnion()
Example with angularfire2:
this.afs.collection('collection').doc(id).update( {
array: firebase.firestore.FieldValue.arrayUnion( 'newItem' )
});
For more information: https://firebase.google.com/docs/reference/js/firebase.firestore.FieldValue#arrayunion
In this case, you will have to read the existing data, then write it back with the new value added. Arrays like this are not always the best way to store lists of data if you want to perform a lot of append operations. For that, you're better off pushing data into a location using childByAutoId.
Reading and writing lists
Append to a list of data
Use the childByAutoId method to append data to a list in multiuser applications. The childByAutoId method generates a unique key every time a new child is added to the specified Firebase reference. By using these auto-generated keys for each new element in the list, several clients can add children to the same location at the same time without write conflicts. The unique key generated by childByAutoId is based on a timestamp, so list items are automatically ordered chronologically.
You can use the reference to the new data returned by the childByAutoId method to get the value of the child's auto-generated key or set data for the child. Calling getKey on a childByAutoId reference returns the auto-generated key.
You can use these auto-generated keys to simplify flattening your data structure. For more information, see the data fan-out example.
-https://firebase.google.com/docs/database/ios/lists-of-data
You could set the values of the keys in the array to true, and then set the value directly in an update.
So if 'newId' is the new item to add, maybe something like:
const update = {
[`/users/${localUser.key}/booksPurchased/${newId}`]: true]
}
firebase.db.ref().update(update);
Firebase docs example of an update:
https://firebase.google.com/docs/database/web/read-and-write

best way to rename a class in Realm swift

I use a "common" library in my iOS project. This library creates a Realm database. So far, I've been using this library on only iOS projects. I want to now use that same library with a macOS project. It's Foundation based, and doesn't use UIKit, so why not?
Here's the problem: I have a Realm class named Collection
Collection is also the name of a standard Swift protocol.
While I've been able to get away with this name collision on my iOS project, for some reason, I can't do the same on my MacOS project -- it creates a name-collection.
I read about this notation that can be used like this:
#objc(SpecialCollection)
class Collection: Realm.Object {
let items: List<ItemObject>
let name: String
let url: String
....
}
So, this solves the name-collision problem. In ObjC, the name will be something different, but in Swift, I don't need to change anything.
This is all well and good except for my local Realm database. I have a lot of Collection objects that should be renamed to SpecialCollection (since Realm uses ObjC underneath Swift). I'd like to perform a migration to do this, but apparently there isn't a supported way to do this yet? I noticed tickets on github about this issue being "watched", but unfortunately, there still exists no published solution to fix this problem.
All of my Collection objects contain List objects (hence the name). So, I tried to run an enumeration on all of the Collection objects in a migration... I would just take the older object, and create a new object with the new name, like this:
migration.enumerateObjects(ofType: "Collection", { (oldObject, _) in
migration.create("SpecialCollection", value: oldObject)
}
But since oldObject has a list of other objects, Realm's migration will try and create all the items in any List objects... which can't be done, because it creates objects with the same primaryKey value (causing a crash).
So, I can't keep the old name (Collection), and I can't convert to the new name, and I can't just trash the user's data. So, I'm truly at an impasse.
Blockquote
I tried to modify oldObject before creating the new object, but you can't change oldObject in a migration.
The only rule is that the old data has to be preserved, I can't just destroy the user's realm here.
Thanks for any help in this. It is greatly appreciated.
I had a very similar problem last night. I had a couple Realm classes I wanted to rename, and where one of them had a List property referring to the second class. So the only difference compared to your problem is I was renaming ItemObject class as well.
So here's how I did it:
Migrate your Collection class first, creating SpecialCollection.
While migrating, walk the Collection's list and create new
SpecialItemObject for each ItemObject and append it to the new list.
Delete each ItemObject.
Now enumerate all ItemObject remaining
in the realm and create a new SpecialItemObject and map its values
over. The reason is there may be other ItemObject floating around
in your realm, not tied to the list.
Delete all remaining ItemObject.
migration.enumerateObjects(ofType: "Collection")
{ (oldObject, newObject) in
let specialCollection = migration.create(SpecialCollection.className())
specialCollection["name"] = oldObject!["name"]
specialCollection["url"] = oldObject!["url"]
if let oldItems = oldObject!["items"] as? List<MigrationObject>,
let newItems = specialCollection["items"] as? List<MigrationObject>
{
for oldItem in oldItems
{
let newItem = migration.create(SpecialItemObject.className())
newItem["name"] = oldItem["name"] // You didn't specify what was in your ItemObject so this example just assumes a name property.
newItems.append(newItem)
migration.delete(oldItem)
}
}
}
migration.deleteData(forType: "Collection")
// Now migrate any remaining ItemObject objects that were not part of a Collection.
migration.enumerateObjects(ofType: "ItemObject")
{ (oldObject, newObject) in
let newItem = migration.create(SpecialItemObject.className())
newItem["name"] = oldItem["name"]
}
// Now let's be sure we'll have no further ItemObject in our entire Realm.
migration.deleteData(forType: "ItemObject")
So this is how I solved it for myself last night, after finding next to nothing about most of this in cocoa-realm in GitHub or on SO or elsewhere. The above example only differs from what you asked in that you weren't asking to rename your ItemObject class. You could try just creating new ItemObject objects and mapping the properties across in the same way I show in my example. I don't see why it wouldn't work. I've provided my example exactly how I solved my own problem, since I tested some migrations last night to prove it was solid.
Since your question is almost 5 months old, I'm really just posting this answer for posterity. Hope this helps someone!
Tested with Realm 3.3.2 on iOS 11.3 sim / Xcode 9.3 / Swift 4.1

Best way to store a single instance of RLMObject

I'm currently developing an iOS app with Realm as the backend data storage. I have a class that is an RLMObject for the user profile. It stores their name, profile picture, stats, etc.
There should only always be one of these objects, however I know implementing a singleton pattern is usually a bad idea. Currently I have it implemented as
//AppDelegate.swift, applicationDidFinishLaunching
//If there's no user profiles yet (first time launch), create one
if UserProfile.allObjects().count == 0 {
let realm = RLMRealm.defaultRealm()
try! realm.transactionWithBlock() {
realm.addObject(UserProfile())
}
}
//ProfileViewController.swift
//Get the first UserProfile
var userProfile: UserProfile? {
get {
return UserProfile.allObjects().objectAtIndex(0) as? UserProfile
}
}
Is there a better way to keep track of a single instance of this class?
Your code sample uses a computed property, which will fetch the object from the Realm each time you access it.
Instead, try using a lazy var property:
lazy var userProfile: UserProfile? = {
return UserProfile.allObjects().objectAtIndex(0) as? UserProfile
}()
This type of property will load the object from the Realm only the first time it is accessed. All subsequent accesses will be directly to the object.
Note that, since UserProfile is a Realm object, its fields will automatically update in response to changes made to the underlying object in the Realm. Likewise, any changes you wish to make will need to be wrapped within a Realm write transaction.
In terms of your overall architecture, there is nothing wrong with storing a single instance of an object type in a Realm similar to what you are doing. You may want to give your UserProfile object a hardcoded constant primary key, then use the 'add or update' version of the update API (see https://realm.io/docs/swift/latest/#updating-objects). This will allow you to avoid having to explicitly create a new object.

Writing a list of objects to realm

I just start thinking about implementing Realm and have some newbie doubts.
For example I receive a list of objects which I transform into realm objects. How can I write the whole list directly to Realm, without writing each object separately?
A typical workaround comes into my mind, which would be defining a bigger object which contains this list as a property and writing that bigger object to the DB. But is it possible to write directly the obtained list of objects to DB without something that encapsulates them?
And also when preparing this list of Realm objects, I basically have a list of dictionaries. What's the best approach into transforming all of them directly into a list of Realm objects?
Just to confirm. When you mention your list of objects at the top and then mention that you have a list of dictionaries, are they the same thing?
If they are, and your data is coming down as a set of dictionaries, as long as the key names match the properties in your Realm Object models, then you can simply loop through each dictionary and pass each one to Realm to create it as a new entry in the database:
let realm = try! Realm()
try! realm.write {
for dictionary in dictionaries {
realm.create(MyObject.self, value: dictionary, update: false)
}
}
More information on that API can be found on Realm Swift's documentation page.
If your objects don't map directly to the properties in your Realm file, then you're going to need to manually reformat the structure of your list of objects until either could be inserted as a dictionary, or you can manually create your own Realm Object instances off them.

Persists One RLMObject

In my app I have a CustomUser which inherits from RLMObject.
I am wondering how do I store 1 object only for this CustomUser object?
func saveUser() {
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
realm.addObject(customUser)
realm.commitWriteTransaction()
}
This code will write an additional object to the database everytime it runs. Resulting in many objects in database after the code is run multiple times.
Any thoughts please?
The simplest way would be to query Realm to check if there are any previously saved objects before going ahead and adding a new one.
func saveUser() {
let realm = RLMRealm.defaultRealm()
if CustomUser.allObjects().count > 0 {
return
}
realm.beginWriteTransaction()
realm.addObject(customUser)
realm.commitWriteTransaction()
}
On a more advanced note, if you want to reference your single CustomUser object explicitly, you can use Realm's primary key feature to directly query for that specific object. There's more information on Realm's website on how to set up classes to use primary keys (In the Objective-C section, since it looks like you're using the Objective-C bridged version).
Good luck!

Resources