realm / realm-java

Realm is a mobile database: a replacement for SQLite & ORMs
http://realm.io
Apache License 2.0
11.47k stars 1.75k forks source link

Inheritance / Polymorphism #761

Open cmelchior opened 9 years ago

cmelchior commented 9 years ago

A highly request feature is polymorphism. Mostly for use in ListAdapters, but a lot of other scenarios exists.

We need support for the following:

Example

public abstract Animal extends RealmObject {
  private String name;
}

public class Dog extends Animal {
  private boolean willPlayCatch;
}

public class Spider extends Animal {
  private boolean isScary;
}

// Following should be possible
RealmResults<Animal> animals = realm.where(Animal.class).equalsTo("name","Foo").findAll();
String name = animals.get(0).getName();

RealmResults<Dog> dogs = realm.where(Dog.class).equalsTo("name","Foo").and().equalsTo("willPlayCatch", true).findAll();
String name = dogs.get(0).getName();
jason-kilic commented 8 years ago

+1

henriquedesousa commented 8 years ago

We've tried composition, but introduced way more problems. Now, instead of developing, we're lots of time hacking the models to fit our bussiness rules. Mangle it with retrofit, deserializers, and all hell broke loose!

valokafor commented 8 years ago

This is super painful, I cannot forgive myself for making a huge assumption that Realm supports inheritance. I have a project with a fast-approaching deadline and I had ripped out the good old SQLite/ContentProvider combo and implemented Realm for faster performance and now I hit the wall with Inheritance and composition is not an option for me. I will keep tuned in for this feature.

Overall, I am very happy with all that Realm has offered and I wish the team luck with finding a solution for this.

akiander commented 8 years ago

I'm trying to use Realm and I just ran into this limitation. Is there a timeline for support objects that already have their own inheritance? Composition is not an option for us right now. Is there a timeline for this yet?

yuriy-larin commented 8 years ago

+1 - I vote for it. More then a year has passed. BTW it's a middle of 2016!

schatekar commented 8 years ago

I had just started migrating from ActiveAndroid to realm and realised that subclassing is still not supported. I think I will have to go back to looking for another ORM.

yuriy-larin commented 8 years ago

I also switched back to SQLite for now as I need to work with database from Service and Activity code. I found it hard to use realm in such 'multi-threaded' environment.

bryant1410 commented 8 years ago

I can recommend DbFlow, which supports polymorphism

schatekar commented 8 years ago

@bryant1410 Had a quick look at DbFlow. I did not like the code it auto-generates. This is totally a personal preference though. why you think DbFlow is better than something like ActiveAndroid?

bryant1410 commented 8 years ago

Do you mean that you don't like its API? Or the code itself? If you don't like the auto-generated code, this is not a problem as you don't have to read it very often (if at all).

In my opinion the game-changing factor here is the performance. ActiveAndroid relies heavily on reflection, while DBFlow uses annotation processing in order to generate classes and to avoid using reflection as little as possible. Reflection is usually slow in Java, and not using it has a big impact. You can take a look to DBFlow's company benchmark about this.

oliveeyay commented 8 years ago

+1 - If you could find a solution for the inheritance to work, that would be awesome! :)

paulkagiri commented 8 years ago

I have used Realm for quite a while but lack of support of this fundamental OOP characteristic is quite disappointing, a year and a half is quite a long time to have built the required support.

leonapse commented 8 years ago

Just ran into this problem. It would be very great if this feature gets prioritized. Gonna have to abandon Realm for now and use other ones. Updates from devs would definitely be appreciated! <O

lordplagus02 commented 8 years ago

This issue means I can't attach any JSONAPI deserializers like moshi-jsonapi in Android (which requires that you extend your class with Resource, which means you can't extend RealmObject or vice versa. Anybody have a temporary workaround for something like this?

beeender commented 8 years ago

@lordplagus02 See https://realm.io/docs/java/latest/#realmmodel-interface

lordplagus02 commented 8 years ago

I think you've just saved my day, what with deadlines fast approaching and solutions undiscovered... you know how it is

lordplagus02 commented 8 years ago

@beeender It appears that it still doesn't actually help, you still can't extend or implement anything that isn't Realm related...

beeender commented 8 years ago

@lordplagus02 Sorry I missed that part "moshi-jsonapi in Android (which requires that you extend your class with Resource" ... I think for now you need to find some other ways if you want to use moshi-jsonapi and Realm together, maybe create some delegate class? Not sure if there is an easy way to do it.

lordplagus02 commented 8 years ago

@beeender easiest way was to revert to moshi-jsonapi v1.x because it doesn't require that you extend Resource. I think due to the current limitations of Realm, we're still awhile away from a full jsonapi/realm/syncadapter implementation.

Zhuinden commented 8 years ago

Looking at it, a possibility is forking the project and rewriting it to use interface instead of hard-coding their abstract class

navjotbedi commented 8 years ago

+1

renaudfavier commented 8 years ago

Hello,

I'm trying to reshape my code and models to workaround this issue through composition.


enum ChildType {
    CAT, DOG
}

class Animal extends RealmObject implements CatInterface, DogInterface {

    @PrimaryKey
    @Required
    private String id;

    @Ignore
    private ChildType type;
    private String typeDescription;

    private ChildMarker child;

    public Animal() {
    }

    public Animal(ChildMarker childMarker) {
        if (childMarker instanceof Cat) {
            type = ChildType.CAT;
        } else if (childMarker instanceof Dog) {
            type = ChildType.DOG;
        }
    }

    public ChildType getType() {
        if (type == null) {
            type = ChildType.valueOf(typeDescription);
        }
        return type;
    }

    public void setType(ChildType type) {
        this.type = type;
        this.typeDescription = type.name();
    }

    @Override
    public Object getCatAttribute() {
        if (type != ChildType.CAT) {
            return null;
        }
        return ((Cat) child).getCatAttribute();
    }

    @Override
    public void setCatAttribute(Object catAttribute) {
        if (type != ChildType.CAT) {
            return;
        }
        ((Cat) child).setCatAttribute(catAttribute);
    }

}

interface ChildMarker {
}

interface CatInterface extends ChildMarker {
    Object getCatAttribute();

    void setCatAttribute(Object catAttribute);
}

class Cat extends RealmObject implements CatInterface {

    private Object catAttribute;

    @Override
    public Object getCatAttribute() {
        return catAttribute;
    }

    @Override
    public void setCatAttribute(Object catAttribute) {
        this.catAttribute = catAttribute;
    }
}

interface DogInterface extends ChildMarker {

}

class Dog extends RealmObject implements DogInterface {

}

But my ChildMarker is not supported, Does it mean I have to have one attribute of each child type in my Animal class ? I could also do my composition the other way around, with children containig their mother. But I'd like only one table, I don't know in advance if I'm looking for a dog or a cat and since reverse lookup is in development (#607) I'll have to wait for it.

It would be great if we could have a working code alternative in this topic

Zhuinden commented 8 years ago
enum ChildType {
    CAT, DOG
}

class Animal extends RealmObject implements CatInterface, DogInterface {
    // animal attributes
    @PrimaryKey
    @Required
    private String id;

    @Required
    @Index
    private String type;

    public String getType() {
         return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public void setChildType(ChildType childType) {
        setType(childType.name());
    }

    public ChildType getChildType() {
        return ChildType.valueOf(this.type);
    }

    public Animal() {
    }

    // cat attributes
    private String catSomething;
        // getter, setter

    // dog attributes
    private String dogSomething;
        // getter, setter
}

interface AnimalInterface {
    //animal's fields' getters/setters
}

interface CatInterface extends AnimalInterface {
    String getCatSomething();

    void setCatSomething(String catSomething);
}

interface DogInterface extends AnimalInterface  {
    String getDogSomething();

    void setDogSomething(String dogSomething);
}

Then

    RealmResults<CatInterface> cats = 
                         (RealmResults<CatInterface>)((RealmResults)(realm.where(Animal.class)
                                                         .equalTo(AnimalFields.TYPE, ChildType.CAT.name())
                                                         .findAll()));

But the interfaces aren't necessary if you can afford to keep it as Animal.

renaudfavier commented 8 years ago

@Zhuinden Nice ! thanks it looks like what I did but better

lordplagus02 commented 8 years ago

Can I bump this? Any update on this feature?

lazluiz commented 8 years ago

I was just modifying my Realm classes to work with Polymorphism but then I faced this issue ):

I wanted to make General functions to work with my "BaseObject (which extends RealmObject)" so I could use them with any Child classs.

Zhuinden commented 8 years ago

Only interfaces are supported at the moment, base classes other than RealmObject are not.

bogomazov commented 8 years ago

I assumed that inheritance is here by default and refactored my code around it without compiling in the process (yes, I am naive but if the support won't be added I am gonna have to have lots of code duplications around primary keys for each model, what the point of using Realm then?) Waiting for an update

Zhuinden commented 8 years ago

@bogomazov

I am gonna have to have lots of code duplications around primary keys for each model

you can always use an interface, and a repository implementation for T extends YourInterface.

lordplagus02 commented 8 years ago

That's what I did, but it causes for quite a bit of un-dry code in your object classes. The interface is just useful for your getters and setters at the end of the day. It desperately needs some polymorphism. On 4 Nov 2016 12:18 am, Gabor Varadi notifications@github.com wrote:

@bogomazov you can always use an interface, and a repository implementation for T extends YourInterface.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

valokafor commented 8 years ago

@lordplagus02 and @bogomazov can you show me an example of the Repository implementation that you got working for you. I am using Repository implementation, maybe I am doing it wrong.

My model class was simple, a LineItem that inherits from a Product like so:

public class LineItem extends Product {
    private long id;
    private int quantity;

    public LineItem(Product product, int quantity) {
        super(product);
        this.quantity = quantity;        
    }
}

I have now changed this to this as a work around.

@RealmClass
public class LineItem implements RealmModel {
    private long id;
    private int quantity;
    private long productId;
    private Product product;

    public LineItem() {
    }

    public LineItem(Product product, int quantity) {
        this.quantity = quantity;
        this.productId = product.getId();
        this.product = product;
    }
}

Here is my repository interface, how can I change my repository implementation to support polymorphism.

 public interface Repository{
        List<LineItem> getAllLineItemsInATransaction(long transactionId);
        long saveLineItem(LineItem lineItem);
        void updateLineItem(LineItem lineItem);
        LineItem getLineItemById(long id);
        void deleteLineItem(long id);
    }
Zhuinden commented 8 years ago

I do not see a single place where you're actually sharing fields with Product.

valokafor commented 8 years ago

LineItem is inheriting from Product and then adding properties that are LineItem specific such as quantity, discount, refund, etc. I have not shown those for brevity. The goal is not to duplicate the properties of Product such as name, image, price, etc again in LineItem hence the inheritance.

ozgurkaragoz commented 8 years ago

+1

lordplagus02 commented 7 years ago

sigh this is messing with my zen

bubakazouba commented 7 years ago

Wow 2 years

Zhuinden commented 7 years ago

it is a complicated problem, and using common interfaces is a fairly simple workaround

chantellosejo commented 7 years ago

Can someone help with this? http://stackoverflow.com/questions/42259735/composition-over-inheritance-for-realmobjects-with-gson-serialization

I'm totally stuck. This is incredibly frustrating and no one seems to have posted a thorough example of how to work around this issue, and if I can't figure out a workaround, then Realm isn't even an option. I'm astonished they haven't addressed inheritance in two years; I get that development takes time, but inheritance is one of the basic principles of OOP. How can anyone use this if their app has even a modicum of complexity?

Zhuinden commented 7 years ago

You should map your JSON responses into a Realm schema. Don't just directly map GSON's classes into Realm, it's generally a bad idea to assume that your API calls and your database will 1-to-1 match each other directly.

I've made apps that had different types of data and the like, you just need to copy the properties and share a common interface. Frustrating in a sense, inheriting fields is simpler ; but that's just how it is.

wingu-wiktor commented 7 years ago

Why don't you state clearly in RealmModel interface that class implementing it cannot extend any class? Documentation in current form leads to opposite conclusion.

Zhuinden commented 7 years ago

I really do think the fact that you can't inherit from a class that only has @Ignore'd fields or no fields at all is a bug.

beeender commented 7 years ago

@Zhuinden But if an object only has @Ignore fields or no fields, what is the point of being a RealmObject?

Zhuinden commented 7 years ago

@beeender example code

public class BaseObservable implements Observable {
    @Ignore
    private transient PropertyChangeRegistry mCallbacks;

    @Override
    public synchronized void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
        if (mCallbacks == null) {
            mCallbacks = new PropertyChangeRegistry();
        }
        mCallbacks.add(callback);
    }

    @Override
    public synchronized void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
        if (mCallbacks != null) {
            mCallbacks.remove(callback);
        }
    }

    @Override
    public synchronized void notifyChange() {
        if (mCallbacks != null) {
            mCallbacks.notifyCallbacks(this, 0, null);
        }
    }

    public void notifyPropertyChanged(int fieldId) {
        if (mCallbacks != null) {
            mCallbacks.notifyCallbacks(this, fieldId, null);
        }
    }
}

@RealmClass
public class ObservableDog extends BaseObservable implements RealmModel {
    @PrimaryKey
    private long id;

    private String text;

    @Bindable
    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
        if(!isManaged()) {
            notifyPropertyChanged(BR.id);
        }
    }

    @Bindable
    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
        if(!isManaged()) {
            notifyPropertyChanged(BR.text);
        }
    }
}

This code fails on the Realm annotation processor, even though parent is not a RealmObject and it does not have any inherited fields that Realm could "use"

srinivas3120 commented 7 years ago

any update on this issue ?

VedavyasBhat commented 7 years ago

Any update on this issue?

I have models that look like this:

class Chat extends RealmObject {
    RealmList<TextMessage> textMessages;
    RealmList<ImageMessage> imageMessages;
}
class Message extends RealmObject {
    //some common fields
}
class TextMessage extends RealmObject {
    Message baseMessage;
}
class ImageMessage extends RealmObject {
    Message baseMessage;
}

instead of looking something like this:

class Chat extends RealmObject {
    RealmList<Message> messages
}
class Message extends RealmObject {
    //some common fields
}
class TextMessage extends Message {}
class ImageMessage extends Message {}

and it's a nightmare to add more message types or even write queries.

Zhuinden commented 7 years ago

I do not see why you have any more realmObjects beyond Message

VedavyasBhat commented 7 years ago

@Zhuinden Because I have more fields in TextMessage and ImageMessage, which I haven't included here for the sake of readability.

Zhuinden commented 7 years ago

@VedavyasBhat I stand by my question. It would have been easier to just include nullable fields and a @Index String type field.

sluedecke commented 7 years ago

@Zhuinden: I see what you have in mind, but I have the same issue as @VedavyasBhat: I want to be able to attach Actions and Observations to an object, both being an event in time with some shared attributes. But since both have >10 unique attributes I would end up with a large class Event which I consider hard to maintain.

Also I am afraid that I will loose productivity when using code completion and that it will add a source for errors (accessing fields from the wrong type, ...).

But it is a feasible workaround, factory methods will help constructing the right type.

Zhuinden commented 7 years ago

When I had this, I prefixed the fields with what event type (in my case, post type) the given field belonged to.