Realm Migration: Migrating objects to another - swift

I've added a new Model object to my realm objects and I am trying to move data from my old realm object properties to this new object.
In the old Schema, I have the following:
class Item: Object {
#objc dynamic var image = ""
#objc dynamic var coverImage = ""
#objc dynamic var video = ""
}
In the new schema, I've added a new property called media
so now it's looking this this
class Item: Object {
#objc dynamic var image = ""
#objc dynamic var coverImage = ""
#objc dynamic var video = ""
#objc dynamic var media: Media?
}
I've also added this new Model object:
class Media: Object {
#objc dynamic var fullImage = ""
#objc dynamic var thumbnailImage = ""
#objc dynamic var video = ""
var item = LinkingObjects(fromType: Item.self, property: "media")
}
My goal is to move the data from the old Item objects to the new Media objects.
I was trying to do something like this, but I don't know how to migrate that linking object, any help in the right direction would be appreciated.
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
// enumerate first object
migration.enumerateObjects(ofType: Item.className()) { oldItem, newItem in
let image = oldItem?["image"] as! String
let coverImage = oldItem?["coverImage"] as! String
let video = oldItem?["video"] as! String
//migrate second object
migration.enumerateObjects(ofType: Media.className(), { (oldMedia, newMedia) in
})
}
}
})

You don't need to do anything with LinkingObjects, realm calculates those automatically when you query them.
All you'll need to do in your migration is set media to be a new Media object with the values you already have.
Other notes:
The second enumerateObjects isn't needed.
You can remove image, coverImage, and video from Item since you're moving those value to Media
Edit: This is what you would need to have in your migration.
let media = Media()
media.fullImage = oldItem?["image"] as! String
media.thumbnailImage = oldItem?["coverImage"] as! String
media.video = oldItem?["video"] as! String
newItem?["media"] = media

When adding objects to a project, the migration is super simple. In this case you're not changing or adding data to existing or new properties so it's even easier.
All that needs to be done is to increment the schemaVersion and implement your migration block. Suppose the prior version was 1, increment that to 2.
let config = Realm.Configuration (
schemaVersion: 2,
migrationBlock: { migration, oldSchemaVersion in
//nothing to do here since we not altering any data
})
Realm already knows your Object has data and that data will persist as it's not being altered. All of the Item objects will have the new media property added and linked to the new Media object.
See Local Migrations for more examples. The Updating values section is when you want to actually manipulate the existing data
Edit:
We now have a bit more information and what the OP is trying to do is to copy data from an existing object to a new object and then create a relationship between the objects. Here's the code that would do that.
The initial object is Item and the new object is Media. For this example I am copying the data in a Item property image to the Media property fullImage
let config = Realm.Configuration (
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
migration.enumerateObjects(ofType: Item.className() ) { oldObject, newObject in
let image = oldObject!["image"] as! String
let media = Media()
media.fullImage = image
newObject!["media"] = media
}
})

Related

Realm Swift Migration: String Wrapper List to String List

I am trying to migrate our Realm Swift schema from using a String wrapper class to the primitive String for collections. I am running into issues extracting the string values from the wrapper during migration.
Here is the wrapper class:
class StringDB: Object {
#objc var stringVal: String = ""
convenience init(string: String) {
self.init()
stringVal = string
}
}
Then I have an example class with a List<StringDB> property:
class MyObjDB: Object {
var emails: List<StringDB> = List<StringDB>()
#objc dynamic var id: String = UUID().uuidString
convenience init(_ emails: [StringDB]) {
self.init()
for email in emails {
self.emails.append(objectsIn: emails)
}
}
override static func primaryKey() -> String? {
return "id"
}
}
that I want to convert to a List<String>.
Here is my migration code.
let config = Realm.Configuration(schemaVersion: latestSchemaVersion, migrationBlock: {
migration, version in
if version < 2 {
migration.enumerateObjects(ofType: MyObjDB.className(), { old, new in
guard let old = old, let new = new else { return }
let oldEmails = old["emails"] as! List<StringDB>
let newEmails = List<String>()
for email in oldEmails {
newEmails.append(email.stringVal)
}
new["emails"] = newEmails
})
}
})
However, the let oldEmails = old["emails"] as! List<StringDB> cast fails. I've tried also casting the collection to List<MigrationObject> and then casting the individual objects to StringDB but that cast fails as well.
I've found a workaround that may be satisfactory (I haven't confirmed yet), of converting the MigrationObject directly to string using coercion like "\(email)" and then running a regEx that will extract a desired substring from the garbage (StringDB {\n\tstringVal = email#address.com;\n}), but I have no idea yet whether that will hold up in production, and I would prefer to work with something resembling a recommended way for doing this migration.
The Realm version is not shown in the question and there are a few typo's in the code. For older Realm versions, Lists should be defined thusly:
let emails = RealmSwift.List<StringDB>() //need RealmSwift. to differentiate it from Swift List
then newer versions should be this:
#Persisted var emails = RealmSwift.List<StringDB>()
Then this is a problem as it iterates over the emails array count times and appends the entire emails list over and over for every email in that list.
for email in emails {
self.emails.append(objectsIn: emails)
}
Also, when using older Realm versons with #Objc property types, they need to include dynamic. So on the StringDB object this
#objc var stringVal: String = ""
should be this
#objc dynamic var stringVal: String = ""
Lastly, the MyObjDB needs to have somewhere to put the new email list. You can't overwrite the old one as it's not the correct type. So add a property
class MyObjDB: Object {
var emails: List<StringDB> = List<StringDB>()
let updatedEmailList = List<String>()
Then to the question: See comments in code for the flow
How about this:
migration.enumerateObjects(ofType: MyObjDB.className()) { oldItem, newItem in
//instantiate a List of the old email objects as migration objects
let oldEmailList = oldItem!["emails"] as! List<MigrationObject>
//we're going to populate a new List with the email strings
var newEmailList = List<String>()
//iterate over the old list, extracting the string from the old object as
// a string an inject it into a new List of Strings
for oldEmailObject in oldEmailList {
let oldEmail = oldEmailObject["stringVal"] as! String
newEmailList.append(oldEmail)
}
//assign the new List of strings to a new emails property
newItem!["updatedEmailList"] = newEmailList
}

Add Data in multiple classes Realm swift

I'm new to swift. I'm working with realm in swift. I've a realm database which have following classes.
I'm inserting same data in each class from application as:
for item in myarray {
print(item)
let realm = try! Realm(fileURL: db_path!)
try! realm.write({
let obj = English()
obj.rowid = ((item["rowid"] as? NSString)?.integerValue)!
obj.words = (item["word"] as? NSString)! as String
obj.frequency = ((item["frequency"] as? NSString)?.integerValue)!
realm.add(obj)
print("successfully added to database")
})
}
class English : Object {
#objc dynamic var rowid = 0
#objc dynamic var words = ""
#objc dynamic var frequency = 0
}
This is saving data in English class. Now I want to save same object in other realm class like Arabic, languages, test.
Should I've to make separate object type class for each class?
No ,you can create object of each class and assign English class object to other like this
let obj1 = Arabic()
obj1 = obj//English Class Object
And append like this
realm.add(obj1)

Trying to add objects to Class and save info to Firebase database

I want to save new objects via a view controller in my app. However, I want these new objects to load when the app is logged into. I am using firebase to save data into a database, but how can I save an object and have it return when the app is logged into again? I am new-ish to programming, sorry for any potential confusion.
Here is where the goal information is read when the app has been logged into.
for i in 0 ... clientList.count - 1 {
screenHandle = ref?.child(organizationCode).child(clientList[i].name).observe(.value, with: { (snapshot) in
let clientStuffLoad = snapshot.value as! [String:Any]
if clientStuffLoad["Goal 1 Description"] != nil {
clientList[i].goal1 = clientStuffLoad["Goal 1"] as! String
} else {
clientList[i].goal1 = ""
}
This is essentially what I have regarding adding a new member to the class Client:
#IBAction func addingClientSaveButton(_ sender: Any) {
var client7 = Client(name: addingClientName.text!,
goal1: addingClientGoal1.text!, goal2:
addingClientGoal2.text!,
goal3: addingClientGoal3.text!,
isSelected: false, s: 1,
ind: 1, targetBehavior1 : addingClientTB1.text!,
targetBehavior2 : addingClientTB2.text!,
targetBehavior3 : addingClientTB3.text!,
targetBehavior1Info : addingClientTB1Info.text!,
targetBehavior2Info : addingClientTB2Info.text!,
targetBehavior3Info : addingClientTB3Info.text!)
but I would like the object name to read the client name input as opposed to client7
The second part to this is that I want a way to write this to the database, and be able to read it at log in so that I can use the properties of the class and add to it when adding a new client.
This is a super broad question because it covers a lot of different aspects of working with Firebase; writing, reading, handling DataSnapshots etc. Also, I don't know what your data represents so I picked something for me to cover some of the aspects of working with Firebase.
There's no error checking but it works as is. I've commented along the way.
Firebase has no objects; just parent and child nodes. Everything can be thought of as key: value pairs like a dictionary. You cannot write an object or read an object. Only NSString, NSNumber, NSDictionary and the dreaded NSArray (or their Swift counterparts)
Let's start with a class - there's 100 ways to do this but I like classes to be responsible for their properties as well as accepting them and presenting them
class WineClass {
var wine_key = ""
var name = ""
var varietal = ""
//this is used when creating a new wine object before storing in firebase
init(withName: String, andVarietal: String) {
self.name = withName
self.varietal = andVarietal
}
//this is used when we are loading data from firebase to create the wineclass object
init(withSnapshot: DataSnapshot) {
let wineName = withSnapshot.childSnapshot(forPath: "wine_name").value as? String ?? "No Wine Name"
let wineDict = withSnapshot.value as! [String: Any]
let wineVarietal = wineDict["wine_varietal"] as? String ?? "No Wine Varietal"
self.wine_key = withSnapshot.key //when we read a wine, this will be it's reference in case we want to update or delete it
self.name = wineName
self.varietal = wineVarietal
}
//this is use to create a dictionary of key:value pairs to be written to firebase
func getWineDictForFirebase() -> [String: Any] {
let d = [
"wine_name": self.name,
"wine_varietal": self.varietal
]
return d
}
}
Then, we need a class var to store the WineClass's. This would be for example a dataSource for a tableView
var wineArray = [WineClass]() //a class var array to store my wines
Then I will give you two buttons, one that populates and writes some wine to Firebase and then a second that read them in and prints to console
func button0() {
self.writeWine(withName: "Scarecrow", andVarietal: "Red Blend")
self.writeWine(withName: "Ghost Horse", andVarietal: "Cabernet Sauvignon")
self.writeWine(withName: "Screaming Eagle", andVarietal: "Cabernet Sauvignon, Merlot, Cabernet Franc")
}
func button1() {
self.readWines()
}
And then the function that accepts some strings as properites for each wine and writes them to Firebase
func writeWine(withName: String, andVarietal: String) {
let newWine = WineClass(withName: withName, andVarietal: andVarietal) //create a new wine object
let wineListRef = self.ref.child("wine_list") //get a reference to my firebase wine_list
let thisWineRef = wineListRef.childByAutoId() //a new node for this wine
let d = newWine.getWineDictForFirebase() //get the wine properties as a dictionary
thisWineRef.setValue(d) //save it in firebase
}
and finally a function that reads in those wines, and prints their properties in console
func readWines() {
let wineRef = self.ref.child("wine_list")
wineRef.observeSingleEvent(of: .value, with: { snapshot in //we are reading in the entire wine node which will contain many child nodes
let allWines = snapshot.children.allObjects as! [DataSnapshot] //cast each child node as a DataSnapshot & store in array
for wineSnap in allWines { //iterate over each child node in the array
let wine = WineClass(withSnapshot: wineSnap) //create a new wine, ensuring we also keep track of it's key
self.wineArray.append(wine) //add to the array
}
for wine in self.wineArray {
print(wine.wine_key, wine.name, wine.varietal)
}
})
}
lastly, when button0 is clicked, our Firebase looks like this
wine_list
-LhbjhkEC8o9TUISCjdw
wine_name: "Scarecrow"
wine_varietal: "Red Blend"
-LhbjhkEC8o9TUISCjdx
wine_name: "Ghost Horse"
wine_varietal: "Cabernet Sauvignon"
-LhbjhkEC8o9TUISCjdy
wine_name: "Screaming Eagle"
wine_varietal: "Cabernet Sauvignon, Merlot, Cabernet Franc"
and then the output when button1 is clicked
-LhbjhkEC8o9TUISCjdw Scarecrow Red Blend
-LhbjhkEC8o9TUISCjdx Ghost Horse Cabernet Sauvignon
-LhbjhkEC8o9TUISCjdy Screaming Eagle Cabernet Sauvignon, Merlot, Cabernet Franc
Note that self.ref is a reference to the root node of my firebase yours will need to reference your firebase.

Realm Swift Migration to add primary key - Not writing data

I am using RealmSwift and I want to add a new Primary Key to one of my objects.
I have updated the Realm Object to this
class Trip: Object {
dynamic var id = ""
dynamic var start = ""
dynamic var startAddress = ""
dynamic var end = ""
dynamic var endAddress = ""
dynamic var purpose = ""
dynamic var distance = 0.0
dynamic var tripDate = NSDate()
dynamic var month = 0
dynamic var year = 0
dynamic var isWalking = false
dynamic var isReturn = false
func primaryKey() -> String {
return id
}
}
And now I want to migrate to the new version and insert a UUID String as the primary key for any existing record.
The migration works in that the new 'id' field is created, but the UUID strings are not written into the record. No errors displayed on console.
This is what I have added to my AppDelegate application(application:didFinishLaunchingWithOptions:)
I must be missing something, but I cannot determine what, from the documentation or examples on the Realm site.
let config = Realm.Configuration(
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
schemaVersion: 1,
// Set the block which will be called automatically when opening a Realm with
// a schema version lower than the one set above
migrationBlock: { migration, oldSchemaVersion in
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
})
// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
let realm = try! Realm()
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
// The enumerate(_:_:) method iterates
// over every Trip object stored in the Realm file
migration.enumerate(Trip.className()) { oldObject, newObject in
let id = NSUUID().UUIDString
newObject!["id"] = id
}
}
})
The code you've provided sets the default configuration with your migration block after you've already opened the Realm. You need to set the default configuration prior to opening the default Realm in order for the migration block to be used.

Deleting with one-to-many relationship

I have a one-to-many relationship:
class GameSystem: Object {
dynamic var gameSystemName = ""
}
class games: Object {
dynamic var gameSystemName = gameSystemName().name
dynamic var gameTitle = ""
dynamic var gameGenre = ""
}
The gameSystemNames are currently displayed on a TableView. If the user deletes a gameSystemName, I want that gameSystemName along with all of that system's games deleted.
The code I'm currently using will only delete the GameSystem, but leaves all the games.
func deleteRowAtIndexPath(indexPath: NSIndexPath) {
let realm = Realm()
let objectToDelete = gameSystems[indexPath.row]
realm.write {
realm.delete(objectToDelete)
}
gameSystemTableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
gameSystems = Realm(path: Realm.defaultPath).objects(GameSystem)
}
I'm assuming there's a simple way to do this.
If you keep your model as it is, the solution would be to query first for the relevant objects of the relation Game:
// …
let objectToDelete = gameSystems[indexPath.row]
let gameSystemName = objectToDelete.gameSystemName
realm.write {
let games = realm.objects(Game).filter("gameSystemName = ?", gameSystemName)
realm.delete(games)
realm.delete(objectToDelete)
}
// …
Model Recommendation
I'd propose instead that you add an explicit link to your model, instead of expressing the relationship through a loosely linked foreign key. But the object-mapping is very individual and may be dependent on further constraints going beyond the scope of your question and this answer. For further reference, that would look like below:
class GameSystem : Object {
dynamic var name = ""
let games = List<Game>()
}
class Game : Object {
dynamic var title = ""
dynamic var genre = ""
// Use a backlink
// (https://realm.io/docs/swift/latest/#inverse-relationships)
dynamic var gameSystem: GameSystem? {
return linkingObjects(GameSystem.self, forProperty: "games").first
}
}
If you setup your model like this, you can delete your games very easy:
// …
let objectToDelete = gameSystems[indexPath.row]
realm.write {
realm.delete(objectToDelete.games)
realm.delete(objectToDelete)
}
// …
Note: In the future, Realm will bring Cascading Deletes as feature. Once that is released, you won't even need to take care of the manual deletion of associated games, but you will rather be able to declare the strong relationship in your model, so that the Games are automatically deleted.
Alternative Link Declaration
You can also declare your link vice-versa, but that would make it likely harder to use a feature like Cascading Deletes in the future. However the code to delete them for now would look the same as above.
class GameSystem : Object {
dynamic var name = ""
// Use a backlink
// (https://realm.io/docs/swift/latest/#inverse-relationships)
dynamic var games: [Game] {
return linkingObjects(Game.self, forProperty: "gameSystem")
}
}
class Game : Object {
dynamic var title = ""
dynamic var genre = ""
let gameSystem: GameSystem? = nil
}
It is very easy to delete the Parent as well as childrens in REALM SWIFT... You just need to write a small piece of code.
Here I am trying to delete my main category called "Swift" and whenever my main category gets deleted all the Sub Categories are also deleted...
do{
try realm.write({
realm.delete(Category.items)
realm.delete(Category)
})
}catch{
print("ERROR WHILE DELETING CELL ::: \(error)")
}
In short to delete subcategories all you need is to place "items" after . followed by parent name.