I added a list to my realm object class, and i need to migrate but i keep getting this error:
"Migration is required due to the following errors:
- Property 'UserIdCard.idImages' has been added."
these are my classes:
class UserIdCard: Object {
dynamic var idAccountId: Int = 0
dynamic var idName: String = ""
dynamic var idType: String = ""
let idImages = List<UserImage>()
override static func primaryKey() -> String? {
return "idAccountId"
}
}
class UserImage: Object {
dynamic var image: Data? = nil
}
And this is my migration code:
if (oldSchemaVersion < 4) {
migration.enumerateObjects(ofType: UserImage.className()) { oldObject, newObject in
newObject!["image"] = nil
}
migration.enumerateObjects(ofType: UserIdCard.className()) { oldObject, newObject in
let image = migration.create(UserImage.className(), value: Data())
let images = newObject?["idImages"] as? List<MigrationObject>
images?.append(image)
}
}
I did exactly like the example Realm provided : Link
Also i tried this : Link
,but it's not working, i tried to pass different values in "value" field but nothing worked, what is the right way to migrate a list in realm?
Thanks,
Related
I have one to many relationship between two models, Product and WishList like the code below
class Product : Object {
#objc dynamic var productID : String = ""
#objc dynamic var name : String = ""
#objc dynamic var unitPrice: Double = 0.0
#objc dynamic var imagePath : String = ""
#objc dynamic var quantity = 0
#objc dynamic var hasBeenAddedToWishList : Bool = false
var parentCategory = LinkingObjects(fromType: WishList.self, property: "products")
convenience init(productID : String, name: String, unitPrice: Double, imagePath: String, quantity: Int = 1, hasBeenAddedToWishList: Bool = false) {
self.init()
self.productID = productID
self.name = name
self.unitPrice = unitPrice
self.imagePath = imagePath
self.quantity = quantity
self.hasBeenAddedToWishList = hasBeenAddedToWishList
}
override static func primaryKey() -> String? {
return "productID"
}
}
and WishList:
class WishList : Object {
#objc dynamic var userID: String = ""
var products = List<Product>()
}
I try to add or remove product to WishList using the code below when love button in the image above is pressed :
// 1. get the wishlist based on UserID
let allWishList = realm.objects(WishList.self)
let theWishList = allWishList.filter("userID CONTAINS[cd] %#", userID).first
guard let userWishList = theWishList else {return}
// 2. modify Wishlist data in Realm.
if loveIconHasBeenFilled {
guard let index = userWishList.products.index(where: {$0.productID == selectedProduct.productID}) else {return}
do {
// remove data from realm database
try realm.write {
userWishList.products.remove(at: index)
}
} catch {
// error Handling
}
} else {
do {
// add product to wishlist model in realm database
try realm.write {
userWishList.products.append(selectedProduct)
}
} catch {
// error Handling
}
}
and here is the data in Realm Browser
and the problem is ....
when I run the app for the first time, I can add, and then remove, and then add the product again to the wishlist, and the number of product in the realm database still be the same (all have unique productID)
but when I restart the app, and try to click that love button to add the product to wishlist again, it throws an error
'RLMException', reason: 'Attempting to create an object of type
'Product' with an existing primary key value 'a'
this error is triggered because of this line of code userWishList.products.append(selectedProduct) , when adding the product to WishList, it automatically adds Product in the realm database. so because I keep adding the same product that has the same productID (primary key) it will throw that error.
so, my question is, how to avoid addition in Product if it has the same productID (primary key), it is better if i can just update the product in realm database when adding the product to the wishlist using this line of code: userWishList.products.append(selectedProduct)
You could check the property hasBeenAddedToWishList of the selected product and only add it if the property is false.
if loveIconHasBeenFilled {
//your logic to remove already added products
} else if !selectedProduct.hasBeenAddedToWishList { //<--- check if the product already exists in wishlist if not you add it
do {
// add product to wishlist model in realm database
try realm.write {
userWishList.products.append(selectedProduct)
}
} catch {
// error Handling
}
}
I have my Device class defined as such:
class Device: Object {
dynamic var asset_tag = ""
}
I have an array like this ["43", "24", "23", "64"]
and I would like to loop through the array and add each one into the asset_tag attribute of the Device entity in Realm.
To create an array in Realm you use List. According to Realm's docs, List is the container type in Realm used to define to-many relationships. It goes on to say, "Properties of List type defined on Object subclasses must be declared as let and cannot be dynamic." This means you need to define an entirely separate object to create a list in Realm, there are no native types that will allow you to do something like
let assetTagList = List<String>().
You need to create an AssetTags object and make a list of it in your Device object as follows:
class AssetTags: Object {
dynamic var stringValue = ""
}
class Device: Object {
var asset_tags: [String] {
get {
return _assetTags.map { $0.stringValue }
}
set {
_assetTags.removeAll()
_assetTags.append(objectsIn: newValue.map({ AssetTags(value: [$0]) }))
}
}
let _assetTags = List<AssetTags>()
override static func ignoredProperties() -> [String] {
return ["asset_tags"]
}
}
Now you can do this to add asset tags to Device.
//adding asset tags
let realm = try! Realm()
try! realm.write {
let device = Device()
device.asset_tags = ["1", "2"]
realm.add(device)
}
//looking at all device objects and asking for list of asset tags
for thisDevice in realm.objects(Device.self) {
print("\(thisDevice.asset_tags)")
}
SECOND EDIT
This is not the way I would do it but I think this is what you might find easier to understand.
class AssetTags: Object {
dynamic var id = 0
override static func primaryKey() -> String? {
return "id"
}
dynamic var tagValue = ""
}
class Device: Object {
let assetTags = List<AssetTags>()
}
So now Device has a list of asset tags. I added a primary id for Asset Tags because you indicated you might want to add more properties so an id should help make each one unique. It is optional though, so you can remove it if you do not need it.
To add assetTags objects to a Device object in a for-loop style (note: device in this code exampl is the device object you want to save the asset tags to):
var assetTagArray: [String] = ["1", "2"]
let realm = try! Realm()
for assetTag in assetTagArray {
let newAssetTag = AssetTags()
newAssetTag.tagValue = assetTag
newAssetTag.id = (realm.objects(AssetTags.self).max(ofProperty: "id") as Int? ?? 0) + 1
do {
try realm.write {
device?.assetTags.append(newAssetTag)
}
} catch let error as NSError {
print(error.localizedDescription)
return
}
}
I'm using SharkORM on iOS Swift project and I'm having problem with a specific object. I have other objects in the project that works, but this one.
My class is like this:
import Foundation
import SharkORM
class Exam: SRKObject {
dynamic var serverId: NSNumber?
dynamic var type: String?
dynamic var when: String?
dynamic var file: String?
dynamic var filename: String?
dynamic var url: String?
func toJson() -> [String:Any?] {
return [
"name" : type,
"date" : when,
"serverId" : serverId,
"file" : file,
"filename" : filename,
"url" : url,
"id" : id
]
}
static func fromJson(_ json: [String:Any?]) -> Exam {
let exam = Exam()
exam.id = json["id"] as? NSNumber ?? NSNumber(value: 0)
exam.type = json["name"] as? String ?? ""
exam.file = json["file"] as? String ?? ""
exam.filename = json["filename"] as? String ?? ""
exam.url = json["url"] as? String ?? ""
exam.serverId = json["serverId"] as? NSNumber ?? NSNumber(value: 0)
exam.when = json["date"] as? String ?? ""
return exam
}
}
I add to an array objects that needs to be saved and after user press save button, the app starts committing it.
// save exams
for exam in self.examsToSave {
if !exam.commit() {
print("Error commiting exam.")
}
}
if let rs = Exam.query().fetch() {
print("exams: \(rs.count)")
}
The commit method returns true and I added a print right after it finishes committing and result is zero.
Any idea?
I found out the problem right after post it. In my text here, my variable "when" was colored like a keyword. I just changed the name to "whenDate" and it started committing. Weird it didn't show up any error or a crash. Anyway, a variable named "when" is not allowed inside a SRKObject.
Given same Commit problem, figured best to keep to topic here. And I've spent number of hours trying to debug this so thought I'd try this:
I have a simple class (and overly simplified but tested as provided here):
class user: SRKObject {
#objc dynamic var name: String = ""
}
(No, no odd syntax coloring on the object property names.)
And I do the following (simplified test case), first defining
public var currUser = user()
Then in a function:
let users = user.query().fetch() as! [user]
if users.count > 0 {
currUser = users[0]
NSLog("Num users \(users.count) name \(currUser.name)")
} else {
self.currUser.name = "T1 User"
if !self.currUser.commit() {
print ("Failed to commit")
}
else {
let u = user.query().fetch()
print("Num users \(u.count)")
}
}
The commit() call succeeds -- at least I don't get the "Failed to commit" message. However, I do get zero count in the last fetch().
Viewing the DB file (in Simulator) from a "DB Browser for SQLite" shows the DB is created fine but the "user" record is not in there, and neither is the "committed" data.
BTW when I had this code in SRKTransaction.transaction, it DID fall into the failure (rollback) block, so yes, did get a transaction error, but tracking that down will be next.
In the meantime, appreciate in advance any help given this case as presented should work.
#retd111, I copied and pasted your code and got the same error.
Then, I moved the currUser to a local var, like this:
override func viewDidLoad() {
super.viewDidLoad()
var currUser: user? = nil
let users = user.query().fetch() as! [user]
if users.count > 0 {
currUser = users[0]
NSLog("Num users \(users.count) name \(currUser!.name)")
} else {
currUser = user()
currUser!.name = "T1 User"
if !currUser!.commit() {
print ("Failed to commit")
}
else {
let u = user.query().fetch()
print("Num users \(u?.count ?? 0)")
}
}
}
It works without problems.
For some reason, if you instantiate the currUser as a class member variable, as your example:
public var currUser = user()
it won't work.
I'm trying to count how many cards are equal inside my List and update the new quantity property with the count number
eg:
newObject!["list"] = [CardObject1, CardObject2, CardObject2,
CardObject2, CardObject3, CardObject3]
Assign to temporary list
var tempList = List()
CardObject1 (Update quantity property to 1)
CardObject2 (Update quantity property to 3)
CardObject3 (Update quantity property to 2)
tempList = [CardObject1, CardObject2, CardObject3]
Assign back to newObject!["list"] the updated/migrated list
newObject!["list"] = newList
Crash at newList.index(of: card)
* Terminating app due to uncaught exception 'RLMException', reason: 'Object type 'CardDTO' does not match RLMArray type 'DynamicObject'.'
* First throw call stack:
Info:
DeckDTO.swift
class DeckDTO: Object {
dynamic var id = NSUUID().uuidString
dynamic var name = ""
var list = List<CardDTO>()
override class func primaryKey() -> String {
return "id"
}
}
CardDTO.swift
class CardDTO: Object, Mappable {
// Other properties
dynamic var id = NSUUID().uuidString
dynamic var quantity: Int = 1
// Other properties
required convenience public init?(map: Map) {
self.init()
mapping(map: map)
}
func mapping(map: Map) {
//Map all my properties
}
override class func primaryKey() -> String {
return "id"
}
}
What I'm trying
if oldSchemaVersion < 2 {
migration.enumerateObjects(ofType: CardDTO.className()) { oldObject, newObject in
newObject!["quantity"] = 1
}
migration.enumerateObjects(ofType: DeckDTO.className()) { oldObject, newObject in
var newList = List<DynamicObject>()
let oldList = newObject!["list"] as! List<DynamicObject>
for card in oldList {
if let i = newList.index(of: card), i >= 0 {
newList[i] = (newList[i] as! CardDTO).quantity += 1 //some how do quantity++
print("increment quantity")
} else {
newList.append(card)
}
}
newObject!["list"] = newList
}
}
Realm migration blocks (and their dynamic API) aren't really well-suited for your particular use case. Neither index(of:) nor append() can be used properly with dynamic objects.
My recommendation for approaching this problem is to simply set the quantity properties to 1 in the migration block as you are doing, and then set a boolean flag that indicates that you need to perform the deck update. Then, before you do anything else (perhaps in application(_: didFinishLaunchingWithOptions:)), open the Realm and check for that flag. If that flag is set you can then open the Realm and perform the migration using the normal Realm API.
Here is some example code:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Get the configuration and set the migration block on it
var config = Realm.Configuration.defaultConfiguration
config.schemaVersion = 2
var needsMigrationToV2 = false
config.migrationBlock = { migration, oldSchemaVersion in
if oldSchemaVersion < 2 {
migration.enumerateObjects(ofType: CardDTO.className()) { oldObject, newObject in
newObject!["quantity"] = 1
}
needsMigrationToV2 = true
}
}
let realm = try! Realm(configuration: config)
// Run the rest of the migration using the typed API
if needsMigrationToV2 {
let allDecks = realm.objects(DeckDTO.self)
try! realm.write {
for deck in allDecks {
var uniqueCards : [CardDTO] = []
for card in deck.list {
if uniqueCards.contains(card) {
card.quantity += 1
} else {
uniqueCards.append(card)
}
}
deck.list.removeAll()
deck.list.append(objectsIn: uniqueCards)
}
}
}
return true
}
One more thing to note is that List<T> properties should be declared as let, not var, since reassigning to a List<T> property is an error.
So I have Realm object
class RegistrationPlateDB: RLMObject {
dynamic var registrationPlate : String = ""
dynamic var user : String = ""
override static func primaryKey() -> String? {
return "registrationPlate"
} ...
and would like to change it to
class RegistrationPlateDB: Object {
dynamic var plateID : Int = -1
dynamic var registrationPlate : String = ""
dynamic var name : String = ""
dynamic var user : String = ""
override static func primaryKey() -> String? {
return "plateID"
} ....
So i've written a migration
migration.enumerate(RegistrationPlateDB.className()) { oldObject, newObject in
newObject!["name"] = ""
newObject!["user"] = ""
newObject!["registrationPlate"] = ""
newObject!["plateID"] = -1
newObject!["primaryKeyProperty"] = "plateID";
}
but I get an error probably because of the primaryKey change, since if I leave that line out it works but the primary key doesn't change.
Can someone give me any idea how to change the primaryKey.
EDIT: The first object was written for Objective-c realm
EDIT2: Or if anybody would know how could I make plateID autoincrement
Katsumi from Realm here. You don't need to attempt change primary key in the migration block.
We always automatically update the schema to the latest version, and the only thing that you have to handle in the migration block is adjusting your data to fit it (e.g. if you rename a property you have to copy the data from the old property to the new one in the migration block).
So newObject!["primaryKeyProperty"] = "plateID"; is not needed.
I think your migration block should be like the following:
migration.enumerate(RegistrationPlateDB.className()) { oldObject, newObject in
newObject!["user"] = oldObject!["user"]
newObject!["registrationPlate"] = oldObject!["registrationPlate"]
newObject!["plateID"] = Int(oldObject!["registrationPlate"] as! String)
}
If you'd like to assign sequence numbers to plateID, for example:
var plateID = 0
migration.enumerate(RegistrationPlateDB.className()) { oldObject, newObject in
newObject!["user"] = oldObject!["user"]
newObject!["plateID"] = plateID
plateID += 1
}