85 votes

Comment utiliser plusieurs modèles avec une seule route en EmberJS / Ember Data ?

D'après la lecture de la documentation, il semble que vous deviez (ou devriez) affecter un modèle à un itinéraire de la manière suivante :

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

Que faire si j'ai besoin d'utiliser plusieurs objets dans un certain parcours, par exemple les articles, les commentaires et les utilisateurs ? Comment puis-je dire à l'itinéraire de charger ces objets ?

117voto

MilkyWayJoe Points 6251

Mise à jour : Dans ma réponse initiale, j'ai dit d'utiliser embedded: true dans la définition du modèle. C'est incorrect. Dans la révision 12, Ember-Data s'attend à ce que les clés étrangères soient définies avec un suffixe ( enlace ) _id pour un seul enregistrement ou _ids pour la collecte. Quelque chose de similaire à ce qui suit :

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

J'ai mis à jour le fiddle et je le referai si quelque chose change ou si je trouve d'autres problèmes avec le code fourni dans cette réponse.


Réponse avec des modèles connexes :

Pour le scénario que vous décrivez, je m'appuierais sur associations entre les modèles (réglage embedded: true ) et ne charger que le Post dans cette voie, en considérant que je peux définir une DS.hasMany association pour le Comment et DS.belongsTo association pour le User dans les deux Comment y Post modèles. Quelque chose comme ça :

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

Cette définition produirait quelque chose comme ce qui suit :

Associations between models

Avec cette définition, chaque fois que je find un message, j'aurai accès à une collection de commentaires associés à ce message, ainsi qu'à l'auteur du commentaire et à l'utilisateur qui est l'auteur du message, puisqu'ils sont tous intégrés . L'itinéraire reste simple :

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

Ainsi, dans le PostRoute (ou PostsPostRoute si vous utilisez resource ), mes modèles auront accès à l'interface du contrôleur. content qui est le Post modèle, afin que je puisse me référer à l'auteur, simplement en tant que 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>

(voir violon )


Réponse avec des modèles sans rapport :

Cependant, si votre scénario est un peu plus complexe que ce que vous avez décrit, et/ou ont pour utiliser (ou interroger) différents modèles pour un itinéraire particulier, je recommande de le faire en Route#setupController . Par exemple :

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

Et maintenant, lorsque je suis dans la route de la poste, mes modèles auront accès à l'interface de l'utilisateur. user dans le contrôleur tel qu'il a été mis en place dans setupController crochet :

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

(voir violon )

49voto

darkbaby123 Points 571

Utilisation de Em.Object pour encapsuler de multiples modèles est un bon moyen d'obtenir toutes les données en model crochet. Mais il ne peut pas garantir que toutes les données sont préparées après le rendu de la vue.

Un autre choix consiste à utiliser Em.RSVP.hash . Il combine plusieurs promesses ensemble et retourne une nouvelle promesse. La nouvelle promesse est résolue après que toutes les promesses aient été résolues. Et setupController n'est pas appelé tant que la promesse n'est pas résolue ou rejetée.

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 cette façon, vous obtenez tous les modèles avant le rendu de la vue. Et en utilisant Em.Object n'a pas cet avantage.

Un autre avantage est que vous pouvez combiner promesse et non-promesse. Comme ceci :

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

Consultez cette page pour en savoir plus sur Em.RSVP : https://github.com/tildeio/rsvp.js


Mais n'utilisez pas Em.Object o Em.RSVP solution si votre route a des segments dynamiques

Le principal problème est link-to . Si vous changez d'url en cliquant sur le lien généré par link-to avec des modèles, le modèle est transmis directement à cette route. Dans ce cas, le model n'est pas appelé et dans setupController vous obtenez le modèle link-to vous donner.

Un exemple est le suivant :

Le code de la route :

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

Et link-to code, le poste est un modèle :

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

Les choses deviennent intéressantes ici. Lorsque vous utilisez url /post/1 pour visiter la page, le model est appelé, et setupController récupère l'objet simple lorsque la promesse est résolue.

Mais si vous visitez la page en cliquant link-to lien, il passe post modèle à PostRoute et l'itinéraire ignorera model crochet. Dans ce cas setupController obtiendra le post modèle, bien sûr, vous ne pouvez pas obtenir l'utilisateur.

Veillez donc à ne pas les utiliser dans les routes avec des segments dynamiques.

8voto

intuitivepixel Points 17514

Il ne s'agit peut-être pas de la meilleure pratique et d'une approche naïve, mais elle montre de manière conceptuelle comment procéder pour avoir plusieurs modèles disponibles sur une route centrale :

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

J'espère que cela vous aidera

4voto

Grâce à toutes les autres excellentes réponses, j'ai créé un mixin qui combine les meilleures solutions ici dans une interface simple et réutilisable. Il exécute un Ember.RSVP.hash en afterModel pour les modèles que vous spécifiez, puis injecte les propriétés dans le contrôleur dans la section setupController . Il n'interfère pas avec la norme model crochet, donc vous le définissez toujours comme normal.

Exemple d'utilisation :

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

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

1voto

philn5d Points 31

Ajouté à la réponse de MilkyWayJoe, merci btw :

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

renvoie à

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

L'auteur serait

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

commentaires

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

... Je crois que les liens de retour sont importants pour que la relation complète soit établie. J'espère que cela aidera quelqu'un.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X