2014-04-21 12 views
8

Ambiente

# Ember  : 1.4.0 
# Ember Data : 1.0.0-beta.7+canary.b45e23ba 

Modello

Ho semplificato il mio caso d'uso per rendere la questione più facile da capire e anwser. Supponiamo che abbiamo 3 modelli: Country, Region e Area:Come restituire una promessa composta da modelli nidificati in EmberJS con EmberData?

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') 

Risultati attesi

gancio Il modello, lungo la Strada deve restituire un array di oggetti. Come questo:

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 

approccio attuale

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 
     }); 
    }); 
    } 
)}; 

errore

mi sto Error while loading route: TypeError: Object [object Array] has no method 'then' che proviene ovviamente da questo codice:

var regions = countries.map(function(country){ 
    return country.get('regions'); 
}); 
var areas = regions.then(function(regions){ 
// regions is not a promise 

Tuttavia, questo dovrebbe mostrare t egli vero problema che ho:

Il problema

ho bisogno countries deciso di ottenere il regions, che a sua volta ho bisogno di ottenere il areas. Ho controllato le funzioni RSVP.hash e RSVP.all, leggendo l'API ufficiale e guardando this talk, tuttavia non riesco a creare il codice corretto per concatenare le promesse e nel finale then modificare il risultato restituito in modo che corrisponda alle mie aspettative.

Considerazioni finali

mi è stato detto che il caricamento dei dati come questo può causare le richieste molti HTTP e probabilmente questo sarebbe risolto meglio sideloading, ma:

  • in questo momento, io uso FixturesAdapter, così le richieste HTTP non sono un problema
  • voglio davvero capire RSVP e migliori promesse

Ecco perché è impo Ringrazio di me per capire come questo dovrebbe essere fatto correttamente.

Modifica 1: applicare le modifiche suggerite da kingpin2k

Ho creato un JSBin per il mio esempio con le modifiche suggerite dai anwser di kingpin2k.

Mentre il codice funziona, i risultati sono ... inaspettata:

  • nel countries matrice ho trovato entrambe le country e region oggetti. Perché?
  • gli oggetti country e region sembrano essere caricati, ma l'area non funziona (vedere i risultati del log di console nel JSBin) .. Perché?

Modifica 2: spiegazione del comportamento imprevisto da Edit1.

Quindi ho finalmente notato dove sono andato fuori strada dal sentiero retto di Ember. anwser di Kingpin2k è stato un enorme passo in avanti, ma contiene un piccolo errore:

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 = []; 

Quindi .. ora ho finalmente ottenuto tutti gli oggetti correttamente risolti (paesi, regioni, aree) e possono continuare il mio lavoro :)

MODIFICA 3: soluzione di lavoro in questo JSBin!

risposta

12

Il trucco è che è necessario risolvere determinate promesse prima di poter accedere alle proprietà su tali record. Ember.RSVP.all prende una serie di promesse. Ember.RSVP.hash prende un hash di promesse. Sfortunatamente sei nella situazione in cui non puoi costruire le tue promesse fino a quando le promesse precedenti non sono state risolte (a la, non sai quale regions ottenere fino a quando lo countries non viene risolto e non sai quale areas ottenere fino a quando lo regions non viene risolto). Stando così le cose, hai davvero un set seriale di promesse da recuperare (anche se matrici di promesse ad ogni livello). Ember sa aspettare fino a quando la promessa più profonda non è stata risolta e utilizzare quel valore come modello.

Ora abbiamo bisogno di far finta che regions e area sono asincrone, se non lo sono, che stai dicendo Ember dati le informazioni saranno incluse nella richiesta con country, o nella richiesta con region e quelle collezioni vinto' t essere promesse quindi il codice che ho incluso di seguito non funzionerebbe.

regions: DS.hasMany('region', {async: true}) 

areas: DS.hasMany('area', {async: true}) 

App.IndexRoute = Ember.Route.extend({ 
    controllerName: 'application', 

    model: function() { 
    return this.store.find('country').then(function(countries){ 
     // get each country promises 
     var regionCollectionPromises = countries.getEach('regions'); 

     // wait for regions to resolve to get the areas 
     return Ember.RSVP.all(regionCollectionPromises).then(function(regionCollections){ 

     var regions = regionCollections.reduce(function(sum, val){ 
      return sum.pushObjects(val.toArray()); 
     }, []); 

     var areaCollectionPromises = regions.getEach('areas'); 
     //wait on the areas to resolve 
     return Ember.RSVP.all(areaCollectionPromises).then(function(areaCollections){ 

      // yay, we have countries, regions, and areas resolved 

      return countries; 
     }); 
     }); 
    }); 

    } 
}); 

Tutto questo detto, dal momento che sembra che si sta utilizzando Ember dati, avevo appena tornare this.store.find('country') e lascio Ember dati recuperare i dati quando è utilizzato ... Questo modello potrebbe funzionare senza tutto di quella promessa codice, e popolerebbe come Ember Data soddisfa le promesse da solo (richiederà i dati una volta che vedrà che hai tentato di utilizzare i dati, buon vecchio caricamento pigro).

{{#each country in model}} 
    Country: {{country.name}} 
    {{#each region in country.regions}} 
    Region: {{region.name}} 
     {{#each area in region.areas}} 
     Area: {{area.name}} 
    {{/each}} 
    {{/each}} 
{{/each}} 
+0

Ciao, grazie per la risposta, +1 come questo è utile. Tuttavia non posso contrassegnare accettato (ancora), in quanto i risultati non sono ancora come previsto. Ho aggiornato la mia domanda e incluso un esempio JSBin. Se vuoi essere così gentile da dare un'occhiata, per favore fallo. Cheers – loostro

+0

Ciao, ho trovato dov'era il problema. Il tuo anser ha un errore logico non ovvio. Mi hai aiutato molto con il tuo anser quindi se leggi la mia modifica e poi correggi il tuo anser sarei più che felice di segnarlo accettato. – loostro

+0

Una buona cattura, mi mancava il fatto che si trattasse di una serie di raccolte, il problema di non provare;) – Kingpin2k

4

Che cosa si potrebbe fare, invece:

Se siete qui, probabilmente stai facendo lo stesso errore come ho fatto :)

Se avete bisogno di un albero complicata di oggetti da visualizzare il tuo percorso, potresti anche:

  • Se si utilizza RESTAdapter, è possibile trasferire i dati in una richiesta HTTP.
  • Se si utilizza FixturesAdapter (ad esempio in fase di sviluppo) con un set fisso di dati, è possibile passare a LocalStorageAdapter - come quando si richiede un modello, carica tutti i modelli associati. Così sarà facile come una semplice this.store.find('mymodel', model_id)

Tuttavia mi sto lasciando l'anwser originale contrassegnato come "accettato" come realmente anwsers alla domanda iniziale, e questo anwser è solo una nota per i futuri riferimenti/altri utenti .

Problemi correlati