2

Just say I have a relationship like so:

App.Library = DS.Model.extend
    authors = DS.hasMany(App.Author, {async: true})

App.Author = DS.Model.extend
    books: DS.hasMany(App.Book, {async: true)

App.Book = DS.Model.extend()

and I want to get all the books from all the authors in my library. I could do a simple flatten style operation if these were not Promises

How do I do similar when both relationships are async.

Here is what I have come up with

allBooks: Ember.computed('authors', function() {
      var r = []
      this.get('authors').then(function(authors) {
          authors.forEach(function(author) {                  
              author.get('books').then(function(books) {
                  books.forEach(function(book){
                      r.push.apply(r, book);
                  });
              });
            });
      });

    return r;
}

Update:

I should have stated this earlier, but I actually did have this working with the following code. But now that ArrayController is deprecated I am looking for other ways to achieve this same result.

 allBooks: Ember.computed('authors', function() {
      var allBooks  = Ember.ArrayController.create();
      this.get('authors').then(function(authors) {
          authors.forEach(function(author) {                  
              author.get('books').then(function(books) {
                  books.forEach(function(book){
                      allBooks.addObject(book);
                  });
              });
            });
      });

    return allBooks;
}

I suppose I could do something like this is there another way that is better?:

books: Ember.computed('authors', function() {
      var a  = Ember.Object.create({
          content: Ember.A()
      });
      this.get('authors').then(function(authors) {
          authors.forEach(function(author) {
              author.get('books').then(function(books) {
                  books.forEach(function(book){
                      a.get('content').addObject(book);
                  });
              });
            });
      });

    return a.get('content');
}),
abarax
  • 6,229
  • 8
  • 28
  • 30
  • 1
    [Emberjs - Computed Properties and Promises…..don’t](http://www.thesoftwaresimpleton.com/blog/2015/06/10/cps-promise/) (That being said, your attempt is the ever so popular ["How do I return a value from an asynchronous function?"](http://stackoverflow.com/q/14220321/18771) and implies that you miss a basic piece about how asynchronicity works.) – Tomalak Jul 16 '15 at 04:46
  • Check out the update. – abarax Jul 16 '15 at 05:18
  • The blog post I linked to makes a pretty solid case against using promises in Ember.computed properties at all. – Tomalak Jul 16 '15 at 05:26
  • I don't see how. The author has a very different use case that does not use Ember data and his comments about multiple computed property triggers/partially resolved promises do not occur in my case. He also clearly doesn't understand promises because if he did his use case would be solved by simply returning a promise. – abarax Jul 16 '15 at 05:58
  • Mh, I'd say that's a minor detail. Ember data or any other asynchronous process, it doesn't matter. In the code sample and paragraphs following *"When I first encountered this problem,"* he proposes a solution (essentially [this one](http://stackoverflow.com/a/20623551/18771)) and explains why that still isn't a good idea. – Tomalak Jul 16 '15 at 06:05
  • I see what he is doing and he states several times that there are problems but doesn't go in to the specifics. None of the problems he mentions actually occur in my example. He does however go on to implement basically what I have above however the Ember.A is attached to a component and is not a computed property. The issue with this is that when changes occur to the dependent objects, the Ember.A does not get updated. – abarax Jul 16 '15 at 06:15
  • Thanks for the help by the way. – abarax Jul 16 '15 at 06:22
  • I don't have the feeling I'm helping very much. :) – Tomalak Jul 16 '15 at 06:26
  • How are the books displayed in the template? What is `allBooks` used for? – Patsy Issa Jul 16 '15 at 09:17
  • They are displayed as a list of books i.e. a for each loop in the template. Basically I have a bunch of Library objects and I want to find all books across all libraries but I do not want to add the book relation to Library. – abarax Jul 16 '15 at 23:05

1 Answers1

1

You can assemble the array in your route, use the PromiseProxyMixin in your controller (or some other object that is attached to your controller if you don't want them as the main model).

// in route
setupController: function(controller, model) {
    var allBooksPromise = new Ember.RSVP.Promise(function (resolve, reject) {
        // get your books here
        var allBooks = ...
        resolve(allBooks);
    }
    controller.set('promise', allBooksPromise);
}

// in controller
export default Ember.Controller.extend(Ember.PromiseProxyMixin);

// in template
{{#if controller.isPending}}
    Getting all of the books...
{{else}}
    <ul>
    {{#each controller.content as |book|}}
       <li>{{book.title}}</li>
    {{/each}}
    </ul>
{{/if}}
Truffula
  • 99
  • 3
  • I guess the only issue with this is that the books collection is not updated when new books are added. – abarax Jul 18 '15 at 04:28
  • I think if you then use a computed property that watches the arrays it would be updated automatically as the records become available; you just need to kick off the `.get()` in your route. Whether you need the `PromiseProxyMixin` depends on whether you need to make sure all records are loaded before any are displayed (in case you need multiple GET requests for your books). // library controller allBooks: Ember.computed('authors.[]', 'authors.@each.books.[]', { // compile the list synchronously instead of in the route get: function() { ... } }); – Truffula Jul 20 '15 at 03:58
  • ... but I'm not 100% sure if you can use `@each` with `.[]` in the same observer – Truffula Jul 20 '15 at 04:00