-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Abstract Class / Polymorphism Support #1109
Comments
Hi @Kirow, there's definitely value in your modeling approach, but Realm's current inheritance implementation doesn't allow for the polymorphism you're looking for. Your first goal would be fairly easy to support (avoiding creating unused tables in the db). However, empty tables in Realm are very small so even though this is annoying, it shouldn't have any significant performance or usability impact. If you're keen on filtering which tables are created in Realm, you could patch The second goal highlights a more general sore point in Realm's architecture, which is that containers for a class ( So for the time being, you'll have to adapt your model to fit with Realm's inheritance approach. Meanwhile, we'll keep an eye on possible modeling improvements for Realm and we'll make sure to update this thread when we have anything to share. |
I am having a similar issue to @Kirow's, I have an RLMArray defined to store an abstract class, and storing there subclasses of the abstract class, but of course is giving me back instances of the abstract class. It will be really awesome if Realm supports this in the future. |
realm/realm-java#761 is related issue |
@jpsim So what kind of inheritance/polymorphism is available in Realm at this time? From what I can see, with no polymorphism in querying, nor in relationships, there's basically no inheritance supported. |
Sorry for the late reply. Inheritance in Realm at the moment gets you:
It does not get you:
We're 100% behind adding this functionality in Realm, but as you can tell from the labels on this GH issue, it is neither a high priority for us at the moment, or easy to do. Some underlying architecture work is needed to move forward with these additional inheritance-related features. In the meantime, you can work around these inheritance limitations in a number of ways: 1. Running queries on all related types and mapping back to arraysclass A: Object {
dynamic var intProp = 0
}
class B: A {}
class C: A {}
extension Realm {
func filter<ParentType: Object>(parentType parentType: ParentType.Type, subclasses: [ParentType.Type], predicate: NSPredicate) -> [ParentType] {
return ([parentType] + subclasses).flatMap { classType in
return Array(self.objects(classType).filter(predicate))
}
}
// Usage
let realm = try! Realm()
let allAClassesGreaterThanZero = realm.filter(A.self, [B.self, C.self], NSPredicate(format: "intProp > 0")) // => [A]
2. Using an option type for polymorphic relationshipsclass A: Object {
dynamic var intProp = 0
}
class B: A {}
class C: A {}
class AClasses: Object {
dynamic var a: A? = nil
dynamic var b: B? = nil
dynamic var c: C? = nil
}
class D: Object {
dynamic var polymorphicA: AClasses? = nil
}
// D's polymorphicA value can hold a wrapped A, B or C object 3. Initializing objects with their polymorphic counterpartsInstead of casting, you can copy the underlying values from one object to another if they share those properties: class A: Object {
dynamic var intProp = 0
}
class B: A {}
// Usage
let a = A(value: [42])
let b = B(value: a) |
4. Alternative: Using Composition instead of InheritanceInstead of using inheritance, you can avoid it and it's current limitations with Realm in some cases at all by composing your classes via linked objects. class Animal: Object {
dynamic var age = 0
}
class Duck : Object {
dynamic var animal: Animal? = nil
dynamic var name = ""
}
class Frog : Object {
dynamic var animal: Animal? = nil
dynamic var dateProp = NSDate()
}
// Usage
let duck = Duck(value: [ "animal": [ "age": 3 ], "name": "Gustav" ]) If you want to share behavior between multiple classes, you can e.g. facilitate Swift's default implementations of protocols: protocol DuckType {
dynamic var animal: Animal? { get }
func quak() -> ()
}
extension DuckType {
func quak() {
for _ in 1...(animal?.age ?? 1) {
print("quak")
}
}
}
extension Duck: DuckType {}
extension Frog: DuckType {}
// both can quak now |
@mrackwitz Let me ask you about |
@wanbok: You would need to manually take over the value of the |
Is there any update on the status of this issue, or current recommendations for best practices? The Realm Swift documentation's section on model inheritance says that this feature is "on the roadmap" and links to this issue. |
6 years is a long time to be "on the roadmap", maybe it's time to get this one done? |
7 years and counting. |
@JadenGeller since it doesn't seem like Realm is going to improve anything here, it would be nice if you updated your type-erased wrapper example. It's 7 years out of date and throws all kinds of compiler errors now. This GitHub issue seems to be THE canonical example of how to make Realm do this, even though it's not part of the official docs. Additionally, it doesn't look like it'll work with the new
And that's crashing in AnyPaymentMethod's To get around that, I did this:
That runs just fine, but I don't know if using |
Hi @bdkjones we still have class PaymentMethod: Object {
@Persisted var owner: String
}
class CreditCardPaymentMethod: PaymentMethod {
@Persisted(primaryKey: true) var id: String = ObjectId.generate().stringValue
@Persisted var cardNumber: String = ""
@Persisted var csv: String = ""
}
class PaypalPaymentMethod: PaymentMethod {
@Persisted(primaryKey: true) var id: String = ObjectId.generate().stringValue
@Persisted var username: String = ""
@Persisted var password: String = ""
}
class Purchase: Object {
@Persisted(primaryKey: true) var id: ObjectId = ObjectId.generate()
@Persisted var product: String = ""
@Persisted var price: Int = 0
@Persisted var paymentMethod: AnyPaymentMethod?
}
class AnyPaymentMethod: Object {
@Persisted(primaryKey: true) var primaryKey: String = ""
@Persisted var typeName: String = ""
// A list of all subclasses that this wrapper can store
static let supportedClasses: [PaymentMethod.Type] = [
CreditCardPaymentMethod.self,
PaypalPaymentMethod.self
]
// Construct the type-erased payment method from any supported subclass
convenience init(_ paymentMethod: PaymentMethod) {
self.init()
typeName = String(describing: type(of: paymentMethod))
guard let primaryKeyName = type(of: paymentMethod).sharedSchema()?.primaryKeyProperty?.name else {
fatalError("`\(typeName)` does not define a primary key")
}
guard let primaryKeyValue = paymentMethod.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 : PaymentMethod.Type] = {
var dict: [String : PaymentMethod.Type] = [:]
for method in supportedClasses {
dict[String(describing: method)] = method
}
return dict
}()
// Use to access the *actual* PaymentMethod value, using `as` to upcast
var value: PaymentMethod {
guard let type = AnyPaymentMethod.methodLookup[typeName] else {
fatalError("Unknown payment method `\(typeName)`")
}
guard let value = try! Realm().object(ofType: type, forPrimaryKey: primaryKey) else {
fatalError("`\(typeName)` with primary key `\(primaryKey)` does not exist")
}
return value
}
} and can be used the same as proposed. We will post on this issue any update regarding |
@dianaafanador3 Thanks for updating the workaround example. I DO understand that this is a large feature, but 8 years is a very long time and I don't think realm realizes just how long issues like this have been open. Plus, polymorphism isn't some newfangled, fringe idea. It has existed for the better part of a century precisely because it's incredibly useful. And sure, the magic vomit in the example above is an ugly workaround, but when folks see issues like this go unaddressed for a decade, the thing they should be asking is: "Do I really want to tie myself to Realm?" |
Hi Product for Realm here - we realize how long this issue has been open, although perhaps that is a testament to just how long Realm has been a product and how many evolutions we've needed to go through over the years to get to this age. I will say that we do plan to do this because it would give us an incredibly unique position in the market, which, I would observe, there are no other cross-platform mobile databases that have Polymorphism/ Inheritance along with a backend database syncing solution, I believe this is evidence to how technically complex this feature is. We need to get this feature right across all of our SDKs + Sync + MongoDB. |
@ianpward Thanks for the comment. I think it would be very helpful/reassuring if Realm had an official roadmap and timetable for major features like this. Eight years of "it's on the to-do list" comments really make me nervous about tying apps to Realm. |
@bdkjones CC: @ianpward EF Core's roadmap is of particular influence. They've done a fantastic job of setting out the goals for the year ahead, putting out releases across the whole year, and calling out the things that won't make the cut for that year and re-prioritizing for the next major cycle. I think Realm could benefit a lot from this strategy, and the entire feedback loop and trust-building that it creates. |
9 years and counting... @dianaafanador3 any updates on this? |
I think either I use Realm wrong or the suggested workaround here is pretty terrible. So please point me to the right direction if I use this tool wrong. I use SwiftUI and a FlexibleSync config with
Why? So I can either mess up my data modelling by adding redundant data to the children that I use for filtering, or sync on all data in each child's table (which is unnecessary, bad idea in scenarios when data transmission is expensive, and could even have security concerns - loading stuff into the local db and memory that do not belong to the user. e.g. I load every Another flaw of this is that in the example we just create a new If this is what is expected me to have polymorphism, then that's pretty bad and I cannot understand how this project could survive now almost 10 years with this ticket open. EDIT: Sooo messed up! |
Being just a little bit silly, should we plan a 10-year-old party for this request? OK, OK...we are all in software development, and we know that there is a prioritization process for requirements--and it considers ROI, so difficult problems may not make the cut for a release. I'm relatively new to using Realm and, in adding features to my application, I decided to refactor it to use subclasses that I want to store in Realm...thinking certainly that would be supported, no? After all, it's supported in CoreData. But, after a bit of refactoring and testing, I realized I should have researched that more in advance. Thus, it seems to me that we need to make application architecture decisions based on what is available now (or maybe on a published roadmap), so I'm now rethinking my decision to use Realm at all! |
welp, now they are deprecating Realm altogether :D |
@davidgodzsak Thanks for the heads up. To be fair, it sounds like the on-device SDKs will continue to exist. Just Realm Sync is deprecated and will be removed (which I never used, so that is a bit of complexity that can be deleted!) Maybe as open-source, we can get some better sync APIs that work with more technologies now! |
@jadar @davidgodzsak any link on Realm being deprecated? |
Realm Sync was the ENTIRE reason we chose Realm. Without the sync component, everyone is better served using Swift Data, the first-party solution that doesn't get suddenly terminated because some idiot MBA ran a spreadsheet. |
SwiftData is a joke compared to Realm and even CoreData which actually wraps |
@rursache You know what Core Data does NOT do? Randomly get nuked from orbit and cancelled. Call me crazy, but "continues to exist" is a pretty good feature for a product to have. Realm is dead and irrelevant as of today. Zero reason to use it. |
Wow that is a pretty epic fail. We chose to omit Realm Sync and built our entire sync layer by hand (both client-side and server-side) as to be able to have complete control over its many intricacies. Bullet dodged on that one! NOTE: We’ve since moved to GRDB over Realm as well due to the insane immutable-per-thread architecture Realm was built on, so wouldn’t have been an issue there either. But in both cases, the right answer ended up to go lower-level and not trust the black-box abstractions. At this point our ORM does the least possible thing - fetch records, not even relations between them, and we do all the object stitching ourselves. I wish it wasn’t this case, but reality just forced us otherwise. |
Did you notice any performance or memory usage impact with GRDB? |
Does it possible to create smth like this:
Blue - abstract classes. Such model will help me to incapsulate some logic and prevent code duplication.
In this example RLMItem can be associated with RLMCategory or/and RLMFolder. It would be very useful if i will be able to get all RLMFolders and RLMItems using [RLMCatItem allObjects].
Seems that it is not possible.
I've tried to make smth similar with subclasses, but as result - I have additional useless classes in schema.
This works well with CoreData, but can I expect smth like this in Realm?
_In other words:_
The text was updated successfully, but these errors were encountered: