Compreendendo a publicação / assinatura do Meteor

84

Eu tenho um aplicativo simples configurado que mostra uma lista de Projects. Removi o autopublishpacote para não enviar tudo para o cliente.

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

Quando autopublishativado, isso exibiria todos os projetos:

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

Com ele removido, eu tenho que fazer adicionalmente:

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

Portanto, é correto dizer que o find()método do lado do cliente pesquisa apenas registros que foram publicados do lado do servidor? Isso está me incomodando porque eu senti que só deveria ligar find()uma vez.

DVG
fonte

Respostas:

286

Coleções, publicações e assinaturas são uma área complicada do Meteor, que a documentação poderia discutir em mais detalhes, de modo a evitar confusões frequentes , que às vezes são ampliadas por terminologia confusa .

Aqui está Sacha Greif (coautor de DiscoverMeteor ) explicando publicações e assinaturas em um slide:

assinaturas

Para entender corretamente por que você precisa ligar find()mais de uma vez, você precisa entender como as coleções, publicações e assinaturas funcionam no Meteor:

  1. Você define coleções no MongoDB. Nenhum meteoro envolvido ainda. Essas coleções contêm registros de banco de dados (também chamados de "documentos" por Mongo e Meteor , mas um "documento" é mais geral do que um registro de banco de dados; por exemplo, uma especificação de atualização ou um seletor de consulta também são documentos - objetos JavaScript contendo field: valuepares).

  2. Então você define coleções no servidor Meteor com

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

    Essas coleções contêm todos os dados das coleções do MongoDB e você pode executá MyCollection.find({...})-las, o que retornará um cursor (um conjunto de registros, com métodos para iterar por meio deles e retorná-los).

  3. Este cursor é (na maioria das vezes) usado para publicar (enviar) um conjunto de registros (chamado de "conjunto de registros" ). Você pode opcionalmente publicar apenas alguns campos desses registros. São conjuntos de registros ( não coleções) que os clientes assinam . A publicação é feita por uma função de publicação , que é chamada toda vez que um novo cliente se inscreve, e que pode usar parâmetros para gerenciar quais registros retornar (por exemplo, um id de usuário, para retornar apenas os documentos desse usuário).

  4. No cliente , você tem coleções Minimongo que refletem parcialmente alguns dos registros do servidor. "Parcialmente" porque eles podem conter apenas alguns dos campos, e "alguns dos registros" porque você geralmente deseja enviar ao cliente apenas os registros de que ele precisa, para acelerar o carregamento da página, e apenas aqueles que ele precisa e tem permissão para Acesso.

    O Minimongo é essencialmente uma implementação não persistente na memória do Mongo em JavaScript puro. Ele serve como um cache local que armazena apenas o subconjunto do banco de dados com o qual este cliente está trabalhando. As consultas no cliente (localizar) são atendidas diretamente desse cache, sem falar com o servidor.

    Essas coleções do Minimongo estão inicialmente vazias. Eles são preenchidos por

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

    chamadas. Observe que o parâmetro para assinar não é um nome de coleção; é o nome de um conjunto de registros que o servidor usou na publishchamada. A subscribe()chamada inscreve o cliente em um conjunto de registros - um subconjunto de registros da coleção do servidor (por exemplo, 100 postagens de blog mais recentes), com todos ou um subconjunto dos campos em cada registro (por exemplo, apenas titlee date). Como o Minimongo sabe em qual coleção colocar os registros recebidos? O nome da coleção será o collectionargumento usado nas publicam do manipulador added, changed, e removedretornos de chamada, ou se aqueles que estão em falta (que é o caso na maioria das vezes), será o nome da coleção MongoDB no servidor.

Modificando registros

É aqui que o Meteor torna as coisas muito convenientes: quando você modifica um registro (documento) na coleção do Minimongo no cliente, o Meteor atualiza instantaneamente todos os modelos que dependem dele e também envia as alterações de volta para o servidor, que por sua vez armazenará as mudanças no MongoDB e as enviará aos clientes apropriados que se inscreveram em um conjunto de registros incluindo aquele documento. Isso é chamado de compensação de latência e é um dos sete princípios básicos do Meteor .

Múltiplas assinaturas

Você pode ter um monte de assinaturas que obtêm registros diferentes, mas todas acabarão na mesma coleção no cliente se vierem da mesma coleção no servidor, com base em seus _id. Isso não é explicado com clareza, mas está implícito nos documentos do Meteor:

Quando você se inscreve em um conjunto de registros, ele informa ao servidor para enviar registros ao cliente. O cliente armazena esses registros em coleções Minimongo locais, com o mesmo nome que o collectionargumento usado na publicação do manipulador added, changede removedretornos de chamada. O Meteor irá enfileirar atributos de entrada até que você declare Mongo.Collection no cliente com o nome de coleção correspondente.

O que não é explicado é o que acontece quando você não usar explicitamente added, changede removed, ou publicar manipuladores em tudo - que é a maior parte do tempo. Neste caso mais comum, o argumento da coleção é (sem surpresa) tirado do nome da coleção do MongoDB que você declarou no servidor na etapa 1. Mas isso significa que você pode ter diferentes publicações e assinaturas com nomes diferentes, e todos os os registros acabarão na mesma coleção no cliente. Até o nível de campos de nível superior , o Meteor toma o cuidado de realizar uma união definida entre os documentos, de modo que as assinaturas possam se sobrepor - funções de publicação que enviam diferentes campos de nível superior para o trabalho do cliente lado a lado e no cliente, o documento no coleção será ounião dos dois conjuntos de campos .

Exemplo: várias assinaturas preenchendo a mesma coleção no cliente

Você tem uma coleção BlogPosts, que declara da mesma maneira no servidor e no cliente, embora faça coisas diferentes:

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

No cliente, BlogPostspode obter registros de:

  1. uma inscrição para as 10 postagens mais recentes do blog

    // server
    Meteor.publish('posts-recent', function publishFunction() {
      return BlogPosts.find({}, {sort: {date: -1}, limit: 10});
    }
    // client
    Meteor.subscribe('posts-recent');
    
  2. uma inscrição nas postagens do usuário atual

    // 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. uma inscrição para os posts mais populares

  4. etc.

Todos esses documentos vêm da postscoleção no MongoDB, por meio da BlogPostscoleção no servidor, e terminam na BlogPostscoleção no cliente.

Agora podemos entender por que você precisa ligar find()mais de uma vez - a segunda vez no cliente, porque os documentos de todas as assinaturas vão acabar na mesma coleção, e você precisa buscar apenas aqueles de seu interesse. Por exemplo, para obter as postagens mais recentes no cliente, basta espelhar a consulta do servidor:

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

Isso retornará um cursor para todos os documentos / registros que o cliente recebeu até agora, tanto as principais postagens quanto as do usuário. ( obrigado Geoffrey ).

Dan Dascalescu
fonte
10
Isso é ótimo. Talvez valha a pena mencionar o que acontece se você fizer BlogPosts.find({})no cliente depois de assinar ambas as publicações - ou seja, ele retornará um cursor de todos os documentos / registros atualmente no cliente, tanto os principais posts quanto os do usuário. Já vi outras perguntas no SO em que o questionador ficou confuso com isso.
Geoffrey Booth
3
Isso é ótimo. obrigado. Além disso, a coleção Meteor.users () fica um pouco confusa, pois é publicada automaticamente no lado do cliente. Um pouco pode ser adicionado à resposta acima para indicar a coleção de users ()?
Jimmy MG Lim
3
Mesmo que muito mais do que originalmente solicitado, acho que @DVG deve marcar este ótimo artigo como a resposta aceita. Obrigado Dan.
fisiocoder
1
Obrigado @DanDascalescu, Ótima explicação que esclareceu muito para mim, a única coisa que ao seguir documentação de meteoros sobre "coleções" depois de ler sua explicação eu acho que BlogPostsnão é uma coleção, é o objeto retornado que tem métodos como "inserir", "atualizar "..etc, e a coleção real está postsno cliente e no servidor também.
UXE
4
É possível ligar apenas para o conjunto de registros que você assinou? É possível obter diretamente o conjunto de registros no meu javascript, em vez de consultar o banco de dados do Minimongo localmente?
Jimmy Knoot,
27

Sim, o find () do lado do cliente retorna apenas documentos que estão no cliente no Minimongo. Dos documentos :

No cliente, uma instância do Minimongo é criada. O Minimongo é essencialmente uma implementação não persistente na memória do Mongo em JavaScript puro. Ele serve como um cache local que armazena apenas o subconjunto do banco de dados com o qual este cliente está trabalhando. As consultas no cliente (localizar) são atendidas diretamente desse cache, sem falar com o servidor.

Como você disse, publish () especifica quais documentos o cliente terá.

user728291
fonte
1

A regra básica aqui é publishe subscribedos nomes das variáveis ​​devem ser os mesmos no lado do cliente e do servidor.

Os nomes das coleções no Mongo DB e no lado do cliente devem ser iguais.

Suponha que eu esteja usando publicar e assinar para minha coleção chamada employeese o código pareceria


lado do servidor

Aqui, o uso da varpalavra-chave é opcional (use esta palavra-chave para tornar a coleção local para este arquivo).

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

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

arquivo .js do lado do cliente

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

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

arquivo .html do lado do cliente

Aqui, podemos usar o subcribedDataNotAvailablemétodo auxiliar para saber se os dados estão prontos no lado do cliente, se os dados estão prontos, então imprimir os números dos funcionários usando o employeeNumbersmétodo auxiliar.

<TEMPLATE name="templateName">
{{#if subcribedDataNotAvailable}}
   <h1> data loading ... </h1>
 {{else}}
  {{#each employeeNumbers }}
     {{this}}
  {{/each}}
 {{/if}}
<TEMPLATE>
Puneeth Reddy V
fonte
0
// on the server
Meteor.publish('posts', function() {

    return Posts.find();

});

// on the client
Meteor.subscribe('posts');
Shemeer M Ali
fonte