Pubblicazioni avanzate

Approfondimento 13.5

Percentuale Tradotta

In questo capitolo imparerai:

  • Impare delle tecniche più avanzate per manipolare le pubblicazioni.
  • Guarda quanto possono essere flessibili le pubblicazioni e le sottoscrizioni.
  • A questo punto dovresti avere una buona comprensione di come pubblicazioni e sottoscrizioni interagiscono. Cerchiamo quindi di sbarazzarci delle ruote di supporto ed esaminiamo un paio di scenari avanzati.

    Pubblicare una collezione più volte

    Nel nostro primo approfondimento sulle pubblicazioni, abbiamo visto alcune delle tecniche più comuni su pubblicazioni e sottoscrizioni, ora abbiamo imparato come la funzione _publishCursor le ha rese molto facili da implementare sui nostri siti.

    Iniziamo ricordando cosa _publishCursor fa per noi esattamente: prende tutti i documenti che corrispondono ad un certo cursore e li invia alla collezione con lo stesso nome sul client. Da notare che il nome della pubblicazione non è coinvolto.

    Questo significa che possiamo avere più di una pubblicazione di collegamento tra le collezioni sul client e sul server.

    Abbiamo già incontrato questo pattern nel nostro capitolo sulla paginazione, quando abbiamo pubblicato un sottoinsieme paginato di tutti i post in aggiunta al post corrente visualizzato.

    Un altro simile caso d'uso è quello di pubblicare una panoramica di un grande set di documenti, come anche tutti i dettagli di un singolo elemento:

    Pubblicare una collezione due volte
    Pubblicare una collezione due volte
    Meteor.publish('allPosts', function() {
      return Posts.find({}, {fields: {title: true, author: true}});
    });
    
    Meteor.publish('postDetail', function(postId) {
      return Posts.find(postId);
    });
    

    Ora, quando il client sottoscrive queste due pubblicazioni (utilizzando autorun per garantire che il giusto postID sia stato inviato alla sottoscrizione postDetail), la sua collezione 'posts' viene popolata da due sorgenti: un elenco di titoli, i nomi degli autori della prima sottoscrizione e tutti i dettagli di un post dalla seconda.

    Si potrebbe notare che il post pubblicato da postDetail viene anche pubblicato da allPosts (anche se con un solo sottoinsieme delle sue proprietà). Tuttavia, Meteor si prende cura della sovrapposizione unendo i campi e garantendo che nessun post sia duplicato.

    Questa è una bella cosa, perché ora quando renderizziamo l'elenco sommario dei post, gestiamo degli oggetti di dati che hanno sufficienti dati per mostrare quello che ci serve. Quando renderizziamo la pagina per un singolo post, quindi, abbiamo tutto quello che serve per mostrarlo. Certo, dovremo verificare che sul client tutti i campi siano disponibili su tutti i post - questa è una situazione comune!

    Facciamo presente che non si è limitati al solo variare le proprietà del documento, si potrebbero benissimo pubblicare le stesse proprietà di entrambe le pubblicazioni, ma ordinare gli elementi in modo diverso.

    Meteor.publish('newPosts', function(limit) {
      return Posts.find({}, {sort: {submitted: -1}, limit: limit});
    });
    
    Meteor.publish('bestPosts', function(limit) {
      return Posts.find({}, {sort: {votes: -1, submitted: -1}, limit: limit});
    });
    
    server/publications.js

    Sottoscrivere una pubblicazione più volte

    Abbiamo appena visto come sia possibile pubblicare una singola collezione più di una volta. Allo stesso modo si può realizzare un risultato molto simile con un altro pattern: la creazione di una singola pubblicazione, ma sottoscrivendoci più volte.

    In Microscope, sottoscriviamo la pubblicazione posts più volte, ma Iron Router attiva e disattiva ogni sottoscrizione per noi. Non c'è alcun motivo per cui non ci si possa sottoscrivere più volte contemporaneamente.

    Ad esempio, diciamo che abbiamo voluto caricare sia i più recenti che i migliori post in memoria allo stesso tempo:

    Sottoscriversi due volte a una pubblicazione
    Sottoscriversi due volte a una pubblicazione

    Stiamo impostando una singola pubblicazione:

    Meteor.publish('posts', function(options) {
      return Posts.find({}, options);
    });
    

    E poi sottoscriviamo questa pubblicazione più volte. In realtà questo è più o meno quello che stiamo facendo in Microscope:

    Meteor.subscribe('posts', {submitted: -1, limit: 10});
    Meteor.subscribe('posts', {baseScore: -1, submitted: -1, limit: 10});
    

    Quindi cosa sta succedendo esattamente? Ogni browser sta aprendo due sottoscrizioni differenti, ciascuna che si connette alla stessa pubblicazione sul server.

    Ogni sottoscrizione fornisce diversi argomenti per tale pubblicazione ma, fondamentalmente, ogni volta un (diverso) set di documenti viene raccolto dalla collezione posts e trasferito sulla collezione client-side.

    Collezioni multiple in una singola sottoscrizione

    A differenza dei database relazionali più tradizionali come MySQL, che fanno uso di join, i database NoSQL come Mongo sono tutti su denormalizzazione e incorporamento (embedding). Vediamo come funziona nel contesto di Meteor.

    Diamo uno sguardo ad un esempio concreto. Abbiamo aggiunto commenti ai nostri post e, finora, siamo stati felici di pubblicare solo i commenti sul singolo post che l'utente sta guardando.

    Tuttavia, supponiamo di voler mostrare i commenti su tutti i post in prima pagina (tenendo presente che questi post cambieranno nel momento in cui verranno impaginati, essendo solo quelli in prima pagina a dover mostrare i commenti). Questo caso d'uso rappresenta un buon motivo per l'incorporamento dei commenti nei post, ed in effetti è ciò che ci ha spinto a denormalizzare il contatore dei commenti.

    Naturalmente potremmo sempre solo incorporare i commenti nei post, sbarazzandoci della collezione Comments. Ma come abbiamo visto nel capitolo Denormalizzazione, così facendo perderemmo alcuni vantaggi supplementari di lavorare con una collezione separata.

    Scopriamo ora un trucco che coinvolge le sottoscrizioni e che permette di inserire i nostri commenti preservando le collezioni separate.

    Supponiamo che, insieme con la nostra lista di post in prima pagina, vogliamo sottoscriverci ad una lista dei primi 2 commenti per ogni post.

    Sarebbe difficile realizzare questo con una pubblicazione commenti indipendente, soprattutto se l'elenco dei post fosse limitato in qualche modo (per esempio, gli ultimi 10). Dovremmo scrivere una pubblicazione che assomiglierebbe a qualcosa di simile:

    Due collezioni in una sottoscrizione
    Due collezioni in una sottoscrizione
    Meteor.publish('topComments', function(topPostIds) {
      return Comments.find({postId: topPostIds});
    });
    

    Questo sarebbe un problema dal punto di vista delle prestazioni, visto che la pubblicazione dovrebbe essere abbattuta e ristabilita ogni volta l'elenco dei topPostIds cambia.

    C'è però un modo per aggirare questo ostacolo. Abbiamo appena sfruttato il non poter avere più di una pubblicazione per collezione, ma possiamo anche avere più di una collezione per pubblicazione:

    Meteor.publish('topPosts', function(limit) {
      var sub = this, commentHandles = [], postHandle = null;
    
      // send over the top two comments attached to a single post
      function publishPostComments(postId) {
        var commentsCursor = Comments.find({postId: postId}, {limit: 2});
        commentHandles[post._id] =
          Meteor.Collection._publishCursor(commentsCursor, sub, 'comments');
      }
    
      postHandle = Posts.find({}, {limit: limit}).observeChanges({
        added: function(id, post) {
          publishPostComments(post._id);
          sub.added('posts', id, post);
        },
        changed: function(id, fields) {
          sub.changed('posts', id, fields);
        },
        removed: function(id) {
          // stop observing changes on the post's comments
          commentHandles[id] && commentHandles[id].stop();
          // delete the post
          sub.removed('posts', id);
        }
      });
    
      sub.ready();
    
      // make sure we clean everything up (note `_publishCursor`
      //   does this for us with the comment observers)
      sub.onStop(function() { postsHandle.stop(); });
    });
    

    Possiamo notare che non stiamo restituendo nulla in questa pubblicazione, visto che trasmettiamo manualmente messaggi alla nostra sub (via .added() e simili). Non abbiamo quindi bisogno di chiedere a _publishCursor di farlo per noi restituendo un cursore.

    Ora, ogni volta che pubblichiamo un post, pubblichiamo anche automaticamente i primi due commenti ad esso collegati, il tutto con una sola chiamata di sottoscrizione!

    Sebbene Meteor non renda ancora questo approccio molto semplice, puoi anche guardare nel pacchetto publish-with-relations su Atmosphere, che mira a rendere questo pattern più facile da usare.

    Collegare collezioni differenti

    Che altro può darci la nostra conoscenza ritrovata sulla flessibilità delle sottoscrizioni? Beh, se non usiamo _publishCursor, non abbiamo bisogno di seguire il vincolo che la collezione sorgente sul server deve avere lo stesso nome della collezione di destinazione sul client.

    Una collezione per due sottoscrizioni
    Una collezione per due sottoscrizioni

    Una ragione per cui vorremmo fare questo è l’ ereditarietà su singola tabella (Single Table Inheritance).

    Supponiamo che abbiamo voluto fare riferimento a vari tipi di oggetti da parte dei nostri post, ognuno dei quali memorizza campi comuni, ma anche che differivano leggermente nel contenuto. Ad esempio, potremmo costruire un motore di blogging Tumblr-like, dove ogni post ha la solita ID, timestamp e titolo; ma in più potrebbe essere caratterizzato da un'immagine, un video, un collegamento o semplicemente del testo.

    Potremmo memorizzare tutti questi oggetti in una singola collezione 'risorse', utilizzando un attributo tipo per indicare quale tipo di oggetto che sono. (video, immagine, link, ecc.)

    Se avessimo una singola collezione risorse sul server, potremmo trasformare questa collezione unica in multiple (Video, Immagini, ecc) collezioni sul client col seguente pizzico di magia:

      Meteor.publish('videos', function() {
        var sub = this;
    
        var videosCursor = Resources.find({type: 'video'});
        Meteor.Collection._publishCursor(videosCursor, sub, 'videos');
    
        // _publishCursor doesn't call this for us in case we do this more than once.
        sub.ready();
      });
    

    Stiamo dicendo a _publishCursor di pubblicare i nostri video come il cursore farebbe, ma invece di pubblicare la collezione di risorse sul client, pubblichiamo da “‘risorse’” a 'video'.

    È una buona idea farlo? Non è nostro compito giudicare. In ogni caso, è bene sapere che è possibile farlo per poter scegliere il metodo migliore per la nostra applicazione, utilizzando Meteor al massimo!