2014-04-21 9 views
8

Enviroment

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

Modell

Ich habe meine Anwendungsfall vereinfacht die Frage leichter zu verstehen und anwser zu machen. Nehmen wir an, wir haben 3 Modelle: Country, Region und Area:Wie kann man ein Versprechen bestehend aus verschachtelten Modellen in EmberJS mit EmberData zurückgeben?

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

Erwartete Ergebnisse

Die Modell Haken Route sollte ein Array von Objekten zurück. Wie folgt aus:

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 

Aktuelle Ansatz

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

Fehler

ich Error while loading route: TypeError: Object [object Array] has no method 'then' bekommen, die offensichtlich von diesem Code kommt:

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

Dies sollte jedoch zeigen t er echtes Problem habe ich:

Das Problem

Ich brauche countries beschlossen, den regions zu bekommen, was wiederum ich die areas bekommen müssen. Ich habe die RSVP.hash und RSVP.all Funktionen überprüft, die offizielle API gelesen und this talk geguckt, jedoch schaffe ich es irgendwie nicht, den richtigen Code für die Kettenversprechen zu erstellen und im letzten then das zurückgegebene Ergebnis so zu modifizieren, dass es meinen Erwartungen entspricht.

Abschließende Gedanken

Ich habe gesagt, dass Laden von Daten wie diese Anfragen vielen HTTP verursachen und wahrscheinlich würde dies durch sideloading besser gelöst werden, aber:

  • in diesem Moment, ich benutze FixturesAdapter, so HTTP-Anforderungen sind kein Problem
  • ich möchte wirklich RSVP verstehen und verspricht bessere

das ist, warum es impo ist mir, um herauszufinden, wie dies richtig gemacht werden sollte.

Edit 1: Anwendung von Änderungen vorgeschlagen von kingpin2k

Ich habe ein JSBin für mein Beispiel mit Änderungen von kingpin2k des anwser vorgeschlagen erstellt.

Während der Code funktioniert, sind die Ergebnisse ... unerwartete:

  • im countries Array fand ich beide country und region Objekte. Warum?
  • die country und region Objekte scheinen geladen zu werden, aber die Bereiche nicht (siehe Konsolenprotokoll führt zu JSBin). Warum?

Edit 2: Erklärung des unerwarteten Verhaltens von Edit1.

So habe ich endlich bemerkt, wo ich vom rechten Weg von Ember abirrte. Kingpin2k des anwser war ein großer Schritt nach vorn, aber es enthält einen kleinen Fehler:

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 .. jetzt habe ich endlich alle Objekte korrekt aufgelöst (Länder, Regionen, Gebiete) und kann meine Arbeit fortsetzen :)

EDIT 3: Arbeitslösung in diesem JSBin!

Antwort

12

Der Trick besteht darin, dass Sie bestimmte Versprechen lösen müssen, bevor Sie auf die Eigenschaften dieser Datensätze zugreifen können. Ember.RSVP.all nimmt ein Array von Versprechen. Ember.RSVP.hash nimmt ein Versprechenshash. Leider bist du in der Situation, wo du deine Versprechen nicht aufbauen kannst, bis die vorherigen Versprechen gelöst sind (a la, du weißt nicht, welche regions zu bekommen sind, bis die countries gelöst sind, und du nicht weißt, welche areas zu bekommen ist bis die regions aufgelöst sind). In diesem Fall haben Sie wirklich eine Reihe von Versprechen zu holen (wenn auch Arrays von Versprechen auf jeder Ebene). Ember weiß zu warten, bis sich das tiefste Versprechen aufgelöst hat und diesen Wert als Modell zu verwenden.

Jetzt müssen wir so tun, dass regions und area sind async, wenn sie es nicht sind, Sie sagen Ember Daten werden die Informationen mit country oder in der Anfrage mit region und diese Sammlungen gewann in der Anfrage enthalten sein‘ t versprechen, so dass der Code, den ich unten eingefügt habe, nicht funktionieren würde.

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

    } 
}); 

All dies gesagt ist, da es Sie verwenden Ember Daten erscheint, ich habe gerade this.store.find('country') zurückkehren würde und lassen Ember Daten die Daten abrufen, wenn es verwendet wird ... Diese Vorlage ohne all dieses Versprechen funktionieren würde Code, und würde füllen, wie Ember Data die Versprechungen selbst erfüllt (es wird die Daten anfordern, sobald es sieht, dass Sie versucht haben, die Daten zu verwenden, gute alte Lazy Loading).

{{#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

Hallo, danke für die Antwort, +1, wie dies hilfreich ist. Allerdings kann ich (noch) nicht akzeptiert werden, da die Ergebnisse immer noch nicht wie erwartet sind. Ich habe meine Frage aktualisiert und ein JSBin-Beispiel eingefügt. Wenn Sie so nett wären, es sich anzusehen, tun Sie es bitte. Prost – loostro

+0

Hallo, ich habe gefunden, wo das Problem war. Ihr Verstärker hat einen nicht offensichtlichen logischen Fehler. Sie haben mir geholfen, mit Ihrer Anwers viel, also wenn Sie meine Bearbeitung lesen, und dann korrigieren Sie Ihre Antwort ich wäre mehr als glücklich, es akzeptiert zu markieren. – loostro

+0

Guter Fang, ich vermisste die Tatsache, dass es eine Sammlung von Sammlungen war, das Problem mit nicht testen;) – Kingpin2k

4

Was könnten Sie stattdessen tun:

Wenn Sie hier sind, sind Sie wahrscheinlich den gleichen Fehler machen wie ich :)

tat Wenn Sie einen komplizierten Baum von Objekten müssen angezeigt werden Ihre Route könnten Sie auch:

  • Wenn Sie RESTAdapter verwenden, können Sie Daten in einer HTTP-Anforderung seitenweise laden.
  • Wenn Sie FixturesAdapter (z. B. in der Entwicklungsphase) mit einem festen Satz von Daten verwenden, können Sie zu LocalStorageAdapter wechseln - wenn Sie ein Modell anfordern, lädt es alle zugehörigen Modelle. So wird es so einfach sein wie eine einfache this.store.find('mymodel', model_id)

Allerdings bin ich die ursprüngliche anwser verlassen, die als „akzeptiert“, wie es anwsers tatsächlich die ursprüngliche Frage, und diese anwser ist nur ein Hinweis für zukünftige Referenz/andere Benutzer .