3

I have a model, Node:

App.Node = DS.Model.extend({
    parents: DS.hasMany('node', { inverse: null })
});

Let's say my server is sending back a partial tree:

{ id: "A", parents: [ "C" ] }
{ id: "B", parents: [ "C", "D" ] }
{ id: "C", parents: [ "E" ] }

I want to render this like so:

A ---- C
      /
B ---/

However, when I call B.get('parents'), I get a message stating:

Uncaught Error: Assertion Failed: You looked up the 'parents' relationship on a 'node' with id B but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (DS.hasMany({ async: true }))

Neither of those are desirable to me. I want just the loaded records in the graph.

I may eventually want to render something like this as well:

A ---- C--?
      /
B ---/--?

Representing unimportant parents with a UI element.

Is there a way to peek only loaded records in a relationship?

John Gietzen
  • 48,783
  • 32
  • 145
  • 190

3 Answers3

3

Great question.

Currently, Ember Data falls short on such use cases.

There was an answer to your question but it's obsolete now: the API has changed and it's now private.

If you're brave enough, you can do something like this:

App.Node = DS.Model.extend({
  parent:   DS.belongsTo('node', {inverse: 'children'}),
  children: DS.hasMany('node', {inverse: 'parent'}),

  childrenIds: Ember.computed(
    '_internalModel._relationships.initializedRelationships.children.canonicalState.@each.ids',
    function() {
      var children =
        this
          ._internalModel
          ._relationships
          .initializedRelationships
          .children;

      if (!children) return [];

      return children
        .canonicalState
        .mapBy('id');
    }
  ),

  availableChildren: Ember.computed('childrenIds', function () {
    var childrenIds = this.get('childrenIds');
    return this.store.all('node').filter(function(node) {
      return childrenIds.indexOf(node.id) > -1;
    });
  })
});

Demo: http://emberjs.jsbin.com/qugofu/1/edit?html,js,output

The problem with this code (apart from using private API) is that the availableChildren property will not auto-update when new nodes are fetched from the server.

You'll have to figure out a way to do it. When you do, release it as an addon!

UPD 2015-07-20

This question wasn't letting me sleep.

I've managed to improve the above solution:

  1. It now automatically updates when new records appear in the store.
  2. It also updates when relationships of records change.

The downside is that the lookup for available nodes happens in the component and not in the model.

Demo: http://emberjs.jsbin.com/qugofu/2/edit?html,js,output

Community
  • 1
  • 1
Andrey Mikhaylov - lolmaus
  • 23,107
  • 6
  • 84
  • 133
2

As another answer correctly states, Ember Data has a hard time dealing with this. A related situation is trying to find the length (count) of an async hasMany relationship without raising network requests. The problem is that the parents property is essentially a promisified array, which has to be a promise for something.

Unless you want to go spelunking into Ember Data internals, I would handle this problem by giving up on hasMany and simply defining the field as an array of IDs. Then, when you really need the parent objects, do a find to get them. You can use coalesceFindRequests in the adapter to ensure that Ember makes only one network call to get both parents (if your server can handle the api/nodes?ids[]=A&ids[]=B format).

If you are so inclined, you could encapsulate this behavior into mixins on serializers and models.

1

Currently (2016-07-03) this can be accomplished with DS.Model's hasMany() method:

var children = model.hasMany('children').value();
Xaser
  • 168
  • 1
  • 13
Gabriel Grant
  • 5,415
  • 2
  • 32
  • 40
  • For posterity, this requires Ember-Data 2.5.0, but works like a charm for those up-to-date. – Xaser Sep 29 '16 at 16:17