I'm facing an issue where a Realm object has another Realm object as member which is always nil after adding to the database.
class MedPack: Object {
dynamic var uuid = NSUUID().UUIDString
dynamic var medicine: Medicine?
convenience init(medicine: Medicine) {
self.init()
self.medicine = medicine
}
override static func primaryKey() -> String? {
return "uuid"
}
}
The reference to the object Medicine is always nil after adding.
class Medicine: Object {
var uuid = NSUUID().UUIDString
var name: String?
override static func primaryKey() -> String? {
return "uuid"
}
}
Creation of object
let medPack = MedPack(medicine: med)
Adding to database
static let sharedInstance = DBHelper()
var realmDb: Realm!
private init() {
realmDb = try! Realm()
}
func store(object: Object) {
try! self.realmDb.write {
self.realmDb.add(object)
}
}
After comparing this code to one of the Realm sample projects, it would appear that simply setting an Object as a child of another does not implicitly write it to the database as well.
Instead, you may need to refactor your code slightly, but make sure you explicitly add your Medicine object to Realm in a write transaction, before you set its relation to MedPack and then write MedPack to the database.
Related
I'm using ReamSwift to store data in my iOS application and I have troubles to figure out, why sometimes objects are duplicated in my application.
I use an UUID as primary key on every object. This property is defined in a base class and all the others are subclasses of that.
I use the update approach with a dictionary. So new objects are created and existing should be updated.
I use a central class which holds the Realm object and handles the update. This method could be called from multiple different threads.
This is my base class
import RealmSwift
open class RealmObject : Object {
static let ID_ATTRIBUTE : String = "id"
#objc dynamic var id : String = ""
override public static func primaryKey() -> String? {
return "id"
}
}
Central class managing swift.
This class holds the Realm Object
public var realm: Realm {
var tempRealm: Realm?
do {
tempRealm = try Realm(configuration: configuration)
}
catch let err {
print("------ERROR----- initializing realm\n \(err)")
}
if let tempRealm = tempRealm{
return tempRealm
}
return self.realm
}
This is the update method in the class. As a fallback if the id property is not set, it will be set as it is the primary key
func update<T: RealmObject>(_ obj : T, values : [String:Any?] ) -> T? {
var vals = values
do {
var res : T?
try realm.write {
if let idattr = vals[T.ID_ATTRIBUTE] as? String {
if(idattr.isEmpty) {
vals[T.ID_ATTRIBUTE] = UUID().uuidString
}
} else {
vals[T.ID_ATTRIBUTE] = UUID().uuidString
}
res = realm.create(T.self, value: vals, update: .modified)
}
return res
} catch {
return nil
}
}
Could calling the update method cause in any case the duplication as I set the primary key of the object? The problem is, I cannot reproduce to find the problem, i just encounter the issue from time to time and in the field from users.
One interesting thing, when a copy is deleted, also the other object will be deleted.
This is the method which deletes objects by id and type
func delete<T: RealmObject>(_ id : String, _ type : T.Type) -> Bool {
do {
let obj = get(id, T.self)
if let obj = obj {
try realm.write {
realm.delete(obj)
}
return true
} else {
return false
}
} catch {
return false
}
}
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"
}
}
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 have an issue about Inheritance with my Objects in Realm.
Could you please have a look a it. I have :
an Object Activity
an Object Sport which I want to be a subclass of Activity
an Object Seminar which I want to be a subclass of Activity
To make this happen I write, according to the documentation, the following code :
// Base Model
class Activity: Object {
dynamic var id = ""
dynamic var date = NSDate()
override static func primaryKey() -> String? {
return "id"
}
}
// Models composed with Activity
class Nutrition: Object {
dynamic var activity: Activity? = nil
dynamic var quantity = 0
}
class Sport: Object {
dynamic var activity: Activity? = nil
dynamic var quantity = 0
dynamic var duration = 0
}
Now I have an Model Category which I want it to hold the activities, doesn’t matter if it’s an Nutrition or Sport.
Here is my code :
class Categorie: Object {
let activities = List<Activitie>()
dynamic var categoryType: String = ""
override static func primaryKey() -> String? {
return "categoryType"
}
}
Now I try to add a Nutrition object to my List<Activitie> by doing this :
let nutrition = Nutrition(value: [ "activity": [ "date": NSDate(), "id": "0" ], "quantity": 12 ])
try! realm.write {
realm.add(nutrition, update: true)
}
It doesn’t work because List<Activitie> expect an Activity Object and not a Nutrition Object. Where am I wrong ?
Thanks a lot for the help.
You encountered one of the big problems of Realm : there is no complete polymorphism.
This github post gives a big highlight on what is possible or not, and a few possible solutions that you can use.
Quick quote from jpsim from the link above:
Inheritance in Realm at the moment gets you:
Class methods, instance methods and properties on parent classes are
inherited in their child classes.
Methods and functions that take
parent classes as arguments can operate on subclasses.
It does not get you:
Casting between polymorphic classes (subclass->subclass,
subclass->parent, parent->subclass, etc.).
Querying on multiple classes simultaneously.
Multi-class container (RLMArray/List and RLMResults/Results).
According to the article about type erased wrappers in swift and the #5 option I have ended up with something more flexible, here is my solution.
( please note that the solution #5 need to be updated for Swift 3, my solution is updated for Swift 3 )
My main Object Activity
class Activity: Object {
dynamic var id = ""
override static func primaryKey() -> String? {
return "id"
}
}
and my inheritance : Nutrition and Sport
class Nutrition: Activity { }
class Sport: Activity { }
The solution according to the solution #5 option : Using a type-erased wrapper for polymorphic relationships.
If you want to store an instance of any subclass of Activity, define a type-erased wrapper that stores the type's name and the primary key.
class AnyActivity: Object {
dynamic var typeName: String = ""
dynamic var primaryKey: String = ""
// A list of all subclasses that this wrapper can store
static let supportedClasses: [Activity.Type] = [
Nutrition.self,
Sport.self
]
// Construct the type-erased activity from any supported subclass
convenience init(_ activity: Activity) {
self.init()
typeName = String(describing: type(of: activity))
guard let primaryKeyName = type(of: activity).primaryKey() else {
fatalError("`\(typeName)` does not define a primary key")
}
guard let primaryKeyValue = activity.value(forKey: primaryKeyName) as? String else {
fatalError("`\(typeName)`'s primary key `\(primaryKeyName)` is not a `String`")
}
primaryKey = primaryKeyValue
}
// Dictionary to lookup subclass type from its name
static let methodLookup: [String : Activitie.Type] = {
var dict: [String : Activity.Type] = [:]
for method in supportedClasses {
dict[String(describing: method)] = method
}
return dict
}()
// Use to access the *actual* Activitie value, using `as` to upcast
var value: Activitie {
guard let type = AnyActivity.methodLookup[typeName] else {
fatalError("Unknown activity `\(typeName)`")
}
guard let value = try! Realm().object(ofType: type, forPrimaryKey: primaryKey) else {
fatalError("`\(typeName)` with primary key `\(primaryKey)` does not exist")
}
return value
}
}
Now, we can create a type that stores an AnyActivity!
class Category: Object {
var categoryType: String = ""
let activities = List<AnyActivity>()
override static func primaryKey() -> String? {
return "categoryType"
}
}
and to store the data :
let nutrition = Nutrition(value : [ "id" : "a_primary_value"] )
let category = Category(value: ["categoryType" : "0"])
category.activities.append(AnyActivity(tree))
To read the data we want to check the activity method, use the value property on AnyActivity
for activity in activities {
if let nutrition = activity.value as? Nutrition {
// cool it's a nutrition
} else if let sport = activity.value as? Sport {
// cool it's a Sport
} else {
fatalError("Unknown payment method")
}
}
Owen is correct, in regarding OO principles, and I noticed that also, that you are not truly doing inheritance.
When an object uses another as an attribute or property, it is Association, not inheritance. I too am reviewing whether Realm supports Table/Object level inheritance like Java does with Hibernate ... but not expecting it.
This framework while still young but powerful, is good enough for me to avoid using SQLite ... very fast, easy to use and much easier with data model migrations !
In your code Nutrition and Sport don't inherit Activity, they inherit Object and composite an Activity instance. To inherit Activity, you should do
class Nutrition: Activity {
dynamic var quantity = 0
}
I think maybe you worried if you did above, your code did not inherit Object any more. But it is not true. Nutrition is still inherited from Object as Object is inherited by Activity.
Their relations are Nutrition: Activity: Object.
As Yoam Farges pointed it out, Realm doesn't support :
Casting between polymorphic classes (subclass->subclass,
subclass->parent, parent->subclass, etc.).
Which is what I was trying to do.
You guys are right when saying that my Inheritance is not an Inheritance, but as you can see in the Realm documentation it's how you achieve it.
Thanks to the informations I got in this github post I could achieved what I wanted and could keep a easy readability.
Use an option type for polymorphic relationships :
class PolyActivity: Object {
dynamic var nutrition: Nutrition? = nil
dynamic var sport: Sport? = nil
dynamic var id = ""
override static func primaryKey() -> String? {
return "id"
}
}
Create my main Object Activity
class Activity: Object {
dynamic var date = NSDate()
}
and have my Nutrition and Sport object inherited properly to Activity
class Nutrition: Activity {
dynamic var quantity = 0
}
class Sport: Activity {
dynamic var quantity = 0
dynamic var duration = 0
}
My Category object can now hold a List
class Categorie: Object {
let activities = List<PolyActivity>()
var categoryType: String = ""
override static func primaryKey() -> String? {
return "categoryType"
}
}
And this is how I create my Nutrition object :
let polyActivity = PolyActivity(value : [ "id": primaryKey ] )
poly.nutrition = Nutrition(value: [ "date": NSDate(), "quantity": 0, "duration": 0 ])
let category = Category(value: ["categoryType" : "0"])
category.activities.append(polyActivity)
And to retrieve just use Optional Binding :
if let nutrition = category.activities[0].nutrition { }
If you guys have a better, clearer, easier solution please go head !
I am trying to add a non-realm class object to realm object something like this.
class TrainTripItinerary: Object {
dynamic var departStationName: String?
dynamic var departStationCode: String?
var runningStatus: TrainRunningStatus?
override static func ignoredProperties() -> [String] {
return ["runningStatus"]
}
}
While TrainRunningStatus is not a realm class.
class TrainRunningStatus {
var trainDataFound: String?
var startDate: String?
var startDayDiff: String?
}
I am not able to update runningstatus property now. Anyone know how it works? I fetch separately runnningstatus and assign it to the realm object later but it stays nil even after the assignment.
eg.
let runningStatus = TrainRunningStatus()
trainTripItinerary.runningStatus = runningStatus
This line is not working, trainTripItinerary runningStatus property is not set properly its always nil.
As suggested in comments make sure you use the same instance of TrainTripItinerary because ignored properties won’t automatically update their value across different instances.
See an example code below that demonstrates how ignored properties work
let realm = try! Realm()
try! realm.write {
realm.deleteAll()
}
let runningStatus = TrainRunningStatus()
var trainTripItinerary = TrainTripItinerary()
trainTripItinerary.runningStatus = runningStatus
assert(trainTripItinerary.runningStatus != nil)
try! realm.write {
realm.add(trainTripItinerary);
}
assert(trainTripItinerary.runningStatus != nil)
trainTripItinerary = realm.objects(TrainTripItinerary.self).first!
assert(trainTripItinerary.runningStatus == nil)
Firstly, your code is not correct.
class TrainTripItinerary: Object {
dynamic var departStationName: String?
dynamic var departStationCode: String?
var runningStatus: TrainRunningStatus?
override static func ignoredProperties() -> [String] {
return ["runningStatus"]
}
}
func ignoredProperties() -> [String] is only used on Realm properties. Since your property var runningStatus: TrainRunningStatus? does not begin with dynamic, it is not a Realm property. You don't need to use func ignoredProperties() -> [String] here.
var runningStatus: TrainRunningStatus? here is called a "transient property" in Realm. Usually a transient property is something calculated basing on current date or on Realm properties, Realm won't do anything on transient properties and you should maintain them yourself.
So if you just want to use runningStatus as a transient property, you can simply remove the code override static func ignoredProperties() -> [String].