Коллекции

4

На данный момент переведено

В этой главе вы:

  • Узнаете о ключевой технологии Meteor - коллекциях.
  • Поймете, как работает синхронизация данных в Meteor.
  • Подключите данные из коллекций к шаблонам.
  • Превратите наш простой прототип в полностью функциональное веб-приложение!
  • В первой главе мы говорили о ключевой особенности Meteor - автоматической синхронизации данных между клиентом и сервером.

    В этой главе мы рассмотрим подробнее, как это работает, а также разберем функционирование ключевой технологии Meteor, которая делает это возможным, - Meteor Коллекции (Collections).

    Коллекция в Meteor - это особая структура данных, ответственная за постоянное хранение данных на сервере в базе данных MongoDB и обеспечивающая синхронизацию в реальном времени этих данных с браузерами всех подключенных пользователей.

    Мы хотим, чтобы наши посты были общими для всех пользователей и сохранялись на сервере, поэтому мы начнем с того, что создадим коллекцию под названием Posts для хранения постов.

    Коллекции являются ключевым элементом любого приложения, и чтобы гарантировать их загрузку в первую очередь, мы положим их в директорию lib. Создайте поддиректорию ‘collections‘ внутри lib; а в ней создайте файл ‘posts.js` со следующим содержанием:

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

    Коммит 4-1

    Added a posts collection

    Var или не Var?

    В Meteor зона видимости переменной, определенной с помощью var, ограничена текущим файлом. Так как мы хотим, чтобы коллекция Posts была доступна для всего нашего приложения, мы не будем использовать это ключевое слово.

    Хранение данных

    У веб-приложений есть три основных способа хранения данных, каждый из которых выполняет свою роль:

    • Память браузера: такие типы данных, как переменные JavaScript хранятся в памяти браузера, что означает, что они непостоянные: они являются локальными по отношению к текущей вкладке браузера и исчезнут, как только вы ее закроете.
    • Браузерное хранилище: браузеры также могут хранить данные более долгий срок используя куки (cookies) или Локальное хранилище (Local Storage). Несмотря на то, что эти данные сохраняются от сессии к сессии браузера, они являются локальными для текущего пользователя (хотя и доступны во всех вкладках), и их не так легко использовать совместно с другими пользователями.
    • Серверная база данных: старая добрая база данных - это лучшее место для постоянного хранения данных, доступных для использования многими пользователями (MongoDB является БД по умолчанию для Meteor приложений).

    Meteor использует все перечисленные методы и иногда синхронизирует данные между ними (как мы вскоре увидим). Но база данных остается “каноническим” источником данных, в котором хранится мастер-копия наших данных.

    Клиент и сервер

    Код вне директорий /server и /client будет исполняться как на сервере, так и на клиенте, так что наша коллекция Posts будет доступна в обоих средах; однако поведение коллекции на сервере и на клиенте может отличаться.

    На сервере коллекции поддерживают связь с MongoDB, производя чтение и запись любых изменений. В этом смысле коллекции можно сравнить с обычной библиотекой для работы с БД.

    Тогда как коллекция на клиенте - это выборка из канонической коллекции на сервере. Коллекции на клиенте постоянно и незаметно (чаще всего) синхронизируются с коллекциями на сервере в реальном времени.

    Консоль или Консоль или Консоль?

    В этой главе мы начнем использовать консоль браузера (browser console); ее не нужно путать с командной строкой ОС (terminal), командной строкой Meteor (Meteor shell) или командной строкой Mongo (Mongo shell). Ниже краткое описание каждой из них.

    Командная строка операционной системы

    Командная строка ОС
    Командная строка ОС
    • Вызывается из операционной системы.
    • Результат команды console.log() на сервере отображается здесь.
    • Обозначение: $.
    • Также называют Shell или Bash.

    Отладочная консоль браузера

    Консоль браузера
    Консоль браузера
    • Открывается в браузере и исполняет JavaScript.
    • Результат команды console.log() на клиенте отображается здесь.
    • Обозначение: .
    • Также называют консоль JavaScript, консоль разработчика, консоль Devtools.

    Командная строка Meteor

    Командная строка Meteor
    Командная строка Meteor
    • Вызывается из командной строки ОС при помощи команды meteor shell.
    • Дает прямой доступ к серверному коду вашего приложения
    • Обозначение: >.

    Командная строка Mongo

    Командная строка Mongo
    Командная строка Mongo
    • Открывается в терминале командами meteor mongo.
    • Позволяет напрямую проводить операции с базой данных.
    • Обозначение: >.
    • Также называют консоль Mongo.

    Заметьте, что вам не нужно вводить символ краткого обозначения ($, , или >) как часть команды. И как вы можете заметить, каждая строка, не начинающаяся с краткого обозначения, - это вывод результата предыдущей команды.

    Коллекции на сервере

    На сервере коллекции работают в качестве API для нашей базы MongoDB. Это позволяет нам выполнить команды вроде Posts.insert() или Posts.update() на сервере, которые произведут изменения в коллекции posts непосредственно в MongoDB.

    Чтобы взглянуть поближе на нашу базу данных, откройте еще одно окно командной строки (в то время как сам процесс meteor исполняется в первом окне) и перейдите в директорию нашего приложения. Затем введите команду meteor mongo для запуска консоли Mongo, в которой мы сможем выполнять стандартные команды MongoDB (как обычно, выйти из консоли Mongo можно нажав ctrl+c). Для примера, давайте добавим новый пост:

    meteor mongo
    
    > db.posts.insert({title: "A new post"});
    
    > db.posts.find();
    { "_id": ObjectId(".."), "title" : "A new post"};
    
    Командная строка Mongo

    Mongo на Meteor.com

    Если вы опубликовали ваше приложение на myApp.meteor.com, вы можете получить доступ к консоли Mongo вашего приложения при помощи команды meteor mongo myApp.

    И раз уж мы говорим про опубликованное приложение, вывести логи с сервера можно набрав meteor logs myApp.

    Синтаксис Mongo многим знаком, так как он использует JavaScript. Мы не будем дальше работать с нашей БД при помощи консоли Mongo, но иногда уместно туда зайти, чтобы проверить, в каком состоянии сейчас находится MongoDB.

    Коллекции на клиенте

    Гораздо интереснее обстоят дела с коллекциями на клиенте. Когда вы пишете Posts = new Mongo.Collection('posts'); на клиенте, вы создаете локальную браузерную кэш-копию настоящей коллекции Mongo. Когда мы говорим, что коллекция на клиенте - это «кэш», мы подразумеваем то, что она содержит выборку части данных из БД и предоставляет очень быстрый доступ к этим данным.

    Важно понимать, что это одна из фундаментальных особенностей Meteor. В общем и целом, коллекция на клиенте состоит из частичной выборки из документов коллекции Mongo на сервере (ведь мы не хотим отправлять на клиент всю базу данных целиком).

    Также важно то, что коллекции на клиенте хранятся в памяти браузера, а это значит, что мы можем получить к ним доступ практически мгновенно. Больше никаких медленных запросов на сервер, чтобы получить данные из БД, так как вызывая, скажем, метод Posts.find() на клиенте, мы работаем с уже предварительно загруженными данными.

    Представляем MiniMongo

    Версия Mongo на клиенте в Meteor называется MiniMongo. Пока технология еще не доведена до совершенства, и есть некоторые функции MongoDB, которые не будут работать в MiniMongo. Несмотря на это, все функции, которые мы затрагиваем в этой книге, работают как в MongoDB, так и в MiniMongo.

    Обмен данными клиент-сервер

    Важнейшая часть всего этого процесса - сам способ, при помощи которого коллекция на клиенте синхронизирует свои данные с одноименной коллекцией на сервере (в нашем случае 'posts').

    Вместо того чтобы объяснять все в деталях, давайте просто посмотрим, что происходит.

    Начнем с того, что откроем два окна браузера, и в каждом из них откроем консоль JavaScript. Далее, запускаем консоль Mongo в командной строке.

    Сейчас мы должны увидеть единственный документ, который мы создали ранее, во всех трех открытых консолях (обратите внимание, что пользовательский интерфейс нашего приложения все еще показывает три предыдущих поста. На время мы их проигнорируем).

    > db.posts.find();
    {title: "A new post", _id: ObjectId("..")};
    
    Командная строка Mongo
     Posts.findOne();
    {title: "A new post", _id: LocalCollection._ObjectID};
    
    Консоль первого окна браузера

    Теперь в одном из окон браузера давайте создадим новый пост, набрав команду:

     Posts.find().count();
    1
     Posts.insert({title: "A second post"});
    'xxx'
     Posts.find().count();
    2
    
    Консоль первого окна браузера

    Пост появился в локальной коллекции на клиенте. Давайте проверим MongoDB:

    ❯ db.posts.find();
    {title: "A new post", _id: ObjectId("..")};
    {title: "A second post", _id: 'yyy'};
    
    Командная строка Mongo

    Как вы видите, пост также появился и в MongoDB, при этом мы не написали ни строчки кода для этого (ну, строго говоря, мы все же написали одну строчку: new Mongo.Collection('posts')). Но это еще не все!

    Введите в консоли другого окна браузера:

     Posts.find().count();
    2
    
    Консоль второго окна браузера

    Этот пост доступен и там! Даже несмотря на то, что мы не обновляли это окно и уж тем более не писали никакого кода для того, чтобы он там появился. Все случилось само собой, как по волшебству; и к тому же мгновенно. Далее мы поймем, каким образом все это осуществилось.

    А произошло следующее: коллекция на клиенте сообщила коллекции на сервере, что у нее появился новый пост, а коллекция на сервере добавила этот пост непосредственно в базу данных Mongo; и разослала его всем остальным коллекциям post на открытых в данный момент клиентах.

    Получать документы через браузерную консоль - не особо полезное занятие. Скоро мы научимся, как связывать данные с шаблонами, и превратим наш простой HTML прототип в полностью функциональное приложение, обновляющееся в реальном времени.

    Заполнение базы данных

    Просто отображать коллекции через браузерную консоль это не совсем то, что нам нужно. То, что мы действительно хотим - это отображать сами данные и изменения в этих данных прямо на экране. Этим мы превратим наше приложение из простой статичной страницы в настоящее веб-приложение с динамическими, постоянно меняющимися данными, и обновляющееся в реальном времени.

    Первое, что мы сделаем - это создадим некоторые записи в нашей БД. Для этого создадим особый файл, который загрузит специально подготовленные нами данные в коллекцию Posts, когда сервер впервые запустится.

    Для начала давайте убедимся, что наша база данных пуста. Для этого используем команду meteor reset, которая сотрет нашу БД и перезапустит проект. Само собой, нужно быть очень осторожными с этой командой, как только вы начнете работать над реальным проектом.

    Остановите Meteor сервер, нажав в командной строке ctrl+c, и затем введите команду:

    meteor reset
    

    Эта команда полностью очистит нашу базу данных. Эта полезная команда в процессе разработки, когда велика вероятность того, что база данных придет в непригодное состояние.

    Давайте опять запустим наше Meteor приложение:

    meteor
    

    Теперь, когда наша база пуста, мы можем добавить в наше приложение следующий код, который при запуске сервера загрузит три поста в коллекцию Posts в том в случае, если она пуста:

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

    Коммит 4-2

    Added data to the posts collection.

    Мы поместили этот файл в директорию server/, так что он никогда не будет загружен в браузер пользователя. Код исполнится сразу же после того, как сервер будет запущен, и добавит три простых поста в нашу коллекцию Posts при помощи метода insert.

    Теперь снова запустите сервер командой meteor и эти три поста будут загружены в базу данных.

    Динамические данные

    Теперь, открыв браузерную консоль, мы увидим, что все три поста загружены также и в MiniMongo:

     Posts.find().fetch();
    
    Консоль браузера

    Чтобы отразить эти данные на странице, мы воспользуемся помощью нашего друга под названием “метод шаблона” (template helper).

    В Главе 3 мы видели, каким образом Meteor позволяет привязывать контекст данных к нашим шаблонам Spacebars, чтобы построить HTML представление простых структур данных. Мы можем аналогичным образом привязать данные из нашей коллекции. Мы просто заменим наш статичный JavaScript объект postsData на динамическую коллекцию.

    Для этого можете смело удалить код с ‘postsData’. Вот как теперь должен выглядеть файл posts_list.js:

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

    Коммит 4-3

    Wired collection into `postsList` template.

    Методы Find и Fetch

    В Meteor метод find() возвращает курсор (cursor), который является реактивным источником данных (reactive data source). Когда мы хотим получить его содержимое, мы можем использовать на нем метод fetch(), который трансформирует содержимое курсора в массив.

    Внутри приложения Meteor достаточно умен, чтобы знать, как перебирать содержимое курсоров не трансформируя их в массивы. Поэтому вы не часто сможете встретить fetch() в коде Meteor приложения (по этой же причине мы не использовали его в нашем коде выше).

    Теперь вместо того, чтобы получать список постов в виде статичного массива, мы возвращаем курсор, указывающией на наш метод 'posts’ (правда, на экране ничего особо не изменится, т.к. мы по прежнему используем одни и те же данные):

    Использование динамических данных
    Использование динамических данных

    Наш метод {{#each}} перебрал все документы в коллекции Posts и вывел их на экран. Коллекция на сервере получила посты из MongoDB, передала их в коллекцию на клиенте и далее наш Spacebars метод передал эти данные в шаблон.

    Давайте пойдем еще дальше, добавив еще один пост через консоль:

     Posts.insert({
      title: 'Meteor Docs',
      author: 'Tom Coleman',
      url: 'http://docs.meteor.com'
    });
    
    Консоль браузера

    Теперь вернемся в браузер и увидим следующее:

    Добавление постов через консоль
    Добавление постов через консоль

    Вы только что впервые увидели реактивность в действии. Когда мы сказали Spacebars методу перебрать данные внутри курсора Posts.find(), он также начал следить за состоянием этого курсора; в случае его изменения он будет обновлять наш HTML, отображая на экране актуальные данные.

    Отслеживая изменения в DOM

    В нашем случае, самый простой способ внести изменения - добавить еще один <div class="post">...</div>. Если вы хотите убедиться, что Meteor действительно сделал именно это, то откройте закладку DOM Inspector в окне инструменты разработчика вашего браузера и выберите <div>, относящийся к любому посту.

    Далее добавьте с помощью консоли браузера еще один пост. Когда вы вернетесь обратно на экран DOM Inspector, вы увидите еще один <div>, относящийся к новому посту, но при этом у вас останется выбранным тот же самый первоначальный <div>. Это удобный способ проверить, какой элемент был обновлен, а какой остался нетронутым.

    Соединяем коллекции: публикации и подписки

    До этого момента, у нас был подлючен пакет autopublish, который не предназначен для готовых приложений. Как можно понять из его названия, этот пакет просто говорит приложению, что каждая серверная коллекция должна быть полностью доступна каждому подключенному клиенту (опубликована). Это совсем не то, чего бы нам хотелось, так что давайте его отключим.

    Откройте новое окно командной строки и введите:

    $ meteor remove autopublish
    

    Это произведет мгновенный эффект. Если вы сейчас откроете браузер, то увидите, что все наши посты исчезли! Причина этому - мы использовали пакет autopublish для того, чтобы полностью копировать данные из нашей БД в коллекцию на клиенте.

    Рано или поздно нам придется проконтролировать, что мы передаем клиенту только те посты, которые пользователь должен видеть (при этом принимая во внимание такие вещи, как нумерация страниц (pagination)). Но сейчас мы настроим коллекцию Posts таким образом, чтобы она публиковалась целиком.

    Для этого мы создадим функцию publish(), которая возвращает курсор со ссылкой на все посты (публикация):

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

    На клиенте, в свою очередь, мы должны подписаться на эту публикацию. Просто добавьте следующую строку в файл main.js:

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

    Коммит 4-4

    Removed `autopublish` and set up a basic publication.

    Если мы снова проверим браузер, то увидим, что наши посты вернулись. Ура!

    Заключение

    Чего же мы в итоге добились? Ну, хотя у нас пока и нет пользовательского интерфейса, наше приложение уже полностью функционально. Мы можем опубликовать его в интернете и начать добавлять новые посты (используя консоль браузера), которые будут появляться в браузерах других пользователей по всему миру.