Case insensitive primary key realm swift - swift

I have an object with the following structure:
class REObject:Object {
dynamic var id = ""
dynamic var status = ""
override static func primaryKey() -> String? {
return "id"
}
}
The flow is: I get an array of items from the BE, the user can enter the object's id and change the status.
And the question is: how could get the item with case-insensitive id?
if let item = realm.object(ofType: REObject.self, forPrimaryKey: id) {
return item
}

Override isSameObjectAs in your class and compare lowercase (or uppercase) versions of id there

Related

How I can get new unique id for each new object in Realm?

I try create objects in Realm with unique id. I use this code:
class Persons: Object {
#objc dynamic var id = 0
#objc dynamic var name = ""
override static func primaryKey() -> String? {
return "id"
}
}
and in my StorageManager I use this code:
import RealmSwift
let realm = try! Realm()
class StorageManager {
static func saveObject(_ person: Persons) {
try! realm.write {
realm.add(person)
}
}
static func deleteObject(_ person: Persons) {
try! realm.write {
realm.delete(person)
}
}
}
But when I add second new object, I get error:
Terminating app due to uncaught exception 'RLMException', reason:
'Attempting to create an object of type 'Persons' with an existing
primary key value '0'.'
How I can get new unique id for each new object?
Your best bet is to let your code define that for you
class PersonClass: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var name = ""
override static func primaryKey() -> String? {
return "id"
}
}
UUID().uuidString returns a unique string of characters which are perfect for primary keys.
HOWEVER
That won't work for Realm Sync in the future. MongoDB Realm is the new product and objects need to have a specific primary key property called _id which should be populated with the Realm ObjectID Property. So this is better if you want future compatibility
class PersonClass: Object {
#objc dynamic var _id = ObjectId()
#objc dynamic var name = ""
override static func primaryKey() -> String? {
return "_id"
}
}
You can read more about it in the MongoDB Realm Getting Started Guide
Note that after RealmSwift 10.10.0, the following can be used to auto-generate the objectId
#Persisted(primaryKey: true) var _id: ObjectId
Keeping in mind that if #Persisted is used on an object then all of the Realm managed properties need to be defined with #Persisted
Note if using #Persisted then the override static func primaryKey() function is not needed
The better answer since Realm v10 came out is to use the dedicated new objectID class
A 12-byte (probably) unique object identifier.
ObjectIds are similar to a GUID or a UUID, and can be used to uniquely identify objects without a centralized ID generator. An ObjectID consists of:
A 4 byte timestamp measuring the creation time of the ObjectId in seconds since the Unix epoch.
A 5 byte random value
A 3 byte counter, initialized to a random value.
ObjectIds are intended to be fast to generate. Sorting by an ObjectId field will typically result in the objects being sorted in creation order.

How to use enums for class functions

I'm working on a project and I have created a class to handle the json response to convert it to modal class and change it back to json request with updated data if needed.
Here in the class I'm getting and saving values from and into dictionary. I need to create an enum for the dictionary keys so that there should be less chance for error for complex key formats.
I even tried using like
enum Fields {
case Name
case Email
}
but Fields.Email return Fields object
if I use a protocol of a variable like
protocol someProtocol {
var name: String { get }
}
extension someProtocol {
var name:String {
return String(describing: self)
}
}
and then extend the enum Fields:someProtocol
then I can use it like Fields.name.name or Fields.email.name
But My client will not approve this I want to create an enum so that I can access the string directly like for name I want key "Name" and I should get it liek "Fields.name" or ".name"
So here I have two objectives
first it that I need to create something that can be accessed through class function
second it should be common so that I can use it with multiple classes
third I can access it with less operators
—
class PersonService {
class Update {
var name = ""
var email = ""
var personId = 0
func createDataFrom(dic:[AnyHashable : Any]) -> Update {
let update = Update()
update.name = dictionary["Name"]
update.email = dictionary["Email"]
update.personId = dictionary["Id"]
return update
}
func createDataTo() -> [AnyHashable:Any] {
var ret = [AnyHashable : Any]()
ret["Name"] = name
ret["Email"] = email
ret["Id"] = personId
return ret
}
}
}
Something like that?
enum Fields: String {
case Name = "Name"
case Email = "Email"
}
Print(Fields.Name.rawValue)
result: "Name"
Or
struct Constants {
static let name = "Name"
static let email = "Email"
}
print(Constants.name)
result: "Name"

Modifying Realm List tries to recreate linked Objects

I have the following (simplified) Realm Models:
final class Item: Object {
#objc dynamic var identifier: String = ""
#objc dynamic var programSet: ProgramSet?
override static func primaryKey() -> String? {
return "identifier"
}
}
final class ProgramSet: Object {
#objc dynamic var identifier: String = ""
#objc dynamic var editorialCategory: EditorialCategory?
override static func primaryKey() -> String? {
return "identifier"
}
}
final public class EditorialCategory: Object {
#objc dynamic var identifier: String = ""
override public static func primaryKey() -> String? {
return "identifier"
}
}
final class Playlist: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var name = ""
#objc dynamic var created = Date()
var items = List<Item>()
override static func primaryKey() -> String? {
return "id"
}
}
When the user adds an item to a playlist, I try to append the ItemModel to the Playlist.items List:
try realm.write {
playlistToModify.items.append(item)
}
My problem is that it's possible for Items to share the same EditorialCategory, so it may already be in the database. When I add other items via the Realm.add(_:update:) method, Realm won't try to recreate the linked objects if one with the same primary key exists.
The List.append(_:) method seems to force recreating the linked objects of an ItemModel when called. The app crashes in the following line from object_accessor.hpp:
throw std::logic_error(util::format("
Attempting to create an object of type '%1' with an existing primary key value '%2'.",
object_schema.name, ctx.print(*primary_value)));
Stacktrace:
The object_schema.name is "EditorialCategoryModel", which is linked in ProgramSetModel, which itself is linked from ItemModel.
Is there a way to avoid this error and append the item to the list without Realm attempting to re-create every linked object? Thanks!

Realm Primary key with OR operator

I am using RealmSwift for my new app. My Realm class has two primary keys.
Just an example I have a Realm Model(Product) like this:-
class Product: Object, Mappable {
dynamic var id: String? = nil
dynamic var tempId: String? = nil
dynamic var name: String? = nil
dynamic var price: Float = 0.0
dynamic var purchaseDate: Date? = nil
required convenience init?(map: Map) {
self.init()
}
//I want to do something like this
override static func primaryKey() -> String? {
return "id" or "tempId"
}
func mapping(map: Map) {
id <- map["_id"]
tempId <- map["tempId"]
name <- map["name"]
price <- map["price"]
purchaseDate <- (map["purchaseDate"], DateFormatTransform())
}
So I am creating an realm object in my device and storing into realm db with primary key tempId, as the actual primary key is the id, which is a server generated primary key is coming only after the report sync. So when I am sending multiple reports to the server with those tempId server response me back with actual id mapped with each tempId. As the report is not only created from my side so I can't keep tempId as the primary key. I thought of Compound primary key but it won't solve the problem.
So I want to create a primary key such as If id is there then that is the primary key else tempId is the primary key.
How to do this?
What you need essentially is a computed property as a primary key. However, this isn't supported at the moment, only stored and managed realm properties can be used as primary keys. A workaround could be to define both id and tempId to have explicit setter functions and inside the setter function you also need to set another stored property, which will be your primary key.
If you want to change id or tempId don't do it in the usual way, but do it through their setter function.
Idea taken from this GitHub issue.
class Product: Object {
dynamic var id:String? = nil
dynamic var tempId: String? = nil
func setId(id: String?) {
self.id = id
compoundKey = compoundKeyValue()
}
func setTempId(tempId: String?) {
self.tempId = tempId
compoundKey = compoundKeyValue()
}
dynamic var compoundKey: String = ""
override static func primaryKey() -> String? {
return "compoundKey"
}
func compoundKeyValue() -> String {
if let id = id {
return id
} else if let tempId = tempId {
return tempId
}
return ""
}
}
dynamic private var compoundKey: String = ""
required convenience init?(map: Map) {
self.init()
if let firstValue = map.JSON["firstValue"] as? String,
let secondValue = map.JSON["secondValue"] as? Int {
compoundKey = firstValue + "|someStringToDistinguish|" + "\(secondValue)"
}
}

How can I generate ids for objects in RealmSwift?

I am using RealmSwift. What is the best / canonical way to generate ids for objects?
Here is what I came up with:
class MyObject: Object {
dynamic var id = ""
dynamic var createdAt = NSDate()
override class func primaryKey() -> String {
return "id"
}
func save() {
let realm = try! Realm()
if(self.id == "") {
while(true) {
let newId = NSUUID().UUIDString
let saying = realm.objectForPrimaryKey(MyObject.self, key: newId)
if(saying == nil) {
self.id = newId
break
}
}
}
try! realm.write {
realm.add(self)
}
}
}
I wanted a function that persists the object to Realm and either overwrites or creates a new one based on the id. This seems to work ok, but I wasn't sure if there was something built into realm to do this. Or is there a better way?
Thanks.
I know this is a few months old, but this is how I implement auto incrementing Primary Keys.
This code is untested, but you'll get the general idea
class MyObject: Object {
/**
Primary Key
Since you can't change the primary key after the object is saved,
we'll use 0 to signify an unsaved object. When we set the primary key,
we'll never use 0.
*/
dynamic var id: Int = 0
/**
Some persisted value
*/
dynamic var someString: String?
var nextID: Int {
do {
let realm = try Realm()
/// I would use .max() but Realm only supports String and signed Int for primary keys
/// so using overflow protection, the `next` primary key would be Int.min if the
/// current value is Int.max
var id = realm.objects(MyObject.self).sorted("id", ascending: true).last?.id ?? 0
/// add 1 to id until we find one that's not in use... skip 0
repeat {
id = Int.addWithOverflow(id, 1).0 /// Add with overflow in case we hit Int.max
} while id == 0 || realm.objectForPrimaryKey(MyObject.self, key: id) != nil
return id
} catch let error as NSError {
/// Oops
fatal(error: error.localizedDescription)
}
}
convenience init(someString: String?) {
self.init()
id = nextID
self.someString = someString
save()
}
override class func primaryKey() -> String? {
return "id"
}
func save() {
/// Gotta check this in case the object was created without using the convenience init
if id == 0 { id = nextID }
do {
let realm = try Realm()
try realm.write {
/// Add... or update if already exists
realm.add(self, update: true)
}
} catch let error as NSError {
fatalError(error.localizedDescription)
}
}
}
The process for creating a unique string ID (IE: a UUID) is very similar:
class MyObject: Object {
/**
Primary Key
*/
dynamic var id: String = ""
/**
Some persisted value
*/
dynamic var someString: String?
var nextID: String {
do {
let realm = try Realm()
var id: String = NSUUID().UUIDString
/// NSUUID().UUIDString almost always makes a unique ID on the first try
/// but we'll check after we generate the first one just to be sure
while realm.objectForPrimaryKey(MyObject.self, key: id) != nil {
id = NSUUID().UUIDString
}
return id
} catch let error as NSError {
/// Oops
fatal(error: error.localizedDescription)
}
}
convenience init(someString: String?) {
self.init()
id = nextID
self.someString = someString
save()
}
override class func primaryKey() -> String? {
return "id"
}
func save() {
/// Gotta check this in case the object was created without using the convenience init
if id == "" { id = nextID }
do {
let realm = try Realm()
try realm.write {
/// Add... or update if already exists
realm.add(self, update: true)
}
} catch let error as NSError {
fatalError(error.localizedDescription)
}
}
}
Realm(Swift) does not currently support auto-incrementing primary keys. You can set a primary like you are above, but for auto-incrementing and unique keys, there are a couple routes that you can go:
UUID (like you have above)
Query the max "id" (primary key) of your object and set the object to be inserted as id + 1. Something like...
let id = realm.objects(MyObject).max("id") + 1
Create your own hash signature (one potential example: SHA256(epoch timestamp + SHA256(object.values))