Capire Meteor Pubblica / Sottoscrivi


84

Ho una semplice app configurata che mostra un elenco di file Projects. Ho rimosso il autopublishpacchetto in modo da non inviare tutto al client.

 <template name="projectsIndex">    
   {{#each projects}}      
     {{name}}
   {{/each}}
 </template>

Quando è autopublishstato attivato, questo mostrava tutti i progetti:

if Meteor.isClient
  Template.projectsIndex.projects = Projects.find()

Dopo averlo rimosso, devo anche fare:

 if Meteor.isServer
   Meteor.publish "projects", ->
     Projects.find()
 if Meteor.isClient
   Meteor.subscribe "projects"
   Template.projectsIndex.projects = Projects.find()

Quindi, è corretto affermare che il find()metodo lato client cerca solo i record che sono stati pubblicati dal lato server? Mi ha fatto inciampare perché mi sentivo come se dovessi chiamare solo find()una volta.

Risposte:


286

Collezioni, pubblicazioni e abbonamenti sono un'area delicata di Meteor, che la documentazione potrebbe discutere in modo più dettagliato, in modo da evitare frequenti confusioni , che a volte vengono amplificate da una terminologia confusa .

Ecco Sacha Greif (coautore di DiscoverMeteor ) che spiega pubblicazioni e abbonamenti in un'unica diapositiva:

abbonamenti

Per capire correttamente perché è necessario chiamare find()più di una volta, è necessario capire come funzionano le raccolte, le pubblicazioni e gli abbonamenti in Meteor:

  1. Tu definisci le raccolte in MongoDB. Nessun Meteor ancora coinvolto. Queste raccolte contengono i record del database (anche chiamati "documenti" di entrambi Mongo e Meteor , ma un "documento" è più generale di un record di database, per esempio, una specifica di aggiornamento o di un selettore di query sono documenti troppo - JavaScript oggetti che contengono field: valuecoppie).

  2. Quindi definisci le collezioni sul server Meteor con

    MyCollection = new Mongo.Collection('collection-name-in-mongo')
    

    Queste raccolte contengono tutti i dati delle raccolte MongoDB e puoi eseguirle MyCollection.find({...}), il che restituirà un cursore (un insieme di record, con metodi per iterarli e restituirli).

  3. Questo cursore è (la maggior parte delle volte) utilizzato per pubblicare (inviare) una serie di record (chiamata "serie di record" ). Facoltativamente, puoi pubblicare solo alcuni campi di quei record. Sono i set di record ( non le raccolte) a cui i clienti si iscrivono . La pubblicazione viene eseguita da una funzione di pubblicazione , che viene chiamata ogni volta che un nuovo client si iscrive e che può assumere parametri per gestire i record da restituire (ad esempio un ID utente, per restituire solo i documenti di quell'utente).

  4. Sul client , hai raccolte Minimongo che rispecchiano parzialmente alcuni dei record dal server. "Parzialmente" perché possono contenere solo alcuni dei campi e "alcuni dei record" perché di solito si desidera inviare al client solo i record di cui ha bisogno, per velocizzare il caricamento della pagina, e solo quelli di cui ha bisogno e ha il permesso di accesso.

    Minimongo è essenzialmente un'implementazione in memoria e non persistente di Mongo in JavaScript puro. Serve come una cache locale che memorizza solo il sottoinsieme del database con cui sta lavorando questo client. Le query sul client (find) vengono servite direttamente da questa cache, senza parlare con il server.

    Queste raccolte Minimongo sono inizialmente vuote. Sono riempiti da

    Meteor.subscribe('record-set-name')
    

    chiamate. Nota che il parametro per iscriverti non è un nome di raccolta; è il nome di un set di record che il server ha utilizzato nella publishchiamata. La subscribe()chiamata sottoscrive il client a un set di record : un sottoinsieme di record dalla raccolta del server (ad esempio i 100 post del blog più recenti), con tutti o un sottoinsieme dei campi in ogni record (ad esempio solo titlee date). Come fa Minimongo a sapere in quale collezione collocare i record in arrivo? Il nome della collezione sarà collectionl'argomento usato nel pubblicare gestore di added, changed, e removedcallback, o se queste ultime non fossero (che è il caso il più delle volte), sarà il nome della collezione MongoDB sul server.

Modifica dei record

È qui che Meteor rende le cose molto convenienti: quando modifichi un record (documento) nella raccolta Minimongo sul client, Meteor aggiornerà immediatamente tutti i modelli che dipendono da esso e invierà anche le modifiche al server, che a sua volta memorizzerà le modifiche in MongoDB e le invierà ai client appropriati che hanno sottoscritto un set di record che include quel documento. Questo si chiama compensazione della latenza ed è uno dei sette principi fondamentali di Meteor .

Abbonamenti multipli

Puoi avere un gruppo di abbonamenti che inseriscono record diversi, ma finiranno tutti nella stessa raccolta sul client se provengono dalla stessa raccolta sul server, in base al loro _id. Questo non è spiegato chiaramente, ma implicito nei documenti di Meteor:

Quando ti iscrivi a un set di record, dice al server di inviare i record al client. I negozi client questi record nelle collezioni Minimongo locali, con lo stesso nome come collectionargomento usato nel pubblicare del conduttore added, changede removedcallback. Meteor metterà in coda gli attributi in entrata fino a quando non dichiarerai Mongo.Collection sul client con il nome della raccolta corrispondente.

Quello che non viene spiegato è cosa succede quando non usi esplicitamente added, changede removed, o pubblichi affatto gestori, il che è la maggior parte delle volte. In questo caso più comune, l'argomento della raccolta è (non sorprende) preso dal nome della raccolta MongoDB che hai dichiarato sul server al passaggio 1. Ma ciò significa che puoi avere diverse pubblicazioni e sottoscrizioni con nomi diversi e tutte le record finiranno nella stessa raccolta sul client. Fino al livello dei campi di primo livello , Meteor si occupa di eseguire un'unione di set tra i documenti, in modo tale che gli abbonamenti possano sovrapporsi: le funzioni di pubblicazione che inviano diversi campi di primo livello al client lavorano fianco a fianco e sul client, il documento nel raccolta sarà ilunione dei due gruppi di campi .

Esempio: più abbonamenti che riempiono la stessa collezione sul client

Hai una raccolta BlogPosts, che dichiari allo stesso modo sia sul server che sul client, anche se fa cose diverse:

BlogPosts = new Mongo.Collection('posts');

Sul client, BlogPostspuò ottenere record da:

  1. un abbonamento ai 10 post del blog più recenti

    // server
    Meteor.publish('posts-recent', function publishFunction() {
      return BlogPosts.find({}, {sort: {date: -1}, limit: 10});
    }
    // client
    Meteor.subscribe('posts-recent');
    
  2. un abbonamento ai post dell'utente corrente

    // server
    Meteor.publish('posts-current-user', function publishFunction() {
      return BlogPosts.find({author: this.userId}, {sort: {date: -1}, limit: 10});
      // this.userId is provided by Meteor - http://docs.meteor.com/#publish_userId
    }
    Meteor.publish('posts-by-user', function publishFunction(who) {
      return BlogPosts.find({authorId: who._id}, {sort: {date: -1}, limit: 10});
    }
    
    // client
    Meteor.subscribe('posts-current-user');
    Meteor.subscribe('posts-by-user', someUser);
    
  3. un abbonamento ai post più popolari

  4. eccetera.

Tutti questi documenti provengono dalla postsraccolta in MongoDB, tramite la BlogPostsraccolta sul server, e finiscono nella BlogPostsraccolta sul client.

Ora possiamo capire perché devi chiamare find()più di una volta, la seconda volta sul client, perché i documenti di tutte le sottoscrizioni finiranno nella stessa raccolta e devi recuperare solo quelli a cui tieni. Ad esempio, per ottenere i post più recenti sul client, è sufficiente eseguire il mirroring della query dal server:

var recentPosts = BlogPosts.find({}, {sort: {date: -1}, limit: 10});

Questo restituirà un cursore a tutti i documenti / record che il client ha ricevuto finora, sia i post principali che i post dell'utente. ( grazie Geoffrey ).


10
Questo è fantastico. Forse vale la pena menzionare cosa succede se lo fai BlogPosts.find({})sul client dopo esserti iscritto a entrambe le pubblicazioni, ovvero restituirà un cursore di tutti i documenti / record attualmente sul client, sia i post principali che i post dell'utente. Ho visto altre domande su SO in cui l'interrogante era confuso da questo.
Geoffrey Booth

3
Questo è fantastico. Grazie. Inoltre, la raccolta Meteor.users () diventa un po 'confusa poiché viene pubblicata automaticamente sul lato client. È possibile aggiungere un po 'alla risposta sopra per indicare la raccolta users ()?
Jimmy MG Lim

3
Anche se molto più di quanto originariamente richiesto, penso che @DVG dovrebbe contrassegnare questo fantastico articolo come risposta accettata. Grazie Dan.
physiocoder

1
Grazie @DanDascalescu, Ottima spiegazione che mi ha chiarito molto, l'unica cosa che quando seguo documenti meteorici sulle "raccolte" dopo aver letto la tua spiegazione penso BlogPostsnon sia una raccolta, è l'oggetto restituito che ha metodi come "inserisci", "aggiorna "..etc, e la vera raccolta è anche postsnel client e nel server.
UXE

4
È possibile chiamare solo il set di record a cui ti sei iscritto? Come in, è possibile ottenere direttamente il record impostato nel mio javascript, invece di interrogare localmente il db Minimongo?
Jimmy Knoot

27

Sì, find () lato client restituisce solo i documenti che si trovano sul client in Minimongo. Dai documenti :

Sul client viene creata un'istanza Minimongo. Minimongo è essenzialmente un'implementazione in memoria e non persistente di Mongo in JavaScript puro. Serve come una cache locale che memorizza solo il sottoinsieme del database con cui sta lavorando questo client. Le query sul client (find) vengono servite direttamente da questa cache, senza parlare con il server.

Come dici tu, publish () specifica quali documenti avrà il client.


1

La regola del pollice di base qui è publishei subscribednomi delle variabili dovrebbero essere gli stessi sul lato client e server.

I nomi delle raccolte su Mongo DB e lato client dovrebbero essere gli stessi.

Supponiamo che stia utilizzando la pubblicazione e la sottoscrizione per la mia raccolta denominata employeesquindi il codice sarebbe simile


lato server

Qui l'uso della varparola chiave è facoltativo (usa questa parola chiave per rendere la raccolta locale a questo file).

CollectionNameOnServerSide = new Mongo.Collection('employees');   

Meteor.publish('employeesPubSub', function() { 
    return CollectionNameOnServerSide.find({});     
});

file .js lato client

CollectionNameOnClientSide = new Mongo.Collection('employees');
var employeesData = Meteor.subscribe('employeesPubSub');

Template.templateName.helpers({
  'subcribedDataNotAvailable' : function(){
        return !employeesData.ready();
    },
   'employeeNumbers' : () =>{
       CollectionNameOnClientSide.find({'empId':1});
  }
});

file .html lato client

Qui possiamo utilizzare il subcribedDataNotAvailablemetodo helper per sapere se i dati sono pronti sul lato client, se i dati sono pronti quindi stampare i numeri dei dipendenti utilizzando il employeeNumbersmetodo helper.

<TEMPLATE name="templateName">
{{#if subcribedDataNotAvailable}}
   <h1> data loading ... </h1>
 {{else}}
  {{#each employeeNumbers }}
     {{this}}
  {{/each}}
 {{/if}}
<TEMPLATE>

0
// on the server
Meteor.publish('posts', function() {

    return Posts.find();

});

// on the client
Meteor.subscribe('posts');
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.