Cómo utilizar varios modelos con una sola ruta en EmberJS / Ember Data?


Al leer los documentos, parece que tienes que (o deberías) 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, Mensajes, Comentarios y Usuarios? ¿Cómo le digo a la ruta que cargue eso?

Author: Anonymous, 2013-05-09

8 answers

Last update forever: No puedo seguir actualizando esto. Así que esto está en desuso y probablemente será de esta manera. 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 (enlace) _id para registro único o _ids para recogida. 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 haré de nuevo si algo cambia o si encuentro más problemas con el código proporcionado en esta respuesta.


Respuesta con modelos relacionados:

Para el escenario que está describiendo, me basaría en asociaciones entre modelos (configuración embedded: true) y solo carga el modelo Post en esa ruta, considerando que puedo definir una asociación DS.hasMany para el modelo Comment y DS.belongsTo asociación para el User en los 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: {[32]]}

Asociaciones entre modelos

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 se mantiene simple:

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

Así que en el PostRoute (o PostsPostRoute si estás usando resource), mis plantillas tendrán acceso al content del controlador, que es el modelo Post, por lo que puedo 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>

(véase fiddle )


Respuesta con modelos no relacionados:

Sin embargo, si su escenario es un poco más complejo de lo que describió, y/o tiene para 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 Post, mis plantillas tendrán acceso a la propiedad 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>

(véase fiddle )

 116
Author: MilkyWayJoe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:29

Usar Em.Object para encapsular varios modelos es una buena manera de obtener todos los datos en model hook. Pero no puede garantizar que todos los datos se preparen después de la representación de la vista.

Otra opción es usar Em.RSVP.hash. Combina varias promesas juntas y devuelve una nueva promesa. La nueva promesa si se resuelve después de todas las promesas se resuelven. Y setupController no es llamado hasta que la promesa sea resuelta o rechazada.

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 manera se obtienen todos los modelos antes del renderizado de vistas. Y usar Em.Object no tenga esta ventaja.

Otra ventaja es que puedes combinar promesa y no promesa. Así:

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

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


Pero no utilice 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 enlace de clic generado por link-to con modelos, el modelo se pasa directamente a esa ruta. En este caso el gancho model es no se llama y en setupController se obtiene el modelo link-to le dan.

Un ejemplo es como este:

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 link-to código, el post es un modelo:

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

Las cosas se vuelven interesantes aquí. Cuando se utiliza url /post/1 para visitar la página, se llama al gancho model, y setupController obtiene el objeto plano cuando se resuelve promise.

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

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

 49
Author: darkbaby123,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-19 14:10:08

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

Digamos que tienes 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 " splash" el contenido en la parte superior de la vista es pequeño y se cargará rápidamente, por lo que puede cargarlo normalmente:

Crear una ruta main-page.js:

import Ember from 'ember';

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

Luego puede crear una plantilla de Manillar 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>

Así que digamos que en tu plantilla quieres tener secciones separadas sobre tu relación de amor/odio con cheese, cada una (por alguna razón) respaldada por su propio modelo. Tiene muchos registros en cada modelo con amplios detalles relacionados con cada razón, sin embargo, le gustaría el contenido en la parte superior para renderizar 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. Ya 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á que estamos usando el PromiseProxyMixin aquí, lo que hace que el controlador sea consciente de la promesa. Cuando el controlador se inicializa, indicamos que la promesa debería estar cargando el modelo love-cheese a través de datos Ember. Necesitará establecer esta propiedad en la propiedad 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 tu plantilla, podrás renderizar contenido diferente dependiendo del estado de promesa. Cuando su página se cargue inicialmente, su sección" Razones por las que amo el Queso " se mostrará Loading.... Cuando se carga la promesa, mostrará todas las razones asociadas para cada registro de su modelo.

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

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 el ejemplo de Novelys anterior aún más relevante. Si no, lo anterior debería funcionar bien para usted.

 13
Author: SwarmIntelligence,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-29 16:19:04

Esto podría no ser una buena 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 que ayude

 8
Author: intuitivepixel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-09 15:24:36

Gracias a todas las otras excelentes respuestas, he creado 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 estándar model, por lo que aún lo definirías 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);
  }
});
 4
Author: Christopher Sansone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-18 01:35:05

Https://stackoverflow.com/a/16466427/2637573 está bien para modelos relacionados. Sin embargo, con la versión reciente de los datos de Ember CLI y Ember, hay 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 model2, utilice DS.PromiseObject en 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'))
    });
  }
});
 2
Author: AWM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:45

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

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

Devuelve

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

El 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 enlaces de vuelta son importantes para que se establezca la relación completa. Espero que eso ayude a alguien.

 1
Author: philn5d,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-22 02:44:59

Puedes usar los ganchos beforeModel o afterModel ya que siempre se llaman, incluso si model no se llama porque estás usando segmentos dinámicos.

Según el enrutamiento asíncrono docs:

El gancho modelo cubre muchos casos de uso para transiciones pause-on-promise, pero a veces necesitará la ayuda de los ganchos relacionados beforeModel y afterModel. La razón más común de esto es que si estás haciendo la transición a una ruta con un segmento de URL dinámico a través de {{link-to}} o transitionTo (a diferencia de una transición causada por un cambio de URL), el modelo para la ruta a la que está haciendo la transición ya se ha especificado (por ejemplo, {{#link-to 'article' article}} o esto.transitionTo ('article', article)), en cuyo caso no se llamará al gancho del modelo. En estos casos, necesitará hacer uso del gancho beforeModel o afterModel para albergar cualquier lógica mientras el enrutador aún está reuniendo todos los modelos de la ruta para realizar una transición.

Así lo dices tener una propiedad themes en su SiteController, usted 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'));
}
 0
Author: Ryan D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-27 00:13:34