DS.Model, polymorphism, and multiple polymorphic associations


#1

So I find myself in the tricky situation of needing to add more than one of my polymorphic associations to a model. Below is an example of the code I have currently:

App.Image = DS.Model.extend({
  image: DS.attr('image'),
  imageable: DS.belongsTo('App.Imageable', { polymorphic: true })
});

App.Imageable = DS.Model.extend({
  images: DS.hasMany('App.Image') 
});

App.Task = DS.Model.extend({
  name: DS.attr('string'),
  taskable: DS.belongsTo('App.Taskable', { polymorphic: true })
});

App.Taskable = DS.Model.extend({
  tasks: DS.hasMany('App.Task')
});

Now I want to create a new model, App.ImageableTaskable, that has both tasks and images (i.e. extends both App.Taskable and App.Imageable). The only way I can think to do this is to have either App.Taskable extend App.Imageable directly or vice versa. Any creative solutions to this?


#3

But is it possible to create a mixin for polymorphic associations like so? I’ve tried and it gives me the following error:

Uncaught TypeError: Object [object Object] has no method 'eachRelatedType'

However, if I change the DS.belongsTo on the polymorphic models then everything works:

App.Image = DS.Model.extend({
  image: DS.attr('image'),
  imageable: DS.belongsTo('App.ImageableTaskable', { polymorphic: true })
});

App.Task = DS.Model.extend({
  name: DS.attr('string'),
  taskable: DS.belongsTo('App.ImageableTaskable', { polymorphic: true })
});

But this entirely defeats the purpose of having polymorphic types!


#4

As you are using a polymorphic association, I suspect that you have more classes defined that have many images. I haven’t been in that area of the code for a while, but you could define your associations in mixins in order to reuse them.

I’m not sure yet how you use the App.Imageable class. Do you actually have instances of that class?


#5

It hit me right after my last post, this is the best pattern for doing it that I can find:

App.ImageableMixin = Ember.Mixin.create({
  images: DS.hasMany('App.Image') 
});

App.TaskableMixin = Ember.Mixin.create({
  tasks: DS.hasMany('App.Task')
});

App.Imageable = DS.Model.extend(App.ImageableMixin);

App.Taskable = DS.Model.extend(App.TaskableMixin);

App.ImageableTaskable = DS.Model.extend(App.ImageableMixin, App.TaskableMixin);

EDIT: Actually that won’t work… same problem, unfortunately. I guess the only way to do it would be to bundle all polymorphic types together for right now.


#6

@cyril App.Imageable is the belongsTo type of App.Image, and every model that hasMany images would have to extend App.Imageable.


#7

yes, so the mixin approach is what you are looking for.


#8

@cyril Look at the way it’s setup. App.Image belongs to App.Imageable, which is a DS.Model and must be a DS.Model. I’ve tried to make App.ImageableMixin and it throws errors. Any model that extends App.Imageable can have images, but models that use App.ImageableMixin cannot have images because this is a namespace issue with ember-data.

I’m currently trying to figure out a way to hack the polymorphic associations so that they can look for mixin names as well.


#9

Can you detail a little bit more? How does it fail? What does the payload look like if you are retrieving data from a server?


#10

I wish I could describe it more without going into huge detail. Firstly, this is solely a problem with ember-data, the server is returning data in proper JSON format so that’s all good.

The problem with simply making App.Imageable a mixin is that DS.belongsTo expects the value you pass it (e.g. App.Imageable, see the code above) to be a DS.Model, not a mixin. So ember-data essentially explodes trying to call functions on an object that is a mixin rather than a model.

Strangely enough, with my fix above the model loads correctly but there are further association problems which prevent me from saving the model. Right now I’m trying to figure out the way ember-data resolves polymorphic association aliases to see if there’s a way I can add the functionality into my ember-data fixes. Ideally we would be able to just use a mixin, but I don’t think it will be that simple. I think that making modifications to the way ember creates aliases is the way to go. Going to post my solution when I have it working.


#11

I’m not a 100% sure that this can not work in this case. The type declared for a polymorphic belongsTo isn’t instantiated. The real type is passed in the payload. So it should not matter that it is a mixin.

It’s hard to say without an example to see which error is triggered.


#12

Sorry to revive a dead thread, but this is the only discussion I’ve seen about this subject.

I’m trying to use modules for polymorphic types, this is my hierarchy:

Asset = DS.Model.extend({ pic: DS.belongsTo("HasAsset", {polymorphic: true}) });
HasAsset = Ember.Mixin.create();
ChatterPic = DS.Model.extend(HasAsset, {});

When I try to do

this.store.createRecord("asset", { pic: chatterPic });

I just get Expecting a function in instanceof check, but got [object Object] from the instanceof check that happens here: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/relationships/belongs_to.js#L15

It’s basically executing chatterPic instanceof HasAsset, which fails because HasAsset is not a class / can’t play the role of one in an instanceof check I guess.


#13

I think this is the same “inherit chain” problem that I have run into and mentioned here Questions about ember-data 0.13->1.0beta upgrade

Mixins were a no-go because of the instanceOf checks. I haven’t found an alternative solution yet but am interested in finding one!


#14

I’ve run into this as well. In my case, a User acts as Commentable and Ownable (as in a User can be the owner of many Activites). For both to work proeprly, User has to somehow extend both Commentable and Ownable.

So it seems like a model can only act as one polymorphic relationship at a single time, if I understand correctly. Is there a way to work around this?


#15

My current solution to this issue is to make all of the polymorphic models as mixins, then combine them as one in a DS.Object (I call mine Morphable). All polymorphic classes extend Morphable. This will make it easier to use the polymorphic models as they should be, purely mixins, in the future. That said, it is not an ideal solution.

I have not looked at Ember Data’s source in some time, but I will try to find a solution to this is the near future.


#16

Can you go into more detail in how this works? I’m a bit new to ember-data and have run into the same issues.


#17

Sure, here is an example of how I do polymorphism currently:

App.Imageable = Ember.Mixin.create({
  galleryImages: DS.hasMany('galleryImage'),
});

App.Taskable = Ember.Mixin.create({
  tasks: DS.hasMany('task'),
});

App.Noteable = Ember.Mixin.create({
  notes: DS.hasMany('note')
});

App.EmployeeRelatable = Ember.Mixin.create({
  employeeRelations: DS.hasMany('employeeRelation')
});

App.Morphable = DS.Model.extend(App.Imageable, App.Taskable, App.Noteable, App.EmployeeRelatable);

My models then extend App.Morphable and have all of the hasManys from the mixins. Unfortunately, this means that anything extending App.Morphable necessarily has all of the hasManys; you will need to create a new model if you want a different combination.


#18

Is there a reason you aren’t applying the mixins to the appropriate models rather than App.Morphable?


#19

I had this problem myself (my use case was needing to model tree data) and just got around to submitting a pull request with support for mixins as abstract models:

It’s PR 1845 for ember-data. discuss isn’t allowing me to add links because: "Sorry, new users can only put 2 links in a post." Whatever that means.


#20

Right now emberdata is cheking the polymorphic association with "instanceOf’. So that the check fails when the belongsTo relation is serialized if you declare something like this:

App.Relationable = Em.Mixin.create({child: DS.belongsTo('child')})
App.Mymodel = DS.Model.extend( App.Relationable, {someAttr: DS.attr()})

I had the same issue with polymorphic association and ended up with the same solution of @pzuraq. Create real DS.Model combinations of mixins in order to use them as end points for the relations.


#21

In our project using Ember 2.4, we have few entities, Task, Assignment and Tag. Task are taggable and **** through polymorphic associations.

This is our models structure:

// app/models/task.js
import DS from 'ember-data';
import Taggable from 'app/mixins/taggable';
import Assignable from 'app/mixins/assignable';

export default DS.Model.extend(Taggable, Assignable, {  

});

// app/models/tag.js
import DS from 'ember-data';

export default DS.Model.extend({
  taggable: DS.belongsTo('taggable', { polymorphic: true }),
});

// app/models/assignment.js
import DS from 'ember-data';

export default DS.Model.extend({
  assignable: DS.belongsTo('assignable', { polymorphic: true }),
});


// app/mixins/taggable.js
import Ember from 'ember';
import DS from 'ember-data';

export default Ember.Mixin.create({
  tag: DS.belongsTo('tag'), // you can go with hasMany here, we only have one-to-one association
});


// app/mixins/assignable.js
import Ember from 'ember';
import DS from 'ember-data';

export default Ember.Mixin.create({
  assignment: DS.belongsTo('assignment'), // you can go with hasMany here, we only have one-to-one association
});