Realm: nested update issue for many to one relation - ios

I am using Realm notification block for updating messages in a page.
let messageResult = realm.Object(MessageRealm.self)
notificationTokenMessage = messageResult.addNotificationBlock{ [weak self] (changes: RealmCollectionChange) in {
switch changes {
case .initial(_),
.update(_, _, _, _):
self?.tableView.reloadData()
default:
break
}
}
}
In MessageRealm class, there is a property, name author. An author is basically a UserRealm object.
Class MessageRealm extends Object {
dynamic var _id: String? = nil
dynamic var body: String? = nil
dynamic var author: UserRealm? = nil
override class func primaryKey() -> String? { return "_id" }
}
Class UserRealm extends Object {
dynamic var _id: String? = nil
dynamic var fullName: String? = nil
dynamic var status: String? = nil // 'online' or 'offline'
override class func primaryKey() -> String? { return "_id" }
}
When a new message is received from socket, the message page is updated as it gets notifications from Realm. But, when user update notification is received from socket, the Realm updates the message page. I don't want to update message page for an update in author object.
Probable Solutions:
Class MessageRealm extends Object {
dynamic var _id: String? = nil
dynamic var body: String? = nil
dynamic var author: UserRealm? = LinkingObjects(fromType: UserRealm.self, property: "messages")
override class func primaryKey() -> String? { return "_id" }
}
Class UserRealm extends Object {
dynamic var _id: String? = nil
dynamic var fullName: String? = nil
dynamic var status: String? = nil // 'online' or 'offline'
let messages = List<MessageRealm>()
override class func primaryKey() -> String? { return "_id" }
}
We can solve it using LinkingObjects. But, this inverse relation needs a direct relation to map. Am I right? So, need to have a property of List of Messages in User. And from MessageRealm I have to link to User. But this will be complicated to maintain.
Store author's id in MessageRealm as a foreign key like a traditional database.
What do you suggest?
How can we do normalization in Realm to avoid update issue?
Is there any convention or best practices to manage a bigger database? (I am aware of Tim's answer https://stackoverflow.com/a/31594548/2666902 )

In my opinion, the best solution would be keeping the author property as a one-to-one relationship from MessageRealm to UserRealm, since a single message can only have one author and creating an inverse relationship in UserRealm.
class MessageRealm: Object {
dynamic var _id: String? = nil
dynamic var body: String? = nil
dynamic var author: UserRealm? = nil
override class func primaryKey() -> String? { return "_id" }
}
class UserRealm: Object {
dynamic var _id: String? = nil
dynamic var fullName: String? = nil
let messages = LinkingObjects(fromType: MessageRealm.self, property: "author")
override class func primaryKey() -> String? { return "_id" }
}
This way, you only need to keep the author property of your messages updated and the messages property of UserRealm will automatically keep in sync, so any time you try to access it, you will see all MessageRealm objects, where the author equals the specific user.

Related

Preventing duplicate storage to realm

I am using realm to store and persist my data. Everything works fine and I just discovered that users can actually store duplicate item which is bad. I would am unable to create a check to prevent duplicate items, any help would be appreciated
Function
func addData(object: OfflineModel) {
try! database.write {
database.add(object, update: true)
}
}
//MARK:- Get Offline
func getDataFromDB() -> Results<OfflineModel> {
offlineItems = database.objects(OfflineModel.self)
return offlineItems!
}
//MARK:- Create Offline
func createOfflineList(createdDate: Date, photo: Data, title: String, completion: #escaping CompletionHandler) -> Void {
REALM_QUEUE.sync {
let offlineList = OfflineModel()
offlineList.createdDate = createdDate
offlineList.photo = photo
offlineList.title = title
OfflineFunctions.instance.addData(object: offlineList)
completion(true, nil)
}
}
Model
#objc dynamic var id = UUID().uuidString
#objc dynamic var photo: Data? = nil
#objc dynamic var title : String = ""
#objc dynamic var createdDate: Date?
override static func primaryKey() -> String {
return "id"
}
The issue is that in the createOfflineList method you create a new OfflineModel, which generates a random id using UUID().uuidString and hence you cannot have duplicate models from Realm's point of view, since id, which is used as the primary key will always be different. You'll need to use title (or any other non-random property that you actually want to use to identify your model instances) as the primary key.
class OfflineModel: Object {
#objc dynamic var photo: Data? = nil
#objc dynamic var title : String = ""
#objc dynamic var createdDate: Date?
override static func primaryKey() -> String {
return "title"
}
}

Update single key in realm into array of model in array of model

I am facing on problem, I want to update a single key in my realm model which is in below hierarchy.
Array Model -> Array Model - Key
class OrdersOfDeliveryModel: Object, Mappable {
dynamic var id : String?
dynamic var transportCost : String?
var items = List<ItemsModel>()
required convenience init?(map: Map) {
self.init()
}
override class func primaryKey() -> String? {
return "id"
}
func mapping(map: Map) {
id <- map["id"]
transportCost <- map["transportCost"]
items <- (map["items"], ListTransform<ItemsModel>())
}
}
class ItemsModel : Object, Mappable{
dynamic var orderedQty : Int = 0
dynamic var orderedReceivedQty : String?
required convenience init?(map: Map) {
self.init()
}
override class func primaryKey() -> String? {
return nil
}
func mapping(map: Map) {
var dictWeightageqty = [String:Any]()
dictWeightageqty <- map["weightageQty"]
orderedQty = dictWeightageqty["ordered"] as? Int ?? 0
orderedReceivedQty = dictWeightageqty["received"] as? String
}
}
Please consider this example.
I want to update orderedReceivedQty key which is in ItemsModel but How can I find that key in realm. But my ItemsModel is depending on OrderOfDeliveryModel which can I find with primary key.
I have ways like I delete this whole model using single ID and replace it with new data OR I can get that key and update it.
I dont want to delete whole object and insert new I want to update single key.
Please help me with it.
If you have the OrderOfDeliveryModel object, you can query it for the ItemsModel object you want to update, and then update it in place (if you find it).
let results = myOrders.items.filter("orderedQty == %#", 10)
if (results.count == 1) {
results[0].orderedReceivedQty = 10
}

Why I can't access an element of a class instance in Swift

I'm currently writing an iPhone application in Swift. I have a global class instance of my user database, like this:
var currentUser = UserDB()
class UserDB: Object {
dynamic var id:String = "0"
override class func primaryKey() -> String {
return "id"
}
var userName: String?
var firstName: String?
var lastName: String?
}
Then I try to print my user info with a print in another class:
UserDB {
id = 56826e22971f34731a07ba09;
userName = aze;
firstName = (null);
lastName = (null);
}
But if I try to print a single value, it won't works:
print(currentUser.userName)
Will print:
nil
Do you have any idea why?
By the way, is it a good idea to deal with user info like this? I put these info inside a Realm database on exit, when going to background, or after account upgrade.
You need to declare the properties as dynamic if you want Realm to map them. It seems that you did this only for the id property.
dynamic var userName: String?
dynamic var firstName: String?
dynamic var lastName: String?

Swift core data removing entries in one to many relationships

I'm using Xcode 6.4 and Swift in iOS 8.4 project
I have a model with one to many relationship
class Account: NSManagedObject {
#NSManaged var userId: Int
#NSManaged var accessToken: String?
#NSManaged var userName: String
#NSManaged var sex: String
#NSManaged var email: String
#NSManaged var avatarUrl: String?
#NSManaged var friends: NSSet
}
class User: NSManagedObject {
#NSManaged var id: Int
#NSManaged var userName: String
#NSManaged var sex: String
#NSManaged var email: String
#NSManaged var avatarUrl: String
}
When I'm trying to remove all friends it doesn't work:
extension Account {
func replaceFriends(friends:[User]) {
var friendsList = self.mutableSetValueForKey("friends")
friendsList.removeAllObjects()
friendsList.addObjectsFromArray(friends)
}
func getFriends() -> [User] {
return self.mutableSetValueForKey("friends").allObjects as! [User]
}
}
class AccountDao: BaseDao {
private class func findAccount() -> Account? {
if let result = moc.executeFetchRequest(NSFetchRequest(entityName: "Account"), error: nil) as? [Account] {
if (!result.isEmpty) {
return result[0]
}
}
return nil
}
class func getAccount() -> Account {
return findAccount() ??
NSEntityDescription.insertNewObjectForEntityForName("Account", inManagedObjectContext: moc) as! Account;
}
}
During saving I'm setting empty array:
class func saveObjectContext() -> Bool {
var error: NSError?
var account = AccountDao.getAccount()
account.replaceFriends([User]())
if !moc.save(&error) {
self.log.error("Error during saving context, \(error)")
return false
} else {
println("Count in log \(AccountDao.getAccount().getFriends().count)")
self.log.info("Info save context \(error)")
return true
}
}
In log it returns 0:
Count in log 0
But in ControllerView still I'm getting AccountDao.getAccount().getFriends().count == 1. Method replaceFriends works only first time when I fetch and save data. And it save changes for simple properties like userName but not for friends. Do you have any ideas?
I found that in relation one to many Account -> User without inverse relationship in User, after changing list in Account every user in list have to be marked that has changed (property updated == true). But it always was set to false.
I added additional property to User, and after every change of list I had to change this property like user.toUpdate = true. After this change everything is working fine.

Many-to-one with primary key (unique constraint)

I've got an Article and a Category model linked by a many-to-one relationship. However, the Category model has a unique constraint on the id property because it's the primary key as you can see below.
class Article: Object
{
dynamic var id: String = ""
dynamic var title: String = ""
dynamic var category: Category()
override static func primaryKey() -> String? {
return "id"
}
}
class Category: Object
{
dynamic var id: String = ""
dynamic var title: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
This will work until an Article got the same Category and throw an exception because of the unique constraint.
How am I supposed to implement this kind of relationship ? Is there any built-in way to persist only the Category id and retrieve the corresponding Category ?
Thanks
As you can read in Realm doc (0.92.1), you have to use a List<Object> for a many-to-one relationship.
See this link :
http://realm.io/docs/swift/latest/
class Dog: Object {
dynamic var name = ""
dynamic var owner: Person? // Can be optional
}
class Person: Object {
... // other property declarations
let dogs = List<Dog>()
}
let someDogs = Realm().objects(Dog).filter("name contains 'Fido'")
jim.dogs.extend(someDogs)
jim.dogs.append(rex)
So in your case, I guess it should be something like that :
class Article: Object
{
dynamic var id: String = ""
dynamic var title: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
class Category: Object
{
dynamic var id: String = ""
dynamic var title: String = ""
dynamic var articles = List<Article>()
override static func primaryKey() -> String? {
return "id"
}
}
If your Realm version is older :
class Category: Object
{
...
dynamic var categories = RLMArray(objectClassName: Article.className())
}

Resources