Collections

4

Porcentagem Traduzida

Neste capítulo, você irá:

  • Aprenda sobre a principal característica do Meteor, coleções em tempo real.
  • Entenda como a sincronização de dados funciona no Meteor.
  • Integre coleções com seus templates.
  • Transforme nosso protótipo basico em uma aplicação funcional em tempo real!
  • No primeiro capítulo, nós falamos sobre a característica central do Meteor, a sincronização automática de informação entre o cliente e o servidor.

    Neste capítulo, nós olharemos mais de perto como isso funciona, e observaremos a operação da peça chave da tecnologia que permite isso, a Meteor Collection.

    Nós estamos construindo um aplicativo social de notícias, então a primeira coisa que nós queremos é fazer uma lista de links que as pessoas postaram. Nós chamaremos cada um desses ítens de “post”.

    Naturalmente, nós precisamos armazenar esses posts em algum lugar. Meteor vem integrado com um banco de dados Mongo que roda no servidor e é o seu banco de memória persistente.

    Então, apesar do navegador de um usuário poder conter algum tipo de estado (por exemplo que página eles estão, ou o comentário que eles estão digitando agora), o servidor, e especialmente Mongo, contém a fonte de informação permanente e canônica. Por canônica, nós queremos dizer que é a mesma para todos usuários: cada usuário pode estar numa página diferente, mas a lista mestre de posts é a mesma para todos.

    Esta informação é armazenada no Meteor na Coleção. Uma coleção é um tipo de estrutura de dados especial que, através de publicações e assinaturas, toma conta de sincronizar informação em tempo real de e para o navegador de cada usuário conectado e até o banco de dados Mongo. Vamos ver como.

    Nós queremos que nossos posts sejam permanentes e compartilhados entre usuários, então nós começaremos criando uma coleção chamada Posts para armazená-los. Se você ainda não fez isso crie uma pasta collections/ na raíz do seu aplicativo, e então um arquivo posts.js dentro dela. Então adicione:

    Posts = new Meteor.Collection('posts');
    
    collections/posts.js

    Commit 4-1

    Added a posts collection

    Código dentro de pastas que não são client/ ou server/ rodarão em ambos contextos. Então a coleção Posts está disponível para tanto cliente quanto servidor. Entretanto, o que a coleção faz em cada ambiente é bem diferente.

    Usar Var Ou Não Usar Var?

    Em Meteor, a palavra chave var limita o escopo de um objeto ao arquivo atual. Aqui, nós queremos fazer a coleção Posts disponível para todo nosso aplicativo, este é o porquê nós não estamos usando a palavra chave var.

    No servidor, a coleção tem o trabalho de conversar com o banco de dados Mongo, e ler e escrever qualquer mudanças. Neste sentido, pode ser comparada a uma biblioteca de bancos de dados padrão. No cliente entretanto, a coleção é uma cópia segura de um subconjunto da real coleção canônica. A coleção do lado do cliente é constantemente e (em sua maior parte) transparentemente mantida atualiza com esse subconjunto em tempo real.

    Console vs Console vs Console

    Neste capítulo, nós começaremos a fazer uso do browser console, que não deve ser confundido com o terminal ou o Mongo shell. Aqui está uma rápida demão sobre cada um deles.

    Terminal

    The Terminal
    The Terminal
    • Chamado do seu sistema operacional.
    • Lado do Servidor console.log() chama o output aqui.
    • Prompt: $.
    • Também conhecido por: Shell, Bash

    Console do Navegador

    The Browser Console
    The Browser Console
    • Chamando de dentro do navegador, executa código JavaScript.
    • Lado do Cliente console.log() chama o output aqui.
    • Prompt: .
    • Também conhecido por: JavaScript Console, DevTools Console

    Mongo Shell

    The Mongo Shell
    The Mongo Shell
    • Chamado pelo terminal através de meteor mongo.
    • Dá acesso direto ao banco de dados do seu aplicativo.
    • Prompt: >.
    • Também conhecido por: Mongo Console

    Note que em cada caso, você não deve digitar o caracter prompt ($, , ou >) como parte do comando. E você pode assumir que qualquer linha que não comece com o prompt é o resultado do comando anterior.

    Coleções do Lado do Servidor

    No servidor, a coleção atua como uma API dentro do seu banco de dados Mongo. No código do servidor, isto permite que você escreva comandos Mongo como Posts.insert() ou Posts.update(), e eles farão mudanças na coleção posts armazenada dentro do Mongo.

    Para ver dentro do banco de dados Mongo, abra uma segunda janela do terminal (enquanto o meteor ainda está rodando na sua primeira), e vá até o diretório do aplicativo. Então, rode o comando meteor mongo para inicar um Mongo shell, no qual você pode digitar comandos Mongo (e como costumeiramente, você pode sair com o atalho de teclado ctrl+c). Por exemplo, vamos inserir um novo post:

    > db.posts.insert({title: "A new post"});
    
    > db.posts.find();
    { "_id": ObjectId(".."), "title" : "A new post"};
    
    The Mongo Shell

    Mongo no Meteor.com

    Note que quando hospedar um aplicato em *.meteor.com, você pode também acessar a Mongo shell do seu aplicativo hospedado com meteor mongo myApp.

    E já que estamos falando disso, você também pode acessar os logs do seu aplicativo digitando meteor logs myApp.

    A sintaxe do Mongo é familiar, já que utiliza uma interface JavaScript. Nós não faremos nenhuma outra manipulação de informação na Mongo shell, mas nós poderemos dar uma olhada nela de tempos em tempos para nos assegurar do que está lá.

    Coleções do Lado do Cliente

    Coleções se tornam mais interessantes do lado do cliente. Quando você declara Posts = new Meteor.Collection('posts'); no cliente, o que você está criando é um cache em tempo real no navegador da verdadeira coleção Mongo. Quando nós falamos sobre coleção do lado do cliente ser um “cache”, nós queremos dizer que ela contém um subconjunto da sua informação, e oferece um acesso muito veloz a mesma.

    É importante entender este ponto já que é fundamental para o modo como o Meteor funciona. Em geral, a coleção do lado do cliente consiste em um subconjunto de todos os documentos armazenados na coleção Mongo (até mesmo porque nós geralmente não queremos mandar todo nosso banco de dados para o cliente).

    Segundo, esses documentos estão armazenados na memória do navegador, o que significa que acessá-los é basicamente instantâneo. Então não há viagens lentas até o servidor ou ao banco de dados para coletar essa informação quando você chama Posts.find() no cliente, já que a informação já está pré-carregada.

    Introduzindo MiniMongo

    A implementação do Meteor para o Mongo no lado do cliente se chama MiniMongo. Não é uma implementação perfeita ainda, e você pode encontrar ocasionalmente características Mongo que não funcionam no MiniMongo. De qualquer forma, todas as característcas que nós cobrimos neste livro funcionam de modo similar tanto no Mongo quanto no MiniMongo.

    Comunicação Cliente-Servidor

    A peça chave para tudo isso é como a coleção do lado do cliente sincroniza sua informação com a coleção do lado do servidor com o mesmo nome ('posts' no nosso caso).

    Ao invés de explicar isto em detalhe, vamos apenas ver o que acontece.

    Comece abrindo duas janelas no navegador, e acessando o console do navegador em cada uma. Então, abra a Mongo shell na linha de comando. Neste ponto, nós devemos ver um único documento que nós criamos anteriormente em todos os três contextos.

    > db.posts.find();
    {title: "A new post", _id: ObjectId("..")};
    
    The Mongo Shell
     Posts.findOne();
    {title: "A new post", _id: LocalCollection._ObjectID};
    
    First browser console

    Vamos criar um novo post. Em uma das janelas do navegador, rode um comando insert:

     Posts.find().count();
    1
     Posts.insert({title: "A second post"});
    'xxx'
     Posts.find().count();
    2
    
    First browser console

    Sem surpresas, o post chegou na coleção local. Agora vamos checar o Mongo:

    ❯ db.posts.find();
    {title: "A new post", _id: ObjectId("..")};
    {title: "A second post", _id: 'yyy'};
    
    The Mongo Shell

    Como você pode ver, o post fez todo o caminho até o banco de dados Mongo, sem nós escrevermos uma única linha de código que conectasse o cliente até o servidor (bem, estritamente falando, nós escrevemos uma única linha de código: new Meteor.Collection('posts')). Mas isto não é tudo!

    Vá à segunda janela do navegador e digite isto no console do navegador:

     Posts.find().count();
    2
    
    Second browser console

    O post está lá também! Mesmo apesar de nós nunca termos atualizado ou sequer interagido com o segundo navegador, e nós certamente não escrevemos nenhum código para empurrar atualizações. Tudo aconteceu magicamente – e instantaneamente também, apesar que isto se tornará mais óbvio mais tarde.

    O que acontece é que a nossa coleção do lado do servidor foi informada pela nossa coleção do cliente quanto ao novo post, e tomou a missão de distribuir este post até o banco de dados Mongo e de volta para todas as outras coleções post conectadas.

    Trazer posts no console do navegador não é tão útil assim. Nós aprenderemos como escrever esta informação nos nossos templates, e no processo transformar nosso simples protótipo HTML em um aplicativo funcional em tempo real.

    Mantendo em Tempo Real

    Olhar para os contéudos das nossas Coleções no console do browser é uma coisa, mas o que nós realmente gostaríamos de mostrar é a informação, e as mundanças a esta informação, na tela. Fazendo isto nós tornaremos nosso aplicativo de uma página web com contéudo estático, em um aplicativo web em tempo real com contéudo dinâmico e mutável.

    Vamos descobrir como.

    Povoando o Banco de Dados

    A primeira coisa que nós faremos é por alguma informação no banco de dados. Nós o faremos como um arquivo demonstrativo que lê um conjunto de informação estruturada na coleção Posts quando o servidor inicia pela primeira vez.

    Primeiro, vamos ter certeza que não há nada no banco de dados. Nós usaremos meteor reset, o qual apaga seu banco de dados e reseta seu projeto. Claro, você irá querer ser bem cuidadoso com este comando uma vez que você começar a trabalhar em projeto do mundo real.

    Finalize o servidor Meteor (pressionando ctrl-c) e então, na linha de comando, rode:

    $ meteor reset
    

    O comando reset completamente limpa o banco de dados Mongo. É um comando útil em desenvolvimento, onde há uma forte possibilidade do nosso banco de dados cair em um estado inconsistente.

    Agora que nosso banco de dados está vazio, nós podemos adicionar o código seguinte que irá carregar três posts sempre que o servidor iniciar e encontrar a coleção Posts vazia:

    if (Posts.find().count() === 0) {
      Posts.insert({
        title: 'Introducing Telescope',
        author: 'Sacha Greif',
        url: 'http://sachagreif.com/introducing-telescope/'
      });
    
      Posts.insert({
        title: 'Meteor',
        author: 'Tom Coleman',
        url: 'http://meteor.com'
      });
    
      Posts.insert({
        title: 'The Meteor Book',
        author: 'Tom Coleman',
        url: 'http://themeteorbook.com'
      });
    }
    
    server/fixtures.js

    Commit 4-2

    Added data to the posts collection.

    Nós colocamos este arquivo no diretório server/, então ele nunca será lido em nenhum navegador de usuário. O código rodará imediatamente quando o servidor começar, e fará chamadas insert no banco de dados para adicionar três posts simples na nossa coleção Posts. Como nós não construímos nenhuma segurança de informação ainda, não há diferença real entre fazer isso em um arquivo que roda no servidor ou no navegador.

    Agora rode seu servidor de novo com meteor, e estes três posts serão carregados no banco de dados.

    Conectando a informação ao nosso HTML com ajudantes

    Agora, se nós abrirmos o console do navegador, nós veremos três posts carregados no MiniMongo:

     Posts.find().fetch();
    
    Browser console

    Para ter esses posts em HTML renderizado, nós podemos usar um ajudante de template. No Capítulo 3 nós vimos como o Meteor nos permite ligar um contexto de informação aos nossos templates do Spacebars para construir vistas HTML de estruturas de dados simples. Nós podemos ligar na nossa coleção informação do exato mesmo modo. Nós apenas substituiremos nosso objeto JavaScript estático postsData com uma coleção dinâmica.

    Falando nisso, sinta-se livre para deletar o código postsData. Aqui está o que posts_list.js deve se parecer agora:

    Template.postsList.helpers({
      posts: function() {
        return Posts.find();
      }
    });
    
    client/views/posts/posts_list.js

    Commit 4-3

    Wired collection into `postsList` template.

    Encontre & Traga

    No Meteor, find() retorna um cursor, que é uma fonte de informação reativa. Quando nós queremos logar seu contéudo, nós podemos então usar fetch() no cursor para transformá-lo num array.

    Dentro do aplicativo, o Meteor é inteligente o suficiente para saber como iterar sobre cursores sem precisar convertê-los explicitamente em arrays primeiro. Esta é razão de você não ver fetch() com tanta frequência em código de Meteor (e o porquê de não o usarmos no exemplo acima).

    Agora, ao invés de puxarmos uma lista de posts como um array estático de uma variável, nós retornamos um cursor para o nosso ajudante posts. Mas o que isto faz? Se nós voltarmos ao nosso navegador, nós vemos:

    Using live data
    Using live data

    Então nós podemos claramente ver que o nosso ajudante {{#each}} iterou sobre todo o nosso Posts, e os mostrou na tela. A coleção do lado do servidor puxou os posts do Mongo, passou-o pelo fio até a nossa coleção do lado do cliente, e nosso ajudante do Spacebars passou-os para o template.

    Agora, nós tomaremos um passo adiante; vamos adicionar outro post através do console:

     Posts.insert({
      title: 'Meteor Docs', 
      author: 'Tom Coleman', 
      url: 'http://docs.meteor.com'
    });
    
    Browser console

    Olhe de volta ao navegador – você deve ver isto:

    Adding posts via the console
    Adding posts via the console

    Você acabou de ver a reatividade em ação pela primeira vez. Quando nós dissêmos ao Spacebars para iterar sobre o cursor Posts.find(), ele sabia como observar este cursor quanto a mudanças, e remendar o HTML da forma mais simples para mostrar a informação correta na tela.

    Inspecionando mudanças no DOM

    Neste caso, a mudança mais simples possível foi a de adicionar mais outro <div class="post">...</div>. Se você quer ter certeza que isto é realmente o que aconteceu, abra o inspector do DOM e selecione o <div> correspondente a um dos posts existentes.

    Agora, no console JavaScript, insira mais outro post. Quando você voltar ao inspector, você verá um <div> extra, correspondente ao novo post, mais você ainda terá o mesmo <div> existente selecionado. Isto é uma forma útil de dizer quando elementos foram re-renderizados e quando eles foram deixados de lado.

    Conectando Coleções: Publicações e Assinaturas

    Até então, nós tínhamos o pacote autopublish ativado, que não foi feito para aplicações em produção. Como seu nome indica, este pacote simplesmente diz que cada coleção deve ser compartilhada na íntegra com cada cliente conectado. Isto não é o que realmente queremos, então vamos desativá-lo.

    Abra uma nova janela do terminal, e digite:

    $ meteor remove autopublish
    

    Isto tem um efeito instantâneo. Se você ver no seu navegador agora, você verá que todos os nossos posts desapareceram! Isto é porque nós estávamos dependendo do autopublish para assegurar que a nossa coleção de posts do lado do cliente fosse um espelho de todos os posts no banco de dados.

    Eventualmente nós precisaremos assegurar que nós estamos transferindo apenas os posts que o usuário de fato precisa ver (levando em conta coisas como paginação). Mas por enquanto, nós apenas configuraremos o Posts para ser publicado na íntegra.

    Para fazê-lo, nós criamos uma simples função publish() que retorna um cursor referente a todos os posts:

    Meteor.publish('posts', function() {
      return Posts.find();
    });
    
    server/publications.js

    No cliente, nós precisamos assinar a publicação. Nós apenas adicionaremos a linha seguinte ao main.js:

    Meteor.subscribe('posts');
    
    client/main.js

    Commit 4-4

    Removed `autopublish` and set up a basic publication.

    Se nós checarmos o navegador novamente, nossos posts voltaram. Ufa!

    Conclusão

    Então o que nós alcançamos? Bem, apesar de nós não termos uma interface do usuário ainda, o que nós temos agora é um aplicativo web funcional. Nós poderíamos lançar este aplicativo à Internet, e (usando o console do navegador) começar a postar novas estórias e vê-las aparecer nos navegadores de outros usuários pelo mundo todo.