Enviroment
# Ember : 1.4.0
# Ember Data : 1.0.0-beta.7+canary.b45e23ba
Model
I have simplified my use case to make the question easier to understand and anwser. Let's assume we have 3 models: Country
, Region
and Area
:
Country:
- id: DS.attr('number')
- name: DS.attr('string')
- regions: DS.hasMany('region')
Region:
- id: DS.attr('number')
- name: DS.attr('string')
- country: DS.belongsTo('country')
- areas: DS.hasMany('area')
Area:
- id: DS.attr('number')
- name: DS.attr('string')
- region: DS.belongsTo('region')
Expected results
The Route's model hook should return an array of objects. Like this:
Note: The indentations are only to make the example more readable.
Country I
Region A
Area 1
Area 2
Region B
Area 3
Country II
Region C
Area 4
Country III
Region D
Area 5
Current approach
App.MyRoute = Ember.Route.extend({
model: function() {
return this.store.find('country').then(function(countries){
// promise all counties
// map resolved countires into an array of promises for owned regions
var regions = countries.map(function(country){
return country.get('regions');
});
// map resolved regions into an array of promises for owned areas
var areas = regions.then(function(regions){
return regions.map(function(region){
return region.get('areas');
});
});
// do not return until ALL promises are resolved
return Ember.RSVP.all(countries, regions, areas).then(function(data){
// here somehow transform the data into expected output format and return it
});
});
}
)};
Error
I'm getting Error while loading route: TypeError: Object [object Array] has no method 'then'
which obviously comes from this code:
var regions = countries.map(function(country){
return country.get('regions');
});
var areas = regions.then(function(regions){
// regions is not a promise
However this should show the real problem I have:
The problem
I need countries
resolved to get the regions
, which in turn I need to get the areas
. I've been checking the RSVP.hash
and RSVP.all
functions, reading the official API and watching this talk, however I somewhat fail to create the correct code to chain promises and in the final then
modify the returned result to match my expectations.
Final thoughts
I have been told that loading data like this may cause many HTTP requests and probably this would be solved better by sideloading, but:
- at this moment, I use
FixturesAdapter
, so HTTP requests are not an issue - I really want to understand RSVP and Promises better
Thats why it is important to me to figure out how this should be done correctly.
Edit 1: applying changes suggested by kingpin2k
I've created a JSBin for my example with changes suggested by kingpin2k's anwser.
While the code works, the results are... unexpected:
- in the
countries
array I found bothcountry
andregion
objects. Why? - the
country
andregion
objects seem to be loaded, but area's don't (see console log results in the JSBin).. Why?
Edit 2: Explanation of unexpected behaviour from Edit1.
So I've finally noticed where I went astray from the righteous path of Ember. Kingpin2k's anwser was a huge step forward, but it contains a little error:
return this.store.find('country').then(function(countries){
// this does not return an array of regions, but an array of region SETs
var regionPromises = countries.getEach('regions');
// wait for regions to resolve to get the areas
return Ember.RSVP.all(regionPromises).then(function(regions){
// thats why here the variable shouldn't be called "regions"
// but "regionSets" to clearly indicate what it holds
// for this example i'll just reassign it to new var name
var regionSets = regions;
// now compare these two lines (old code commented out)
//var areaPromises = regions.getEach('areas');
var areaPromises = regionSets.getEach('areas');
// since regionSet does not have a property "areas" it
// won't return a promise or ever resolve (it will be undefined)
// the correct approach would be reduceing the array of sets
// an array of regions
var regionsArray = regionSets.reduce(function(sum, val){
// since val is a "Ember.Set" object, we must use it's "toArray()" method
// to get an array of contents and then push it to the resulting array
return sum.pushObjects(val.toArray());
}, []);
// NOW we can get "areas"
var realAreaPromises = regionsArray.getEach('areas');
// and now we can use Ember.RSVP to wait for them to resolve
return Ember.RSVP.all(realAreaPromises).then(function(areaSets){
// note: here again, we don't get an array of areas
// we get an array of area sets - each set for the corresponding region
var results = [];
So.. now I've finally got all the objects correctly resolved (countries, regions, areas) and can continue my work :)