I have an issue about Inheritance with my Objects in Realm.
Could you please have a look a it. I have :
Activity
Sport  which I want to be a subclass of Activity
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")
    }
}
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With