Prevent Realm from overwriting a property when updating an Object - ios

I've setup a REST API to realm object in iOS. However I've found an issue with creating a favorite flag in my object. I've created a favorite bool, however everytime the object is updated from the API it sets the favorite to default false again. Here I want this flag to not be updated, since the favorite only is stored locally. How can I achieve this?
class Pet: Object{
dynamic var id: Int = 1
dynamic var title: String = ""
dynamic var type: String = ""
dynamic var favorite: Bool = false
override class func primaryKey() -> String {
return "id"
}
}
CreateOrUpdate
let pet = Pet()
pet.id = 2
pet.name = "Dog"
pet.type = "German Shephard"
try! realm.write {
realm.add(pet, update: true)
}

There are two ways to solve this:
1. Use an Ignored Property:
You can tell Realm that a certain property should not be persisted. To prevent that your favorite property will be persisted by Realm you have to do this:
class Pet: Object{
dynamic var id: Int = 1
dynamic var title: String = ""
dynamic var type: String = ""
dynamic var favorite: Bool = false
override class func primaryKey() -> String {
return "id"
}
override static func ignoredProperties() -> [String] {
return ["favorite"]
}
}
or you could
2. Do a partial update
Or you could tell Realm explicitly which properties should be updated when you update your Pet object:
try! realm.write {
realm.create(Pet.self, value: ["id": 2, "name": "Dog", "type": "German Shepard"], update: true)
}
This way the favorite property will not be changed.
Conclusion
There is one big difference between the two approaches:
Ignored Property: Realm won't store the favorite property at all. It is your responsibility to keep track of them.
Partial Update: Realm will store the 'favorite' property, but it won't be updated.
I suppose that the partial updates are what you need for your purpose.

If you want to be more explicit, there is third option:
3. Retrieve the current value for the update
// Using the add/update method
let pet = Pet()
pet.id = 2
pet.name = "Dog"
pet.type = "German Shephard"
if let currentObject = realm.object(ofType: Pet.self, forPrimaryKey: 2) {
pet.favorite = currentObject.favorite
}
try! realm.write {
realm.add(pet, update: true)
}
// Using the create/update method
var favorite = false
if let currentObject = realm.object(ofType: Pet.self, forPrimaryKey: 2) {
favorite = currentObject.favorite
}
// Other properties on the pet, such as a list will remain unchanged
try! realm.write {
realm.create(Pet.self, value: ["id": 2, "name": "Dog", "type": "German Shephard", "favorite": favorite], update: true)
}

4. NSUserDefaults (or any other data store, really)
I ran into the same issue and I opted for the other, more traditional option of saving things to another data store (NSUserDefaults). In my case, I was storing the last time a user viewed an item and storing this data in NSUserDefaults felt appropriate. I did something like the following:
First, define a unique key for the object you are storing (self here is the model object being viewed and rendered):
- (NSString *)lastViewedDateKey {
// Note each item gets a unique key with <className>_<itemId> guaranteeing us uniqueness
return [NSString stringWithFormat:#"%#_%ld", self.class.className, (long)self.itemId];
}
Then, when a user views the item, set the key to:
- (void)setLastViewedToNow {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:[NSDate date] forKey:self.lastViewedDateKey];
[userDefaults synchronize];
}
Later, I use it like this:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSDate *createdOnDate = <passed in from elsewhere>;
NSDate *lastViewedDate = [userDefaults objectForKey:self.lastViewedDateKey];
if (!lastViewedDate || [createdOnDate compare:lastViewedDate] == NSOrderedDescending) {
...
}
As opposed to the above solutions:
1. There is no data persistence.
2. This creates yet another place where one has to define the properties of an object and will likely lead to errors if one forgets to update this list every time a new property gets added.
3. If you are doing any large batch updates, going back through every object is not really practical and will certainly cause pain down the road.
I hope this gives someone another option if they need it.

Related

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

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.

How can I properly copy objects from one Realm object to another object

Based on the following code I would like to be able to create a new ItemList from an existing one. In other words I have an ItemList called First List and I want to create a new ItemList, call it Second List and fill it with the Items from First List.
The way I have it right now is that it creates the Second List as expected, the Items from the First List show in Second List but what doesn't work is when I want to delete only the Items from First List, it deletes Items from both lists. I guess I'm not truly copying the items.
So the question is, how can I copy Items from First List to Second List?
Object Models:
class ItemList: Object {
dynamic var listName = ""
dynamic var createdAt = NSDate()
let items = List<Item>()
}
class Item:Object{
dynamic var productName:String = ""
dynamic var createdAt = NSDate()
}
Code to create Second List from First List
This Works fine, it creates Second List and adds the items from First List but I don't think I'm making copies just showing them in Second List.
let newList = ItemList()
newList.listName = "Second List"
if let selectedList = realm.objects(ItemList.self).filter("listName = %#", "First List").first{
let itemsFromFirstList = selectedList.items
newList.items.append(objectsIn:itemsFromFirstList)
}
try! realm.write {
realm.add(newList)
}
This code is supposed to delete only the items from First List
This actually deletes items from both First List and Second List
let listToDelete = realm.objects(ItemList.self).filter("listName = %#", "First List").first
try! realm.write {
for item in (listToDelete?.items)! {
realm.delete(realm.objects(Item.self).filter("productName = %#", item.productName).first!)
}
}
What you want to do is use:
for record in postsDB.objects(PostModel.self) {
if !combinedDB.objects(PostModel.self).filter("postId == \(record.parentId)").isEmpty {
combinedDB.create(PostModel.self, value: record, update: false)
}
}
The create method is inherited from Object. It tells the target to create a new object. Use true if you want it to look to see if there is already a record there, and update it if there is.
PostModel is the Object type, record is what you want copied.
Edit: I added the if statement to provide more context. You didn't show your class definitions, so I was guessing. This is a working example. I ask for a set of records from DatabaseA and copy it to DatabaseB (postsDB to combinedDB).
So if the type of the object you're trying to insert is a List, I'd recommend you define a subclass of Object, and have at least the list you need as a property.
class TagList: Object {
dynamic var tag = ""
var list = List<PostModel>()
override class func primaryKey() -> String? {
return "tag"
}
}
Full working example illustrating: creating new objects, copying all objects to a second list, deleting from second list after copying, adding to first list (which didn't get anything deleted from it.
import Foundation
import RealmSwift
class Letter: Object {
dynamic var letter = "a"
}
class Letters: Object {
var letterList = List<Letter>()
}
class ListExample {
let listRealmStore = try! Realm() // swiftlint:disable:this force_try
func testThis() {
print(Realm.Configuration.defaultConfiguration.fileURL!)
listRealmStore.beginWrite()
addSingleItems() // add 3 objects to the DB
let firstList = Letters()
let allObjects = listRealmStore.objects(Letter.self)
for item in allObjects {
firstList.letterList.append(item)
}
let secondList = Letters()
let itemsToCopy = firstList.letterList
for item in itemsToCopy {
let obj = listRealmStore.create(Letter.self)
obj.letter = item.letter
secondList.letterList.append(obj)
}
let third = Letter()
third.letter = "Z"
listRealmStore.add(third)
firstList.letterList.append(third)
secondList.letterList.removeLast()
do {
try listRealmStore.commitWrite()
} catch let error {
print("couldn't commit db writes: \(error.localizedDescription)")
}
print("list one:\n\(firstList)")
print("list two:\n\(secondList)")
}
func addSingleItems() {
for letter in ["a", "b", "c"] {
let objectToInsert = Letter()
objectToInsert.letter = letter
listRealmStore.add(objectToInsert)
}
}
}
Results in:
list one:
Letters {
letterList = List<Letter> (
[0] Letter {
letter = a;
},
[1] Letter {
letter = b;
},
[2] Letter {
letter = c;
},
[3] Letter {
letter = Z;
}
);
}
list two:
Letters {
letterList = List<Letter> (
[0] Letter {
letter = a;
},
[1] Letter {
letter = b;
}
);
}
Are you really trying to create copies of your items, or do you just want to be able to remove them from lists independently?
When you do:
newList.items.append(objectsIn: itemsFromFirstList)
you end up with the same objects being in both lists. List<T> just stores references to objects that live within the Realm. Appending an object to a List just references the existing object, it doesn't copy the object.
When you call Realm.delete(_:) you remove that object entirely from the Realm, not just from a single list that it is a member of. To remove an object from a List, you should instead use List.remove(objectAtIndex:).
One part the solution you are looking for could be like this, make copy objects in the list, or you can just use this idea to clone whole list it self:
Previously answered here
As of now, Dec 2020, there is not proper solution of this issue. We have many workarounds though.
Here is the one I have been using, and one with less limitations in my opinion.
Make your Realm Model Object classes conform to codable
class Dog: Object, Codable{
#objc dynamic var breed:String = "JustAnyDog"
}
Create this helper class
class RealmHelper {
//Used to expose generic
static func DetachedCopy<T:Codable>(of object:T) -> T?{
do{
let json = try JSONEncoder().encode(object)
return try JSONDecoder().decode(T.self, from: json)
}
catch let error{
print(error)
return nil
}
}
}
Call this method whenever you need detached / true deep copy of your Realm Object, like this:
//Suppose your Realm managed object: let dog:Dog = RealmDBService.shared.getFirstDog()
guard let detachedDog = RealmHelper.DetachedCopy(of: dog) else{
print("Could not detach Note")
return
}
//Change/mutate object properties as you want
detachedDog.breed = "rottweiler"
As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our realm object. Just make sure all your Realm Model Classes conform to Codable.
Though its NOT an ideal solution, but its one of the most effective workaround.

Storing an array of strings using Realm's RLMArray

Does anyone know how you can use Realm to store an array of strings? I'm trying to map the following response into Realm correctly:
"zoneInfo": {
"tariffInfoLines": [
"In this city you pay per minute."
]
}
We have a zoneInfo object that contains a tariffInfoLines array. This tariffInfoLines array contains strings. In Realm there are two different variable types for storing data. The first is RLMObject which allows your standard NSString, int, long etc.
The second type is RLMArray, which is used for arrays (as NSArray is not supported). You have to give the array a type, which must be a class that subclasses RLMObject. We have so far got around this by using a ABCRealmString object, as shown below:
#property RLMArray<ABCRealmString> *tariffInfoLines;
ABCRealmString contains an NSString property (it is basically a wrapper):
#property NSString *value;
However what this means is that when Realm tries to map the response to persist the data, it is looking for a value for the key "value" (the name of the property). It appears that it expects a response similar to the following:
"zoneInfo": {
"tariffInfoLines": [
{
"value": "In this city you pay per minute."
},
]
}
In the project, we have it working for the following structure:
"userOptions": [
{
"wantsEmailNotifications": true,
"wantsPushNotifications": false
},
]
This has an array, with objects inside that have clear key value pairs that Realm can map to. The zoneInfo structure appears to be the only place that we have an array with sets of values inside without them being inside an object or having any keys.
If anyone could shed some light on this, regarding if this is possible using Realm, or whether an API change is required to match a structure that Realm can map.
Cross posting from the github issue response: Although this example demonstrates how to store flat arrays of strings on a Realm model, you can extend this pattern to store anything from arrays of integers to native Swift enum's. Basically anything that you can map to a representable type in Realm.
class RealmString: Object {
dynamic var stringValue = ""
}
class Person: Object {
var nicknames: [String] {
get {
return _backingNickNames.map { $0.stringValue }
}
set {
_backingNickNames.removeAll()
_backingNickNames.appendContentsOf(newValue.map({ RealmString(value: [$0]) }))
}
}
let _backingNickNames = List<RealmString>()
override static func ignoredProperties() -> [String] {
return ["nicknames"]
}
}
// Usage...
let realm = try! Realm()
try! realm.write {
let person = Person()
person.nicknames = ["John", "Johnny"]
realm.add(person)
}
for person in realm.objects(Person) {
print("Person's nicknames: \(person.nicknames)")
}
// Prints:
// Person's nicknames: ["John", "Johnny"]
UPDATE (most of the previous answers are no longer correct):
You can now store primitive types or their nullable counterparts (more specifically: booleans, integer and floating-point number types, strings, dates, and data) directly within RLMArrays or Lists. If you want to define a list of such primitive values you no longer need to define cumbersome single-field wrapper objects. Instead, you can just store the primitive values themselves.
Lists of primitive values work much the same way as lists containing objects, as the example below demonstrates for Swift:
class Student : Object {
#objc dynamic var name: String = ""
let testScores = List<Int>()
}
// Retrieve a student.
let realm = try! Realm()
let bob = realm.objects(Student.self).filter("name = 'Bob'").first!
// Give him a few test scores, and then print his average score.
try! realm.write {
bob.testScores.removeAll()
bob.testScores.append(94)
bob.testScores.append(89)
bob.testScores.append(96)
}
print("\(bob.testScores.average()!)") // 93.0
All other languages supported by Realm also supports lists of primitive types.
For the Swift 3.0 here is the change (in my case the Xcode 8 compiler didn't offer auto fix when i switched to swift 3.0 so I had some pain to resolve it).
_backingNickNames.append(objectsIn: newValue.map { RealmString(value: [$0]) })
The RealmString approach is good, but you end up with a new RealmString every time you update the values, leaving a ton of unused objects laying around if you don't clean them up.
I would suggest using something like:
fileprivate let separator = "\u{FFFF}"
class Person: Object {
fileprivate dynamic var _nicknames: String?
var nicknames: [String] {
get { return _nicknames?.components(separatedBy: separator) ?? [] }
set { _nicknames = newValue.isEmpty ? nil : newValue.joined(separator: separator) }
}
override static func ignoredProperties() -> [String] {
return ["nicknames"]
}
}
extension String {
func toStringObject() -> StringObject {
return StringObject(initValue: self)
}
}
extension Sequence where Iterator.Element == String {
func toStringObjects() -> List<StringObject> {
let list = List<StringObject>()
for s in self {
list.append(s.toStringObject())
}
return list
}
}
extension Int {
func toIntObject() -> IntObject {
return IntObject(initValue: self)
}
}
extension Sequence where Iterator.Element == Int {
func toIntObjects() -> List<IntObject> {
let list = List<IntObject>()
for s in self {
list.append(s.toIntObject())
}
return list
}
}

How do I set a auto increment key in Realm?

I have a unique msgid for each ChatData object.
#interface ChatData : RLMObject
#property NSInteger msgid;
....
#end
But each time I create a new object I have to query all objects and get the last msgid.
RLMArray *all = [[ChatData allObjects] arraySortedByProperty:#"msgid" ascending:YES];
ChatData *last = [all lastObject];
ChatData *newData = [[ChataData alloc]init];
newData.msgid = last.msgid+1;
Is there an efficient way to replace this implementation?
Realm doesn't have auto increment behavior, so you'll need to manage that yourself. A question I'd encourage you to ask yourself about your data:
Is it necessary to have sequential, contiguous, integer ID's?
If not, then a unique string primary key might be sufficient. Then you can use something like [[NSUUID UUID] UUIDString] to generate unique string ID's. The nice thing about this is that these UUID's are more or less guaranteed to be unique, even in multithreaded scenarios.
If so, it might be more efficient to always keep the last number in memory, so that queries aren't required every time a new ID should be generated. If objects might be created in multiple threads, make sure to make your nextPrimaryKey() function thread-safe, otherwise it might generate the same number twice (or more!).
You are use this Code for Auto Incremental Primary Key in Swift :
var myvalue = realm.objects(ChatData).map{$0.id}.maxElement() ?? 0
myvalue = myvalue + 1
Autoincrement id Realm in Swift 2.0:
insert code in class realm and object write use
import Foundation
import RealmSwift
class Roteiro: Object {
dynamic var id = 0
dynamic var Titulo = ""
dynamic var Observacao = ""
dynamic var status = false
dynamic var cadastrado_dt = NSDate()
override static func primaryKey() -> String? {
return "id"
}
//Incrementa ID
func IncrementaID() -> Int{
let realm = try! Realm()
if let retNext = realm.objects(Roteiro.self).sorted(byKeyPath: "id").first?.id {
return retNext + 1
}else{
return 1
}
}
in file write use:
let Roteiro_Add = Roteiro()
//increment auto id
Roteiro_Add.id = Roteiro_Add.IncrementaID()
Roteiro_Add.Titulo = TituloDest
Roteiro_Add.Observacao = Observacao
Roteiro_Add.status = false
let realm = try! Realm()
try! realm.write({ () -> Void in
realm.add([Roteiro_Add])
})
In Realm you need to manage auto-inc ID it self so there are many ways to manage it. Below is some of them.
func incrementID() -> Int {
let realm = try! Realm()
return (realm.objects(Person.self).max(ofProperty: "id") as Int? ?? 0) + 1
}
call this method every time when you adding record.
I used a creationDate in my Model, so I created a Unix timeStamp based on this date, and used it as the primaryKey of my object.
It's 99.99% guaranteed to be unique in my case (because the timestamp is precise to the second), but it may depend on your use case.
It's less robust than a UUID, but in many cases it's sufficient.
extension NSDate {
/** Returns a NSDate instance from a time stamp */
convenience init(timeStamp: Double) {
self.init(timeIntervalSince1970: timeStamp)
}
}
extension Double {
/** Returns a timeStamp from a NSDate instance */
static func timeStampFromDate(date: NSDate) -> Double {
return date.timeIntervalSince1970
}
}
This is essentially what is suggested in jpsim's answer, using UUID to generate unique keys. We query prior to inserting to ensure uniqueness. This will most often only incur one query; in the very rare case of a collision it will continue until it finds a unique id. This solution is a natural extension on the Realm type and is generic over classes that inherits from Object. The class must implement primaryKey and return the name of a String property.
extension Realm {
func createAutoUnique<T: Object>(_ type: T.Type) -> T {
guard let primaryKey = T.primaryKey() else {
fatalError("createAutoUnique requires that \(T.self) implements primaryKey()")
}
var id: String
var existing: T? = nil
repeat {
id = UUID().uuidString
existing = object(ofType: type, forPrimaryKey: id)
} while (existing != nil)
let value = [
primaryKey: id
]
return create(type, value: value, update: false)
}
}

Resources