2014-04-21 12 views
8

Enviroment

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

model

Mam uproszczone moim przypadku użyć, aby kwestia łatwiejsze do zrozumienia i anwser. Załóżmy, że mamy 3 modele: Country, Region i Area:Jak zwrócić obietnicę złożoną z modeli zagnieżdżonych w EmberJS z 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') 

oczekiwane rezultaty

hak modelu Trasa powinna zwracać tablicę obiektów. Tak:

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 

Aktualne podejście

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

Błąd

Dostaję Error while loading route: TypeError: Object [object Array] has no method 'then' który oczywiście pochodzi z tego kodu:

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

Jednak powinno to pokazać t on prawdziwy problem mam:

Problem

muszę countries rozwiązany, aby uzyskać regions, co z kolei muszę dostać areas. Sprawdzałem funkcje RSVP.hash i RSVP.all, czytając oficjalny interfejs API i oglądając this talk, jednak nie udało mi się utworzyć poprawnego kodu obietnic łańcuchowych, aw ostatecznym then zmodyfikować zwracany wynik, aby spełnić moje oczekiwania.

Końcowe przemyślenia

Powiedziano mi, że ładowanie danych, jak może to spowodować prośby wielu HTTP i prawdopodobnie to będzie rozwiązane lepiej sideloading, ale:

  • w tej chwili używam FixturesAdapter, więc żądania HTTP nie są problemem
  • naprawdę chcę zrozumieć RSVP i obiecuje lepsze

to dlaczego jest impo dla mnie ważne, aby dowiedzieć się, jak to zrobić poprawnie.

Edit 1: zastosowanie zmian sugerowanych przez kingpin2k

Utworzyłem JSBin na moim przykładzie ze zmianami zaproponowanymi przez anwser kingpin2k użytkownika.

Chociaż kod działa, wyniki są ... nieoczekiwany:

  • w tablicy countries znalazłem zarówno country i region obiektów. Dlaczego?
  • Obiekty wydają się być załadowane, ale obszary nie są (zobacz wyniki log konsoli w JSBin). Dlaczego?

Edycja 2: Wyjaśnienie nieoczekiwanego zachowania z Edit1.

Tak więc w końcu zauważyłem, gdzie zbłądziłem ze sprawiedliwej ścieżki Ember. anwser Kingpin2k był ogromny krok naprzód, ale zawiera trochę błąd:

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

Tak .. Teraz mam wreszcie wszystkie obiekty prawidłowo rozwiązane (krajów, regionów, obszarów) i może kontynuować swoją pracę :)

EDYCJA 3: Rozwiązanie robocze w tym JSBin!

Odpowiedz

12

Podstępem jest rozwiązanie pewnych obietnic, zanim uzyskasz dostęp do właściwości tych rekordów. Ember.RSVP.all ma szereg obietnic. Ember.RSVP.hash ma hasz obietnic. Niestety znajdujesz się w sytuacji, w której nie możesz zbudować swoich obietnic, dopóki poprzednie obietnice nie zostaną rozwiązane (a la, nie wiesz, które regions uzyskać, dopóki nie zostaną rozwiązane countries, i nie wiesz, który areas uzyskać do momentu rozwiązania regions). W tym przypadku naprawdę masz szereg obietnic do zdobycia (choć tablice obietnic na każdym poziomie). Ember wie, że musi poczekać, aż najgłębsza obietnica ustąpi i użyć tej wartości jako modelu.

Teraz musimy udawać, że regions i area są asynchroniczne, jeśli nie są one, mówisz Ember dane informacje zostaną zawarte we wniosku z country lub we wniosku z region i te zbiory wygrał” Obiecujemy, że kod, który tu zamieściłem, nie zadziała.

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

    } 
}); 

Wszystko to zostało powiedziane, ponieważ wydaje się, że używasz Ember danych, to bym po prostu wrócić this.store.find('country') i niech Ember Dane pobrać dane, gdy jest używany ... Ten szablon będzie działać bez wszystkich tej obietnicy kodu, i zapełni się, gdy Ember Data spełni obietnice samodzielnie (zażąda danych, gdy zobaczy, że próbujesz użyć danych, dobre, leniwe ładowanie).

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

Cześć, dziękuję za odpowiedź, +1, ponieważ jest to pomocne. Jednak nie mogę oznaczyć jako zaakceptowany (jeszcze), ponieważ wyniki nadal nie są zgodne z oczekiwaniami. Zaktualizowałem moje pytanie i dodałem przykład JSBin. Jeśli byłbyś tak miły, żeby na to spojrzeć, proszę. Pozdrawiam – loostro

+0

Witam, Znalazłem problem. Twój anwser ma nieoczywisty błąd logiczny. Pomogłeś mi dużo z twoim anwser, więc jeśli przeczytasz moją edycję, a następnie poprawisz swoją anwser, byłbym bardziej niż szczęśliwy, gdybym ją zaakceptował. – loostro

+0

Dobry połów, brakowało mi faktu, że był to zbiór kolekcji, problem z nie testowaniem;) – Kingpin2k

4

Co można zrobić zamiast:

Jeśli jesteś tu, jesteś prawdopodobnie robi ten sam błąd jak ja :)

Jeśli potrzebujesz skomplikowanego drzewa obiektów do wyświetlenia Twoja trasa, możesz również:

  • Jeśli używasz RESTAdapter możesz sideload danych w jednym żądaniu HTTP.
  • Jeśli używasz FixturesAdapter (np. W fazie rozwoju) ze stałym zestawem danych, możesz przełączyć się na LocalStorageAdapter - tak jak w przypadku żądania modelu, ładuje on wszystkie skojarzone modele. Tak będzie tak proste, jak prosta this.store.find('mymodel', model_id)

Jednak Wyjeżdżam oryginalnego anwser oznaczony jako „przyjęte”, jak to faktycznie anwsers oryginalne pytanie, a to jest po prostu anwser uwaga na przyszłość/innych użytkowników .

Powiązane problemy