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 y solo cargaré el modelo de embedded: true
)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''))
});
}
});