Question

Hopefully you can help me! :)

My issue is that I have a Route that looks like this which I'm expecting to populate a list of items... i.e. "only the tagged ones, please", but it doesn't:

App.TaggedItemsListRoute = App.ItemsRoute.extend({
  model: function() {
    var store = this.get("store");
    var storePromise = store.find("item", { has_tags: true });
    var filtered = store.filter("item", function(item) {
      return item.get("hasTags");
    });
    return storePromise.then(function(response) {
      return filtered;
    });
  }
});

Now... that just plain doesn't work because "hasTags" returns false because it relies on "tags" which returns a ManyArray which is temporarily empty beacuse it hasn't resolved yet (see models below). This seems crappy to me. It's saying "Hey I've gone none in me!" but what I want it to be saying is "please recalculate me later" and the filter is looking for a boolean, but what I want to pass it is "hey, don't resolve the filter until all hasTags have resolved" or at least to recompute the ManyArray that it passes.

If I just pass back a promise as the return value for the filter then it sort of works...

return item.get("tags").then(function(tags){ return item.get("hasTags"); });

Except that it's actually not, beacuse filter is getting a Promise, but it's not aware of promises, apparently, so when it's looking for a boolean it gets a promise which it evaluates as true, and then it pretty much shows all the items in the list. That's not a problem until I go to a different route for items which has, say, all the items on it, then come back... and BAM it's got all the items in it... hm....

The following is how I've "gotten around" it temporarily ... ie it's still buggy, but I can live with it...

App.TaggedItemsListRoute = App.ItemsRoute.extend({
  model: function() {
    var store = this.get("store");
    var storePromise = store.find("item", { has_tags: true });
    var filtered = store.filter("item", function(item) {
      var tags = item.get("tags");
      if tags.get("isFulfilled") {
        return item.get("hasTags");
      } else {
        return tags.then(function() {
          return item.get("hasTags");
        });
      }
    });
    return storePromise.then(function(response) {
      return filtered;
    });
  }
});

I think the only way to really get around this at this stage would be to use RSVP.all... any thoughts?

Actually one thing I haven't tried which I might go try now is to use setupController to do the filtering. The only trouble there would be that ALL the items would get loaded inot the list and then visually "jump back" to a filtered state after about 1 second. Painful!

Models

My Ember App (Ember 1.5.1) has two models (Ember Data beta7): Item and Tag. Item hasMany Tags.

App.Item = DS.Model.extend({
  tags: DS.hasMany("tag", inverse: "item", async: true),
  hasTags: function() {
    return !Em.isEmpty(this.get("tags"));
  }.property("tags")
});

App.Tag = DS.Model.extend(
  item: DS.belongsTo("item", inverse: "tags"),
  hasItem: function() {
    return !Em.isEmpty(this.get("item"))
  }.property("item")
);

If I change the model to the following, it actually does print something to the logs when I go to the route above, so it is fulfilling the promise.

App.Item = DS.Model.extend({
  tags: DS.hasMany("tag", inverse: "item", async: true),
  hasTags: function() {
    this.get("tags").then(function(tags) {
      console.log("The tags are loding if this is printed");
    });
    return !Em.isEmpty(this.get("tags"));
  }.property("tags")
});

This is a spin off question from Ember Data hasMany async observed property "simple" issue because I didn't really explain my quesiton well enough and was actually asking the wrong question. I originally thought I could modify my model "hasTags" property to behave correctly in the context of my Route but I now don't think that will work properly...

Was it helpful?

Solution

This seems like a perfectly good candidate for RSVP.all. BTW if you want a rundown on RSVP I gave a talk on it a few weeks back (don't pay too much attention too it, pizza came halfway through and I got hungry, http://www.youtube.com/watch?v=8WXgm4_V85E ). Regardless, your filter obviously depends on the tag collection promises being resolved, before it should be executed. So, it would be appropriate to wait for those to resolve before executing the filter.

App.TaggedItemsListRoute = App.ItemsRoute.extend({
  model: function() {
    var store = this.get("store");
    return store.find("item", { has_tags: true }).then(function(items){
         var tagPromises = items.getEach('tags');
         return Ember.RSVP.all(tagPromises).then(function(tagCollections){
             // at this point all tags have been received
             // build your filter, and resolve that
             return store.filter("item", function(item) {
                return item.get("hasTags");
             });
         });
    });
  }
});

Example using a similar idea with colors (I only show it if the relationship has 3 associated colors)

http://emberjs.jsbin.com/OxIDiVU/454/edit

On a separate note, if you felt like you wanted this hook to resolve immediately, and populate magically after, you could cheat and return an array, then populate the array once the results have come back from the server, allowing your app to seem like it's reacting super quick (by drawing something on the page, then magically filling in as the results come pouring in).

App.TaggedItemsListRoute = App.ItemsRoute.extend({
  model: function() {
    var store = this.get("store"),
        quickResults = [];
    store.find("item", { has_tags: true }).then(function(items){
         var tagPromises = items.getEach('tags');
         return Ember.RSVP.all(tagPromises).then(function(tagCollections){
             // at this point all tags have been received
             // build your filter, and resolve that
             return store.filter("item", function(item) {
                return item.get("hasTags");
             });
         });
    }).then(function(filterResults){
       filterResults.forEach(function(item){
         quickResults.pushObject(item);
       });
    });

    return quickResults;
  }
});

Example of quick results, returns immediately (I only show it if the relationship has 3 associated colors)

http://emberjs.jsbin.com/OxIDiVU/455/edit

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top