tutorial ember data ember.js ember-data

ember.js - data - ember tutorial



¿Cómo usar múltiples modelos con una sola ruta en EmberJS/Ember Data? (8)

Después de leer los documentos, parece que tiene que (o debería) asignar un modelo a una ruta como esta:

App.PostRoute = Ember.Route.extend({ model: function() { return App.Post.find(); } });

¿Qué pasa si necesito usar varios objetos en una ruta determinada? es decir, publicaciones, comentarios y usuarios? ¿Cómo digo la ruta para cargar esos?


Añadiendo a la respuesta de MilkyWayJoe, gracias por cierto:

this.store.find(''post'',1)

devoluciones

{ id: 1, title: ''string'', body: ''string string string string...'', author_id: 1, comment_ids: [1, 2, 3, 6], tag_ids: [3,4] };

autor sería

{ id: 1, firstName: ''Joe'', lastName: ''Way'', email: ''[email protected]'', points: 6181, post_ids: [1,2,3,...,n], comment_ids: [1,2,3,...,n], }

comentarios

{ id:1, author_id:1, body:''some words and stuff...'', post_id:1, }

... Creo que los vínculos de retroceso son importantes para que se establezca la relación completa. Espero que ayude a alguien.


Durante un tiempo estuve usando Em.RSVP.hash , sin embargo, el problema que encontré fue que no quería que mi vista aguardara a que todos los modelos se cargaran antes de renderizar. Sin embargo, encontré una gran (pero relativamente desconocida) solución gracias a la gente de Novelys que implica hacer uso de Ember.PromiseProxyMixin :

Supongamos que tiene una vista que tiene tres secciones visuales distintas. Cada una de estas secciones debe estar respaldada por su propio modelo. El modelo que respalda el contenido "splash" en la parte superior de la vista es pequeño y se cargará rápidamente, por lo que puede cargarlo normalmente:

Crea una ruta main-page.js :

import Ember from ''ember''; export default Ember.Route.extend({ model: function() { return this.store.find(''main-stuff''); } });

A continuación, puede crear una plantilla de manubrios correspondiente main-page.hbs :

<h1>My awesome page!</h1> <ul> {{#each thing in model}} <li>{{thing.name}} is really cool.</li> {{/each}} </ul> <section> <h1>Reasons I Love Cheese</h1> </section> <section> <h1>Reasons I Hate Cheese</h1> </section>

Entonces, digamos en su plantilla que desea tener secciones separadas sobre su relación de amor / odio con el queso, cada uno (por alguna razón) respaldado por su propio modelo. Tiene muchos registros en cada modelo con detalles detallados relacionados con cada motivo, sin embargo, le gustaría que el contenido se imprima rápidamente. Aquí es donde entra el ayudante {{render}} . Puede actualizar su plantilla de la siguiente manera:

<h1>My awesome page!</h1> <ul> {{#each thing in model}} <li>{{thing.name}} is really cool.</li> {{/each}} </ul> <section> <h1>Reasons I Love Cheese</h1> {{render ''love-cheese''}} </section> <section> <h1>Reasons I Hate Cheese</h1> {{render ''hate-cheese''}} </section>

Ahora necesitará crear controladores y plantillas para cada uno. Dado que son efectivamente idénticos para este ejemplo, solo usaré uno.

Crea un controlador llamado love-cheese.js :

import Ember from ''ember''; export default Ember.ObjectController.extend(Ember.PromiseProxyMixin, { init: function() { this._super(); var promise = this.store.find(''love-cheese''); if (promise) { return this.set(''promise'', promise); } } });

Notarás que estamos usando PromiseProxyMixin aquí, lo que hace que el controlador sea consciente de las promesas. Cuando se inicializa el controlador, indicamos que la promesa debería ser cargar el modelo love-cheese través de Ember Data. Deberá establecer esta propiedad en la propiedad de la promise del controlador.

Ahora, crea una plantilla llamada love-cheese.hbs :

{{#if isPending}} <p>Loading...</p> {{else}} {{#each item in promise._result }} <p>{{item.reason}}</p> {{/each}} {{/if}}

En su plantilla, podrá representar contenido diferente según el estado de la promesa. Cuando su página se carga inicialmente, su sección "Razones por las que amo el queso" mostrará Loading... Cuando se cargue la promesa, representará todas las razones asociadas para cada registro de su modelo.

Cada sección se cargará de forma independiente y no bloqueará el contenido principal de la representación de forma inmediata.

Este es un ejemplo simplista, pero espero que todos los demás lo encuentren tan útil como yo.

Si está buscando hacer algo similar para muchas filas de contenido, puede encontrar que el ejemplo de Novelys anterior es aún más relevante. Si no, lo anterior debería funcionar bien para usted.


Gracias a todas las otras respuestas excelentes, creé un mixin que combina las mejores soluciones aquí en una interfaz simple y reutilizable. Ejecuta un Ember.RSVP.hash en afterModel para los modelos que especifique, luego inyecta las propiedades en el controlador en setupController . No interfiere con el gancho de model estándar, por lo que aún definiría eso como normal.

Ejemplo de uso:

App.PostRoute = Ember.Route.extend(App.AdditionalRouteModelsMixin, { // define your model hook normally model: function(params) { return this.store.find(''post'', params.post_id); }, // now define your other models as a hash of property names to inject onto the controller additionalModels: function() { return { users: this.store.find(''user''), comments: this.store.find(''comment'') } } });

Aquí está el mixin:

App.AdditionalRouteModelsMixin = Ember.Mixin.create({ // the main hook: override to return a hash of models to set on the controller additionalModels: function(model, transition, queryParams) {}, // returns a promise that will resolve once all additional models have resolved initializeAdditionalModels: function(model, transition, queryParams) { var models, promise; models = this.additionalModels(model, transition, queryParams); if (models) { promise = Ember.RSVP.hash(models); this.set(''_additionalModelsPromise'', promise); return promise; } }, // copies the resolved properties onto the controller setupControllerAdditionalModels: function(controller) { var modelsPromise; modelsPromise = this.get(''_additionalModelsPromise''); if (modelsPromise) { modelsPromise.then(function(hash) { controller.setProperties(hash); }); } }, // hook to resolve the additional models -- blocks until resolved afterModel: function(model, transition, queryParams) { return this.initializeAdditionalModels(model, transition, queryParams); }, // hook to copy the models onto the controller setupController: function(controller, model) { this._super(controller, model); this.setupControllerAdditionalModels(controller); } });


Puede que esta no sea la mejor práctica y un enfoque ingenuo, pero muestra conceptualmente cómo se podría tener en varios modelos disponibles en una ruta central:

App.PostRoute = Ember.Route.extend({ model: function() { var multimodel = Ember.Object.create( { posts: App.Post.find(), comments: App.Comments.find(), whatever: App.WhatEver.find() }); return multiModel; }, setupController: function(controller, model) { // now you have here model.posts, model.comments, etc. // as promises, so you can do stuff like controller.set(''contentA'', model.posts); controller.set(''contentB'', model.comments); // or ... this.controllerFor(''whatEver'').set(''content'', model.whatever); } });

Espero eso ayude


Puede utilizar los beforeModel o afterModel ya que siempre se afterModel , incluso si no se llama al model porque está utilizando segmentos dinámicos.

De acuerdo con los documentos de enrutamiento asíncrono :

El gancho modelo cubre muchos casos de uso para transiciones de pausa en promesa, pero a veces necesitará la ayuda de los enlaces relacionados beforeModel y afterModel. La razón más común para esto es que si está realizando la transición a una ruta con un segmento de URL dinámico a través de {{link-to}} o transitionTo (en oposición a una transición causada por un cambio de URL), el modelo de la ruta Ya se ha especificado la transición a (se ha especificado {{# link-to ''artículo'' artículo}} o this.transitionTo (''artículo'', artículo)), en cuyo caso no se llamará al gancho modelo. En estos casos, deberá utilizar ya sea el modelo beforeModel o afterModel para incluir cualquier lógica mientras el enrutador sigue reuniendo todos los modelos de la ruta para realizar una transición.

Así que supongamos que tiene una propiedad de themes en su SiteController , podría tener algo como esto:

themes: null, afterModel: function(site, transition) { return this.store.find(''themes'').then(function(result) { this.set(''themes'', result.content); }.bind(this)); }, setupController: function(controller, model) { controller.set(''model'', model); controller.set(''themes'', this.get(''themes'')); }


Usar Em.Object para encapsular múltiples modelos es una buena forma de obtener todos los datos en el model hook. Pero no puede garantizar que todos los datos estén preparados después de ver la representación.

Otra opción es usar Em.RSVP.hash . Combina varias promesas juntas y devuelve una nueva promesa. La nueva promesa se resuelve después de que se hayan resuelto todas las promesas. Y setupController no se llama hasta que la promesa se resuelva o se rechace.

App.PostRoute = Em.Route.extend({ model: function(params) { return Em.RSVP.hash({ post: // promise to get post comments: // promise to get comments, user: // promise to get user }); }, setupController: function(controller, model) { // You can use model.post to get post, etc // Since the model is a plain object you can just use setProperties controller.setProperties(model); } });

De esta forma, obtienes todos los modelos antes de ver la representación. Y usar Em.Object no tiene esta ventaja.

Otra ventaja es que puedes combinar la promesa y la no promesa. Me gusta esto:

Em.RSVP.hash({ post: // non-promise object user: // promise object });

Consulte esto para obtener más información sobre Em.RSVP : https://github.com/tildeio/rsvp.js

Pero no use la solución Em.Object o Em.RSVP si su ruta tiene segmentos dinámicos

El problema principal es link-to . Si cambia la URL por el enlace de clic generado por el link-to modelos, el modelo se pasa directamente a esa ruta. En este caso, el gancho model no se llama y en setupController obtiene el modelo de link-to darle.

Un ejemplo es como esto:

El código de ruta:

App.Router.map(function() { this.route(''/post/:post_id''); }); App.PostRoute = Em.Route.extend({ model: function(params) { return Em.RSVP.hash({ post: App.Post.find(params.post_id), user: // use whatever to get user object }); }, setupController: function(controller, model) { // Guess what the model is in this case? console.log(model); } });

Y el código de link-to , la publicación es un modelo:

{{#link-to "post" post}}Some post{{/link-to}}

Las cosas se vuelven interesantes aquí. Cuando usa url /post/1 para visitar la página, se llama al enganche del model y setupController obtiene el objeto simple cuando se resuelve la promesa.

Pero si visita la página haciendo clic en link-to vincular, pasa el modelo de PostRoute a PostRoute y la ruta ignorará el PostRoute del model . En este caso, setupController obtendrá el modelo de post , por supuesto, no puede obtener el usuario.

Así que asegúrese de no usarlos en rutas con segmentos dinámicos.


Última actualización para siempre : no puedo seguir actualizando esto. Así que esto está en desuso y es probable que sea así. aquí hay un hilo mejor y más actualizado EmberJS: ¿Cómo cargar varios modelos en la misma ruta?

Actualización: En mi respuesta original, dije usar embedded: true en la definición del modelo. Eso es incorrecto En la revisión 12, Ember-Data espera que las claves foráneas se definan con un sufijo ( link ) _id para registro único o _ids para recopilación. Algo similar a lo siguiente:

{ id: 1, title: ''string'', body: ''string string string string...'', author_id: 1, comment_ids: [1, 2, 3, 6], tag_ids: [3,4] }

He actualizado el violín y lo volveré a hacer si algo cambia o si encuentro más problemas con el código provisto en esta respuesta.

Responda con modelos relacionados:

Para el escenario que está describiendo, me basaría en las associations entre los modelos (configuración embedded: true ) y solo cargaré el modelo de Post en esa ruta, teniendo en cuenta que puedo definir una asociación DS.hasMany para el modelo Comment y la asociación DS.belongsTo para el User en ambos modelos, Comment y Post . Algo como esto:

App.User = DS.Model.extend({ firstName: DS.attr(''string''), lastName: DS.attr(''string''), email: DS.attr(''string''), posts: DS.hasMany(''App.Post''), comments: DS.hasMany(''App.Comment'') }); App.Post = DS.Model.extend({ title: DS.attr(''string''), body: DS.attr(''string''), author: DS.belongsTo(''App.User''), comments: DS.hasMany(''App.Comment'') }); App.Comment = DS.Model.extend({ body: DS.attr(''string''), post: DS.belongsTo(''App.Post''), author: DS.belongsTo(''App.User'') });

Esta definición produciría algo como lo siguiente:

Con esta definición, cada vez que find una publicación, tendré acceso a una colección de comentarios asociados con esa publicación, y también al autor del comentario, y al usuario que es el autor de la publicación, ya que todos están incrustados . La ruta es simple:

App.PostsPostRoute = Em.Route.extend({ model: function(params) { return App.Post.find(params.post_id); } });

Así que en PostRoute (o PostsPostRoute si está utilizando un resource ), mis plantillas tendrán acceso al content del controlador, que es el modelo de Post , para que pueda referirme al autor, simplemente como author

<script type="text/x-handlebars" data-template-name="posts/post"> <h3>{{title}}</h3> <div>by {{author.fullName}}</div><hr /> <div> {{body}} </div> {{partial comments}} </script> <script type="text/x-handlebars" data-template-name="_comments"> <h5>Comments</h5> {{#each content.comments}} <hr /> <span> {{this.body}}<br /> <small>by {{this.author.fullName}}</small> </span> {{/each}} </script>

(ver fiddle )

Responda con modelos no relacionados:

Sin embargo, si su escenario es un poco más complejo que lo que describió, y / o tiene que usar (o consultar) diferentes modelos para una ruta en particular, recomendaría hacerlo en Route#setupController . Por ejemplo:

App.PostsPostRoute = Em.Route.extend({ model: function(params) { return App.Post.find(params.post_id); }, // in this sample, "model" is an instance of "Post" // coming from the model hook above setupController: function(controller, model) { controller.set(''content'', model); // the "user_id" parameter can come from a global variable for example // or you can implement in another way. This is generally where you // setup your controller properties and models, or even other models // that can be used in your route''s template controller.set(''user'', App.User.find(window.user_id)); } });

Y ahora, cuando estoy en la ruta postal, mis plantillas tendrán acceso a la propiedad del user en el controlador tal como se configuró en setupController hook:

<script type="text/x-handlebars" data-template-name="posts/post"> <h3>{{title}}</h3> <div>by {{controller.user.fullName}}</div><hr /> <div> {{body}} </div> {{partial comments}} </script> <script type="text/x-handlebars" data-template-name="_comments"> <h5>Comments</h5> {{#each content.comments}} <hr /> <span> {{this.body}}<br /> <small>by {{this.author.fullName}}</small> </span> {{/each}} </script>

(ver fiddle )


https://.com/a/16466427/2637573 está bien para los modelos relacionados. Sin embargo, con la versión reciente de Ember CLI y Ember Data, existe un enfoque más simple para los modelos no relacionados:

import Ember from ''ember''; import DS from ''ember-data''; export default Ember.Route.extend({ setupController: function(controller, model) { this._super(controller,model); var model2 = DS.PromiseArray.create({ promise: this.store.find(''model2'') }); model2.then(function() { controller.set(''model2'', model2) }); } });

Si solo desea recuperar la propiedad de un objeto para el model2 , use DS.PromiseObject lugar de DS.PromiseArray :

import Ember from ''ember''; import DS from ''ember-data''; export default Ember.Route.extend({ setupController: function(controller, model) { this._super(controller,model); var model2 = DS.PromiseObject.create({ promise: this.store.find(''model2'') }); model2.then(function() { controller.set(''model2'', model2.get(''value'')) }); } });