-
Notifications
You must be signed in to change notification settings - Fork 39
/
Copy path04-collections.md.erb
357 lines (228 loc) · 28.5 KB
/
04-collections.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
---
title: Коллекции
slug: collections
date: 0004/01/01
number: 4
contents: Узнаете о ключевой технологии Meteor - коллекциях.|Поймете, как работает синхронизация данных в Meteor.|Подключите данные из коллекций к шаблонам.|Превратите наш простой прототип в полностью функциональное веб-приложение!
paragraphs: 72
---
В первой главе мы говорили о ключевой особенности Meteor - автоматической синхронизации данных между клиентом и сервером.
В этой главе мы рассмотрим подробнее, как это работает, а также разберем функционирование ключевой технологии Meteor, которая делает это возможным, - Meteor **Коллекции** (Collections).
Коллекция в Meteor - это особая структура данных, ответственная за постоянное хранение данных на сервере в базе данных MongoDB и обеспечивающая синхронизацию в реальном времени этих данных с браузерами всех подключенных пользователей.
Мы хотим, чтобы наши посты были общими для всех пользователей и сохранялись на сервере, поэтому мы начнем с того, что создадим коллекцию под названием `Posts` для хранения постов.
Коллекции являются ключевым элементом любого приложения, и чтобы гарантировать их загрузку в первую очередь, мы положим их в директорию `lib`. Создайте поддиректорию ‘collections‘ внутри `lib`; а в ней создайте файл ‘posts.js` со следующим содержанием:
~~~js
Posts = new Mongo.Collection('posts');
~~~
<%= caption "lib/collections/posts.js" %>
<%= commit "4-1", "Added a posts collection" %>
<% note do %>
### Var или не Var?
В Meteor зона видимости переменной, определенной с помощью `var`, ограничена текущим файлом. Так как мы хотим, чтобы коллекция `Posts` была доступна для всего нашего приложения, мы *не будем* использовать это ключевое слово.
<% end %>
### Хранение данных
У веб-приложений есть три основных способа хранения данных, каждый из которых выполняет свою роль:
- **Память браузера:** такие типы данных, как переменные JavaScript хранятся в памяти браузера, что означает, что они *непостоянные*: они являются локальными по отношению к текущей вкладке браузера и исчезнут, как только вы ее закроете.
- **Браузерное хранилище:** браузеры также могут хранить данные более долгий срок используя куки (cookies) или [Локальное хранилище](http://diveintohtml5.info/storage.html) (Local Storage). Несмотря на то, что эти данные сохраняются от сессии к сессии браузера, они являются *локальными* для текущего пользователя (хотя и доступны во всех вкладках), и их не так легко использовать совместно с другими пользователями.
- **Серверная база данных**: старая добрая база данных - это лучшее место для постоянного хранения данных, доступных для использования многими пользователями (MongoDB является БД по умолчанию для Meteor приложений).
Meteor использует все перечисленные методы и иногда синхронизирует данные между ними (как мы вскоре увидим). Но база данных остается "каноническим" источником данных, в котором хранится мастер-копия наших данных.
### Клиент и сервер
Код вне директорий `/server` и `/client` будет исполняться как на сервере, так и на клиенте, так что наша коллекция `Posts` будет доступна в *обоих* средах; однако поведение коллекции на сервере и на клиенте может отличаться.
На сервере коллекции поддерживают связь с MongoDB, производя чтение и запись любых изменений. В этом смысле коллекции можно сравнить с обычной библиотекой для работы с БД.
Тогда как коллекция на клиенте - это *выборка* из канонической коллекции на сервере. Коллекции на клиенте постоянно и незаметно (чаще всего) синхронизируются с коллекциями на сервере в реальном времени.
<% note do %>
### Консоль или Консоль или Консоль?
В этой главе мы начнем использовать **консоль браузера** (browser console); ее не нужно путать с **командной строкой ОС** (terminal), **командной строкой Meteor** (Meteor shell) или **командной строкой Mongo** (Mongo shell). Ниже краткое описание каждой из них.
#### Командная строка операционной системы
<%= screenshot "terminal", "Командная строка ОС" %>
- Вызывается из операционной системы.
- Результат команды `console.log()` **на сервере** отображается здесь.
- Обозначение: `$`.
- Также называют Shell или Bash.
#### Отладочная консоль браузера
<%= screenshot "browser-console", "Консоль браузера" %>
- Открывается в браузере и исполняет JavaScript.
- Результат команды `console.log()` **на клиенте** отображается здесь.
- Обозначение: `❯`.
- Также называют консоль JavaScript, консоль разработчика, консоль Devtools.
#### Командная строка Meteor
<%= screenshot "meteor-shell", "Командная строка Meteor" %>
- Вызывается из командной строки ОС при помощи команды `meteor shell`.
- Дает прямой доступ к серверному коду вашего приложения
- Обозначение: `>`.
#### Командная строка Mongo
<%= screenshot "mongo-shell", "Командная строка Mongo" %>
- Открывается в терминале командами `meteor mongo`.
- Позволяет напрямую проводить операции с базой данных.
- Обозначение: `>`.
- Также называют консоль Mongo.
Заметьте, что вам не нужно вводить символ краткого обозначения (`$`, `❯`, или `>`) как часть команды. И как вы можете заметить, каждая строка, не начинающаяся с краткого обозначения, - это вывод результата предыдущей команды.
<% end %>
### Коллекции на сервере
На сервере коллекции работают в качестве API для нашей базы MongoDB. Это позволяет нам выполнить команды вроде `Posts.insert()` или `Posts.update()` на сервере, которые произведут изменения в коллекции `posts` непосредственно в MongoDB.
Чтобы взглянуть поближе на нашу базу данных, откройте еще одно окно командной строки (в то время как сам процесс `meteor` исполняется в первом окне) и перейдите в директорию нашего приложения. Затем введите команду `meteor mongo` для запуска консоли Mongo, в которой мы сможем выполнять стандартные команды MongoDB (как обычно, выйти из консоли Mongo можно нажав `ctrl+c`). Для примера, давайте добавим новый пост:
~~~bash
meteor mongo
> db.posts.insert({title: "A new post"});
> db.posts.find();
{ "_id": ObjectId(".."), "title" : "A new post"};
~~~
<%= caption "Командная строка Mongo" %>
<% note do %>
### Mongo на Meteor.com
Если вы опубликовали ваше приложение на myApp.meteor.com, вы можете получить доступ к консоли Mongo вашего приложения при помощи команды `meteor mongo myApp`.
И раз уж мы говорим про опубликованное приложение, вывести логи с сервера можно набрав `meteor logs myApp`.
<% end %>
Синтаксис Mongo многим знаком, так как он использует JavaScript. Мы не будем дальше работать с нашей БД при помощи консоли Mongo, но иногда уместно туда зайти, чтобы проверить, в каком состоянии сейчас находится MongoDB.
### Коллекции на клиенте
Гораздо интереснее обстоят дела с коллекциями на клиенте. Когда вы пишете `Posts = new Mongo.Collection('posts');` на клиенте, вы создаете *локальную браузерную кэш-копию* настоящей коллекции Mongo. Когда мы говорим, что коллекция на клиенте - это «кэш», мы подразумеваем то, что она содержит *выборку* части данных из БД и предоставляет *очень быстрый* доступ к этим данным.
Важно понимать, что это одна из фундаментальных особенностей Meteor. В общем и целом, коллекция на клиенте состоит из *частичной выборки* из документов коллекции Mongo на сервере (ведь мы не хотим отправлять на клиент *всю* базу данных целиком).
Также важно то, что коллекции на клиенте хранятся *в памяти браузера*, а это значит, что мы можем получить к ним доступ практически мгновенно. Больше никаких медленных запросов на сервер, чтобы получить данные из БД, так как вызывая, скажем, метод `Posts.find()` на клиенте, мы работаем с уже предварительно загруженными данными.
<% note do %>
### Представляем MiniMongo
Версия Mongo на клиенте в Meteor называется MiniMongo. Пока технология еще не доведена до совершенства, и есть некоторые функции MongoDB, которые не будут работать в MiniMongo. Несмотря на это, все функции, которые мы затрагиваем в этой книге, работают как в MongoDB, так и в MiniMongo.
<% end %>
### Обмен данными клиент-сервер
Важнейшая часть всего этого процесса - сам способ, при помощи которого коллекция на клиенте синхронизирует свои данные с одноименной коллекцией на сервере (в нашем случае `'posts'`).
Вместо того чтобы объяснять все в деталях, давайте просто посмотрим, что происходит.
Начнем с того, что откроем два окна браузера, и в каждом из них откроем консоль JavaScript. Далее, запускаем консоль Mongo в командной строке.
Сейчас мы должны увидеть единственный документ, который мы создали ранее, во всех трех открытых консолях (обратите внимание, что *пользовательский интерфейс* нашего приложения все еще показывает три предыдущих поста. На время мы их проигнорируем).
~~~bash
> db.posts.find();
{title: "A new post", _id: ObjectId("..")};
~~~
<%= caption "Командная строка Mongo" %>
~~~js
❯ Posts.findOne();
{title: "A new post", _id: LocalCollection._ObjectID};
~~~
<%= caption "Консоль первого окна браузера" %>
Теперь в одном из окон браузера давайте создадим новый пост, набрав команду:
~~~js
❯ Posts.find().count();
1
❯ Posts.insert({title: "A second post"});
'xxx'
❯ Posts.find().count();
2
~~~
<%= caption "Консоль первого окна браузера" %>
Пост появился в локальной коллекции на клиенте. Давайте проверим MongoDB:
~~~bash
❯ db.posts.find();
{title: "A new post", _id: ObjectId("..")};
{title: "A second post", _id: 'yyy'};
~~~
<%= caption "Командная строка Mongo" %>
Как вы видите, пост также появился и в MongoDB, при этом мы не написали ни строчки кода для этого (ну, строго говоря, мы все же написали _одну_ строчку: `new Mongo.Collection('posts')`). Но это еще не все!
Введите в консоли другого окна браузера:
~~~js
❯ Posts.find().count();
2
~~~
<%= caption "Консоль второго окна браузера" %>
Этот пост доступен и там! Даже несмотря на то, что мы не обновляли это окно и уж тем более не писали никакого кода для того, чтобы он там появился. Все случилось само собой, как по волшебству; и к тому же мгновенно. Далее мы поймем, каким образом все это осуществилось.
А произошло следующее: коллекция на клиенте сообщила коллекции на сервере, что у нее появился новый пост, а коллекция на сервере добавила этот пост непосредственно в базу данных Mongo; и разослала его всем остальным коллекциям `post` на открытых в данный момент клиентах.
Получать документы через браузерную консоль - не особо полезное занятие. Скоро мы научимся, как связывать данные с шаблонами, и превратим наш простой HTML прототип в полностью функциональное приложение, обновляющееся в реальном времени.
### Заполнение базы данных
Просто отображать коллекции через браузерную консоль это не совсем то, что нам нужно. То, что мы действительно хотим - это отображать сами данные и изменения в этих данных прямо на экране. Этим мы превратим наше приложение из простой *статичной страницы* в настоящее *веб-приложение* с динамическими, постоянно меняющимися данными, и обновляющееся в реальном времени.
Первое, что мы сделаем - это создадим некоторые записи в нашей БД. Для этого создадим особый файл, который загрузит специально подготовленные нами данные в коллекцию `Posts`, когда сервер впервые запустится.
Для начала давайте убедимся, что наша база данных пуста. Для этого используем команду `meteor reset`, которая сотрет нашу БД и перезапустит проект. Само собой, нужно быть очень осторожными с этой командой, как только вы начнете работать над реальным проектом.
Остановите Meteor сервер, нажав в командной строке `ctrl+c`, и затем введите команду:
~~~bash
meteor reset
~~~
Эта команда полностью очистит нашу базу данных. Эта полезная команда в процессе разработки, когда велика вероятность того, что база данных придет в непригодное состояние.
Давайте опять запустим наше Meteor приложение:
~~~bash
meteor
~~~
Теперь, когда наша база пуста, мы можем добавить в наше приложение следующий код, который при запуске сервера загрузит три поста в коллекцию `Posts` в том в случае, если она пуста:
~~~js
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'
});
}
~~~
<%= caption "server/fixtures.js" %>
<%= commit "4-2", "Added data to the posts collection." %>
Мы поместили этот файл в директорию `server/`, так что он никогда не будет загружен в браузер пользователя. Код исполнится сразу же после того, как сервер будет запущен, и добавит три простых поста в нашу коллекцию `Posts` при помощи метода `insert`.
Теперь снова запустите сервер командой `meteor` и эти три поста будут загружены в базу данных.
### Динамические данные
Теперь, открыв браузерную консоль, мы увидим, что все три поста загружены также и в MiniMongo:
~~~js
❯ Posts.find().fetch();
~~~
<%= caption "Консоль браузера" %>
Чтобы отразить эти данные на странице, мы воспользуемся помощью нашего друга под названием "метод шаблона" (template helper).
В Главе 3 мы видели, каким образом Meteor позволяет привязывать *контекст данных* к нашим шаблонам Spacebars, чтобы построить HTML представление простых структур данных. Мы можем аналогичным образом привязать данные из нашей коллекции. Мы просто заменим наш статичный JavaScript объект `postsData` на динамическую коллекцию.
Для этого можете смело удалить код с 'postsData'. Вот как теперь должен выглядеть файл `posts_list.js`:
~~~js
Template.postsList.helpers({
posts: function() {
return Posts.find();
}
});
~~~
<%= caption "client/templates/posts/posts_list.js" %>
<%= highlight "2~4" %>
<%= commit "4-3", "Wired collection into `postsList` template." %>
<% note do %>
### Методы Find и Fetch
В Meteor метод `find()` возвращает *курсор* (cursor), который является [реактивным источником данных](http://docs.meteor.com/#find) (reactive data source). Когда мы хотим получить его содержимое, мы можем использовать на нем метод `fetch()`, который трансформирует содержимое курсора в массив.
Внутри приложения Meteor достаточно умен, чтобы знать, как перебирать содержимое курсоров не трансформируя их в массивы. Поэтому вы не часто сможете встретить `fetch()` в коде Meteor приложения (по этой же причине мы не использовали его в нашем коде выше).
<% end %>
Теперь вместо того, чтобы получать список постов в виде статичного массива, мы возвращаем курсор, указывающией на наш метод 'posts' (правда, на экране ничего особо не изменится, т.к. мы по прежнему используем одни и те же данные):
<%= screenshot "4-3", "Использование динамических данных" %>
Наш метод `{{#each}}` перебрал все документы в коллекции Posts и вывел их на экран. Коллекция на сервере получила посты из MongoDB, передала их в коллекцию на клиенте и далее наш Spacebars метод передал эти данные в шаблон.
Давайте пойдем еще дальше, добавив еще один пост через консоль:
~~~js
❯ Posts.insert({
title: 'Meteor Docs',
author: 'Tom Coleman',
url: 'http://docs.meteor.com'
});
~~~
<%= caption "Консоль браузера" %>
Теперь вернемся в браузер и увидим следующее:
<%= screenshot "4-4", "Добавление постов через консоль" %>
Вы только что впервые увидели реактивность в действии. Когда мы сказали Spacebars методу перебрать данные внутри курсора `Posts.find()`, он также начал следить за состоянием этого курсора; в случае его изменения он будет обновлять наш HTML, отображая на экране актуальные данные.
<% note do %>
### Отслеживая изменения в DOM
В нашем случае, самый простой способ внести изменения - добавить еще один `<div class="post">...</div>`. Если вы хотите убедиться, что Meteor действительно сделал именно это, то откройте закладку DOM Inspector в окне инструменты разработчика вашего браузера и выберите `<div>`, относящийся к любому посту.
Далее добавьте с помощью консоли браузера еще один пост. Когда вы вернетесь обратно на экран DOM Inspector, вы увидите еще один `<div>`, относящийся к новому посту, но при этом у вас останется выбранным *тот же самый* первоначальный `<div>`. Это удобный способ проверить, какой элемент был обновлен, а какой остался нетронутым.
<% end %>
### Соединяем коллекции: публикации и подписки
До этого момента, у нас был подлючен пакет `autopublish`, который не предназначен для готовых приложений. Как можно понять из его названия, этот пакет просто говорит приложению, что каждая серверная коллекция должна быть полностью доступна каждому подключенному клиенту (опубликована). Это совсем не то, чего бы нам хотелось, так что давайте его отключим.
Откройте новое окно командной строки и введите:
~~~bash
$ meteor remove autopublish
~~~
Это произведет мгновенный эффект. Если вы сейчас откроете браузер, то увидите, что все наши посты исчезли! Причина этому - мы использовали пакет `autopublish` для того, чтобы полностью копировать данные из нашей БД в коллекцию на клиенте.
Рано или поздно нам придется проконтролировать, что мы передаем клиенту только те посты, которые пользователь должен видеть (при этом принимая во внимание такие вещи, как нумерация страниц (pagination)). Но сейчас мы настроим коллекцию `Posts` таким образом, чтобы она публиковалась целиком.
Для этого мы создадим функцию `publish()`, которая возвращает курсор со ссылкой на все посты (публикация):
~~~js
Meteor.publish('posts', function() {
return Posts.find();
});
~~~
<%= caption "server/publications.js" %>
На клиенте, в свою очередь, мы должны *подписаться* на эту публикацию. Просто добавьте следующую строку в файл `main.js`:
~~~js
Meteor.subscribe('posts');
~~~
<%= caption "client/main.js" %>
<%= commit "4-4", "Removed `autopublish` and set up a basic publication." %>
Если мы снова проверим браузер, то увидим, что наши посты вернулись. Ура!
### Заключение
Чего же мы в итоге добились? Ну, хотя у нас пока и нет пользовательского интерфейса, наше приложение уже полностью функционально. Мы можем опубликовать его в интернете и начать добавлять новые посты (используя консоль браузера), которые будут появляться в браузерах других пользователей по всему миру.