Say I have a class and its Realm representation that looks like this:
class Dog {
var identifier: String
var age: Int
...
override static func primaryKey() -> String? {
return "identifier"
}
}
Now here is what my new Identifier class looks like:
class Identifier {
var functionalId: String
var version: String
...
}
I need to replace my Dog's identifier String property to be an Identifier like this:
class Dog {
var identifier: Identifier
var age: Int
...
override static func primaryKey() -> String? {
return "identifier" // I need to change this
}
}
but I'm having a hard time replacing the content of the primaryKey() method:
How do I tell Realm to look for an object's sub property for the primaryKey() ?
I tried something like:
override static func primaryKey() -> String? {
return "identifier.functionalId"
}
But it seems that I was too naive, it won't work
** EDIT ** Following comments, here is the output of the crash I'm getting:
Terminating app due to uncaught exception 'RLMException', reason: 'Primary key property 'identifier.functionalId' does not exist on object Dog
Sorry for bad English though, I couldn't find the words fir this simple problem, especially the title!
I've never tried this in Realm, but it might be possible using a dynamic variable for your primary key and a function that pulls the value from the sub-object:
var _identifier: Identifier
dynamic lazy var identifier: String = self.identifierValue()
override static func primaryKey() -> String? {
return "identifier"
}
func identifierValue() -> String {
return _identifier.functionalId
}
How do I tell Realm to look for an object's sub property for the
primaryKey()
You can't.
Looking at the errors you've mentioned:
If you try setting the primary key to:
override static func primaryKey() -> String? {
return "identifier"
}
Then you get an error from Realm saying: Property 'identifier' cannot be made the primary key of 'Dog' because it is not a 'string' or 'int' property.
If you try setting the primary key to:
override static func primaryKey() -> String? {
return "identifier.functionalId"
}
Then you get an error from Realm saying: Primary key property 'identifier.functionalId' does not exist on object Dog
This leads to the conclusion that the primary key must be of type String or Int, and it must be a property of Dog, not another class.
Related
I thought this would be pretty straightforward after reading here and here but I'm a bit stuck.
I have a 'favouriteWorkout' object that looks like this :
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
#objc dynamic var workoutReference = WorkoutSessionObject()
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}
What I'm trying to do here is reference a WorkoutSessionObject in Realm that links from a WorkoutName when a workout is saved as a favourite.
My WorkoutSessionObject has a primary key of workoutID which is a UUID string. It looks like this :
class WorkoutSessionObject: Object {
#objc dynamic var workoutID = UUID().uuidString
#objc dynamic var workoutType = ""
let exercises = List<WorkoutExercise>()
#objc dynamic var totalExerciseCount = 0
#objc dynamic var rounds = 0
#objc dynamic var favourite : Bool = false
override class func primaryKey() -> String? {
return "workoutID"
}
}
I've then tried to save using this :
let favouriteWorkout = FavouriteObject()
favouriteWorkout.favouriteWorkoutName = favouriteName
favouriteWorkout.workoutReference = (realm.object(ofType: WorkoutSessionObject.self, forPrimaryKey: self.workoutID))!
do {
try realm.write {
realm.add(favouriteWorkout)
}
} catch {
print ("Error adding favourite")
}
but i get a crash when I run of :
'RLMException', reason: 'The FavouriteObject.workoutReference property must be marked as being optional.
However, when I then try to make it optional (by adding ?) it says
"Cannot use optional chaining on non-optional value of type 'WorkoutSessionObject"!
Summary
I want to save a reference of the workoutID of a WorkoutSessionObject in my FavouriteObject which is an actual link to the WorkoutSessionObject (so the properties can be accessed from favourites)
Update
using the answers below I've now sorted the problem of the workout reference. This is now showing in Realm as the proper format () under "workoutReference". However, I'm now getting "nil" in "workoutReference" when trying to save. I know the workoutID is coming through correctly as I am printing it in the console.
You need to change the declaration of workoutReference. First of all, you need to make it Optional by writing ? after the type. Secondly, you shouldn't assign a default value to it, it needs to be Optional for a reason. The linked docs clearly state that
to-one relationships must be optional
, and workoutReference is clearly a to-one relationship.
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
#objc dynamic var workoutReference:WorkoutSessionObject?
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}
In property-cheatsheet you can see that a non-optional Object-property is not allowed, so you have to change it like the following:
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
// here you have to make the property optional
#objc dynamic var workoutReference: WorkoutSessionObject?
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}
How can I model one-to-one relationship between objects?
For example, I have models for user_infoA, user_infoB and user_profile.
user_profile has
user_id (PK)
name
age
user_infoA has
info_a_id (PK)
user_profile
user_infoB has
info_b_id (PK)
user_profile
user_profile (P) have relationship with both user_infoA (A) and user_infoB(B). When A is deleted, also will P be deleted or not? Will P be deleted only if when related A and B are deleted?
And how can I model this with realm swift?
Many-to-one relationship needs optional property, and it makes me use force unwrapping optional. :(
[EDITED]
class RealmMyProfile: Object {
#objc dynamic var id: Int64 = 0
#objc dynamic var profile = RealmUserProfile()
}
class RealmUserProfile: Object {
#objc dynamic var userId: Int64 = 0
#objc dynamic var name: String = ""
override static func primaryKey() -> String? {
return "userId"
}
}
Exception 'The RealmMyProfile.profile property must be marked as being optional.' occurred. It should be optional.
To-one relationships (links) in Realm cannot enforce that a link is always present. So they always have to be marked as optional because there's no way to prevent nil from being stored for a link in the file format.
Therefore, we require that Realm models defined in Swift explicitly mark to-one relationships as Optional.
class RealmMyProfile: Object {
#objc dynamic var id: Int64 = 0
#objc dynamic var profile:RealmUserProfile?
}
class RealmUserProfile: Object {
#objc dynamic var userId: Int64 = 0
#objc dynamic var name: String = ""
override static func primaryKey() -> String? {
return "userId"
}
}
You can do this solution which may save you from using the unwrapping value
Realm Issue 2814
dynamic var profile_:RealmUserProfile? = nil
var profile: RealmUserProfile {
get {
return profile_ ?? RealmUserProfile()
}
set {
profile_ = newValue
}
}
I am writing a method that will give localized names of CNPostalAddress elements. The localization keys I am attempting to retrieve are global constants.
/// Get the localised name of a CNPostalAddress element
///
/// - Parameter field: CNPostalAddress element name (Street, SubLocality, City, SubAdministrativeArea, State, PostalCode, Country, ISOCountryCode)
/// - Returns: localized name of the address field
func localizedAddressFieldName(for field: String) -> String? {
let keyPathKey = "CNPostalAddress\(field)Key"
if let localizationKey = value(forKey: keyPathKey) as? String {
return CNPostalAddress.localizedString(forKey: localizationKey)
} else {
return nil
}
}
However, the application crashes when getting the the localizationKey with the following log:
Terminating app due to uncaught exception 'NSUnknownKeyException',
reason: '[<MyProject.MyViewController 0x7fd83d81c8b0>
valueForUndefinedKey:]: this class is not key value coding-compliant
for the key CNPostalAddressStreetKey.'
CNPostalAddressStreetKey is a valid key, as shown in Apple docs, but is a Global constant.
Solution
extension CNPostalAddress {
class func localizedAddressFieldName(for field: String) -> String? {
let localizationKey = field.decapitalizingFirstLetter()
return CNPostalAddress.localizedString(forKey: localizationKey)
}
}
extension String {
func decapitalizingFirstLetter() -> String {
let firstLetterLowercase = String(prefix(1)).lowercased()
return firstLetterLowercase + String(dropFirst())
}
}
it's because you may have to use those variables instead:
print("\(CNPostalAddress.localizedString(forKey: "subLocality"))")
print("\(CNPostalAddress.localizedString(forKey: "street"))")
so instead of
let keyPathKey = "CNPostalAddress\(field)Key"
you should use
let keyPathKey = "\(field)"
it's case sensitive, so you might have to make some adjustments.
see here
First value(forKey: keyPathKey) is a method defined in NSKeyValueCoding protocol, you are calling that method in your viewController that is why is crashing to avoid the crash you have to implement that method in your ViewController
override func value(forKey key: String) -> Any? {
}
but anyway I think you don't need call this method in first instance
func localizedAddressFieldName(for field: String) -> String? {
let localizationKey = "CNPostalAddress\(field)Key"
return CNPostalAddress.localizedString(forKey: localizationKey)
}
If your key is not defined in CNPostalAddress the result will be an empty string
I am trying to create compound primary key from two keys. Using lazy for compoundKey will raise an exception - either remove lazy or add to ignore property list
So when I try to add ignore property list I am getting following exception - Terminating app due to uncaught exception 'RLMException', reason: 'Primary key property 'compoundKey' does not exist on object 'Collection'
Removing lazy and setting the empty string will add empty key and hence single row which will treat all primary key value as empty.
This is my code
class Collection : Object {
#objc dynamic var count: Int = 0
#objc dynamic var nextURL: String?
#objc dynamic var previousURL: String?
func setCompoundNextURL(nextURL: String) {
self.nextURL = nextURL
compoundKey = compoundKeyValue()
}
func setCompoundTourPreviousURL(previousURL: String) {
self.previousURL = previousURL
compoundKey = compoundKeyValue()
}
public dynamic lazy var compoundKey: String = self.compoundKeyValue()
override static func primaryKey() -> String? {
return "compoundKey"
}
override static func ignoredProperties() -> [String] {
return ["compoundKey"]
}
func compoundKeyValue() -> String {
return "\(nextURL ?? "")\(previousURL ?? "")"
}
}
Please help. I am not able to figure where I went wrong.
You cannot tell Realm to use an ignored property as a primary key. An ignored property isn't persisted to the Realm. The primary key property must be persisted to the Realm. Additionally, the primary key property's value cannot be changed after the object is created. For this reason I'd suggest computing the value inside a convenience initializer and assigning it to the property at that time.
I have next error: Attempting to create an object of type 'TypeId' with an existing primary key value '96292'.
And I got crash after this.
Using String type for the primary key instead of the Int type, and use UUID for each object, then you could avoid the duplicated keys.
class AModel: Object {
#objc dynamic var id = UUID().uuidString
override static func primaryKey() -> String? {
return "id"
}
}
Alternatively, if you want to use Int, and you are sure about that there is only one object will be created in a second, you could use timestamp value to avoid the situation too:
class AModel: Object {
#objc dynamic var id = Date().timeIntervalSince1970
override static func primaryKey() -> String? {
return "id"
}
}
Agree with #Tj3n and #EpicPandaForce's opinions, updating it if it's not a new object actually.