Skip to content

Instantly share code, notes, and snippets.

@blimmer
Last active August 29, 2015 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blimmer/f452be3801951a8d3a40 to your computer and use it in GitHub Desktop.
Save blimmer/f452be3801951a8d3a40 to your computer and use it in GitHub Desktop.
Ember Data Two Models in One Request

The Problem

We've got a server endpoint that's returning two models (in the example, Apples and Oranges). It makes sense for the server to do the computation in this way, so we ideally don't want to change this response structure. So, image you hit an endpoint /fruits that returns apples and oranges in a side-loaded fashion. In the example JSBin, we're mocking this response.

So far, so good. However, we want to be able to request Apples or Oranges indepently from the Store (e.g. store.findAll('orange') or store.findAll('apple')). We found a way to do this by overriding pathForType, which works great, but we are double-requesting the data from server when we request Apples and Oranges on the same page. Imagine you were able to swap in different components containing info about Apples or Oranges, and it needs to request itself from the store to ensure we have the data in the store to show the user (or request from the server if we don't have the data).

The Example

I've written a quick JSBin that shows the issue. Make sure you open the network tab and filter to 4.js to see the double request. Here's a screenshot with what you'll see:
Screenshot of 2 Network Requests in JSBin

NOTE: You might have to do a hard-refresh (command + shift + r) to see the problem. The issue does not seem to occur once the response is cached.

Question for the Community

Is it possible to do something like this? We have thought of a few ways to work around this, but none are ideal. We could keep track of the endpoints to request and present a facade for requesting each group of data, but this is not very declarative. Ideally, we'd like to have a way that Ember sees that we already have a Promise to the shared endpoint, and only make one AJAX call to the server.

Thanks

Thanks for taking a few minutes to look at this issue! If you have any ideas or suggestions, I'd love to hear them in the comments section below.

@YoranBrondsema
Copy link

You can retrieve the Oranges and the Apples separately with something along the following lines. The trick is that all does not look further than the Store, i.e. it is not going to fetch remote data.

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return this.store.find('apple');
  },
  setupController: function(controller, model) {
    controller.set('oranges', this.store.all('orange'));
    controller.set('apples', this.store.all('apple'));
  }
});

@blimmer
Copy link
Author

blimmer commented Jul 15, 2014

Thanks Yoran, that's definitely a solution. Unfortunately, it requires knowledge that apple and orange are linked to the same model call. It makes it easy to accidentally not make the call that populates the data you need in the store.

For example, consider if you had a list of apple varieties in a header component on all pages and list oranges on the homepage but no other pages. It would be nice to call findAll('apple') in the Header component and findAll('orange') in the homepage route to ensure both apple and orange models are always appropriately populated.

@ppcano
Copy link

ppcano commented Jul 16, 2014

Did you try sideloading? There are some rest-adapter tests which are good examples to know how to use it.

@YoranBrondsema
Copy link

@blimmer I see what you're trying to achieve. I think you should be able to retrieve a list of on-going requests in the Store. However, I feel this is going to be hacky. Another way I'm seeing would be to override the ajax method on both the OrangeAdapter and AppleAdapter (create a common Adapter and let both of them inherit from that one) that would set a flag isResolving before performing the request, and would only perform the actual request if isResolving is false. Then you need some support for the adapter to wait until the other request has completed before resolving its promise, so that all the "magic" is contained within the adapter.

This is purely an idea of how I think it could work, I haven't actually tried implementing any of it.

@blimmer
Copy link
Author

blimmer commented Jul 16, 2014

@ppcano thanks for taking a minute to look. The unit tests for sideloading pretty much support what @YoranBrondsema recommended with the store.all call (though, in the tests they're using getById which is similar).

@YoranBrondsema, I like this idea and it's likely the way we're going to have to fix the problem. Just wanted to see if there was a more "Ember-y" way to accomplish this. Do you think this is a use-case that's worth opening an issue for?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment