forked from DiscoverMeteor/DiscoverMeteor_Ru
-
Notifications
You must be signed in to change notification settings - Fork 0
/
04-collections.md.erb
332 lines (214 loc) · 25.3 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
---
title: Коллекции
slug: collections
date: 0004/01/01
number: 4
contents: Узнаете больше о краеугольной телнолоии Meteor - коллекциях.|Поймете, как работает синхронизация данных в Meteor.|Подключите данные из коллекций к шаблонам.|Превратите простой прототип в полностью функциональное real-time приложение!
paragraphs: 72
---
В первой главе мы говорили о том, что Meteor автоматически синхронизирует данные между клиентом и сервером.
В этой главе, мы глубже рассмотрим процесс работы этого механизма, а также разберем операции с краеугольной технологией Meteor, которая делает это возможным - Meteor Collection.
Так как мы разрабатываем приложение социальных новостей, первое, что мы захотим сделать - это создать список ссылок, опубликованных людьми. Назовем каждый элемент этого списка «post».
Само собой, мы где-то должны хранить эти посты. Meteor из коробки предоставляет нам базу данных MongoDB, исполняемую на сервере - это и будет наше хранилище данных.
Хотя браузеры пользователей могут находиться в разных состояниях (скажем, на какой они странице, или какой комментарий они набирают в данный момент), на сервере, и особенно в нашей базе, данные общие для всех пользователей: пользователи могут находиться на разных страницах, но список постов, предоставляемый MongoDB, для всех одинаков.
Meteor хранит данные в Коллекциях (**Collection**). Коллекция в Meteor - это особая структура данных, которая с помощью публикаций (publications) и подписок (subscriptions) осуществляет синхронизацию данных между браузерами пользователей и MongoDB в реальном времени. Давайте посмотрим каким образом.
Мы хотим, чтобы наши ‘posts’ были общими для всех пользователей, так что мы начнем с того, что создадим коллекцию под названием Posts. Создайте директорию ‘/collections’ в корневой папке вашего приложения, и внутри создайте файл ‘posts.js’ со следующим содержанием:
~~~js
Posts = new Meteor.Collection('posts');
~~~
<%= caption "collections/posts.js" %>
<%= commit "4-1", "Added a posts collection" %>
Код вне директорий ‘/server’ и ‘/client’ будет исполняться как на сервере, так и на клиенте, так что наша коллекция 'Posts' будет доступна в обоих средах, хотя поведение коллекции на сервере и на клиенте во многом отличается.
<% note do %>
### Var или не Var?
В Meteor зона видимости переменной, определенной с помощью 'var', ограничена только текущим файлом. Т.к. мы хотим, чтобы коллекция Post была доступна для всего нашего приложения, то мы опустим это ключевое слово.
<% end %>
На сервере, коллекции ответствены за то, чтобы общаться с MongoDB, читать или записывать в нее информацию. В этом смысле можно сравнить коллекции с обычной библиотекой для работы с БД. Тогда как коллекция на клиенте - это безопасная копия части коллекции из базы данных. Коллекции на клиенте постоянно и (чаще всего) незаметно синхронизируются с коллекцией из MongoDB в реальном времени.
<% note do %>
### Консоль vs Консоль vs Консоль
В этой главе мы начнем использовать браузерную консоль, которую не нужно путать с терминалом или Mongo shell. Вот небольшое преставление каждой из них.
#### Терминал
<%= screenshot "terminal", "The Terminal" %>
- доступен в вашей оперативной системе
- данные, выведенные с помощью console.log() на сервере, отобразятся именно там
- prompt: $
- также многие называют его Shell или Bash
#### Браузерная консоль
<%= screenshot "browser-console", "The Browser Console" %>
- открывается в браузере и исполняет JavaScript
- данные, выведенные с помощью console.log() на клиенте, отобразятся именно там
- prompt: >
- также называется: JavaScript консоль, DevTools консоль
#### Mongo Shell
<%= screenshot "mongo-shell", "The Mongo Shell" %>
- открывается в терминале командами ‘meteor mongo’ либо ‘mrt mongo’
- позволяет напрямую проводить операции с базой данных
- prompt: >
- также ее называют консоль Mongo
Заметьте, что вам не нужно вводить символ prompt ($, >) как часть команды. Как вы можете заметить, каждая строка, не начинающаяся с prompt - это вывод предыдущей команды.
<% end %>
### Коллекции на сервере
На сервере коллекции работают в качестве API для наше базы MongoDB. Это позволяет нам на сервере выполнять команды вроде 'Posts.insert()' или 'Posts.update()', которые вносят изменения в коллекцию 'posts' нашей MongoDB.
Чтобы взглянуть непосредственно на нашу базу данных, откройте новую вкладку терминала (в то время как сам процесс Meteor исполняется в первой вкладке), перейдите в директорию нашего приложения и введите команду ‘meteor mongo’, чтобы войти в Mongo Shell, в которой мы сможем выполнять стандартные команды MongoDB (как обычно, выйти из консоли Mongo можно набрав ‘ctrl+c’). Для примера, давайте добавим новый пост:
~~~bash
> db.posts.insert({title: "A new post"});
> db.posts.find();
{ "_id": ObjectId(".."), "title" : "A new post"};
~~~
<%= caption "The Mongo Shell" %>
<% note do %>
### Mongo на Meteor.com
Если вы опубликовали ваше приложение на *.meteor.com, вы также можете получить доступ к серверной базе данных командой ‘meteor mongo myApp’.
И также можно вывести логи набрав meteor logs myApp.
<% end %>
Синтаксис Mongo многим знаком, т.к. он использует JavaScript. Мы не будем дальше работать с нашей БД с помощью консоли Mongo, но иногда уместно туда зайти, чтобы проверить, в каком состоянии сейчас MongoDB.
### Коллекции на клиенте
Гораздо интереснее обстоят дела с коллекциями на клиенте. Когда вы пишете 'Posts = new Meteor.Collection(‘’posts);' на клиенте, вы создаете локальную браузерную кэш-копию настоящей коллекции MongoDB. Говоря, что коллекция на клиенте - это «кэш», мы подразумеваем то, что она содержит копию части данных БД и позволяет получать к ним доступ очень быстро.
Важно понимать, что это одна из фундаментальных особенностей Meteor. Вцелом, коллекция на клиенте состоит из части всех документов коллекции из MongoDB (мы ведь не хотим отправлять на клиент всю базу данных).
Во вторых, коллекции на клиенте хранятся в памяти браузера, а это значит, что мы можем получить к ним доступ практически мгновенно. Больше никаких медленных запросов на сервер, чтобы достать данные из БД, т.к. вызывая, скажем, метод 'Posts.find()’ на клиенте, мы работаем уже с предзагруженными данными.
<% note do %>
### Представляем MiniMongo
Версия Mongo на клиенте в Meteor называется MiniMongo. Пока технология еще не доведена до совершенства, и есть некоторые функции MongoDB, которые не будут работать в MiniMongo. Несмотря на это, все функции, которые мы затрагиваем в этой книге, работают как непосредственно в MongoDB, так и в MiniMongo.
<% end %>
### Общение Клиент-Сервер
Важнейшая часть всего этого процесса - сам способ, с помощью которого коллекция на клиенте синхронизирует свои данные с одноименной коллекцией на сервере (в нашем случае ‘posts’).
Вместо того, чтобы объяснять все в деталях, давайте просто посмотрим, что произойдет.
Начнем с того, что откроем два окна браузера, и в каждом из них откроем JavaScript консоль. Далее, запускаем Mongo shell в терминале. Сейчас мы должны увидеть единственный документ, который мы создали ранее, во всех трех открытых консолях.
~~~bash
> db.posts.find();
{title: "A new post", _id: ObjectId("..")};
~~~
<%= caption "The Mongo Shell" %>
~~~js
❯ Posts.findOne();
{title: "A new post", _id: LocalCollection._ObjectID};
~~~
<%= caption "First browser console" %>
Теперь, в одном из окон браузера, давайте создадим новый пост, набрав команду:
~~~js
❯ Posts.find().count();
1
❯ Posts.insert({title: "A second post"});
'xxx'
❯ Posts.find().count();
2
~~~
<%= caption "First browser console" %>
Теперь пост создан в локальной коллекции на клиенте. Но давайте проверим и саму базу данных:
~~~bash
❯ db.posts.find();
{title: "A new post", _id: ObjectId("..")};
{title: "A second post", _id: 'yyy'};
~~~
<%= caption "The Mongo Shell" %>
Как вы видете, пост также сохранился и в нашей MongoDB, при этом мы не написали и строчки кода для этого (ну если точнее, мы все же написали одну строчку: new Meteor.Collection(‘posts’)). Но это еще не все!
Введите в консоле другого окна браузера:
~~~js
❯ Posts.find().count();
2
~~~
<%= caption "Second browser console" %>
Этот пост доступен и там! Даже несмотря на то, что мы не обновляли это окно и уж тем более не писали никакого кода для того, чтобы он там появился. Все произошло как по волшебству, и при этом практически мгновенно. Далее мы поймем, каким образом все это осуществилось.
Коллекция на клиенте сообщила коллекции на сервере, что у нее появился новый пост, и коллекция на сервере в свою очередь добавила новый пост непосредственно в базу данных Mongo, и отправила обратно ко всем остальным коллекциям post на клиент.
Получать документы через браузерную консоль - не особо полезное занятие. Мы научимся, как связывать данные с шаблонами, и превращать этим простой HTML прототип в полностью функциональное real-time приложение.
### Реализуем Real-time функциональность
Просто отображать коллекции через браузерную консоль, но совсем то, что нам нужно. То, что мы действительно хотим - это отображать данные и их изменения на экране. Этим мы превратим наше приложение из коллекции статичных страничек в real-time приложение с динамическими, постоянно меняющимися данными.
Давайте узнаем, как это сделать.
### Populating the Database
Первое, что мы сделаем - это создадим некоторые записи в нашей MongoDB. Для этого создадим fixture-файл, который загрузит структурированные данные в нашу коллекцию Posts, когда сервер впервые запустится.
Для начала давайте убедимся в том, что наша база данных пуста. Для этого используем команду 'meteor reset', которая сотрет нашу БД и перезапустит проект. Само собой, нужно быть осторожным с этой командой, как только вы начнете работать над реальным проектом.
Прервите процесс Meteor, набрав в терминале ctrl+c, и после, введите команду:
~~~bash
$ meteor reset
~~~
Эта команда полностью очистит нашу базу данных. Эта полезная команда в процессе разработки, когда велика возможность того, что база данных приходит в непригодное состояние.
Теперь, когда наша база пуста, мы можем добавить в наше приложение следующий код, который загрузит три поста в коллекцию Posts, в случае, если она пуста:
~~~js
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'
});
}
~~~
<%= caption "server/fixtures.js" %>
<%= commit "4-2", "Added data to the posts collection." %>
Мы поместили этот файл в директорию ‘/server’, так что он никогда не будет загружен в браузер пользователя. Код исполнится сразу же после того, как сервер будет запущен, и методом ‘insert’ добавит три простых поста в нашу коллекцию Posts. Т.к. мы пока не позаботились о безопасности данных, сейчас нет разницы, где этот файл исполняется, на сервере или на клиенте.
Теперь снова запустите сервер командой meteor, и эти три поста будут загружены в базу данных.
### Подключаем данные к нашим HTML шаблонам с помощью helper’ов
Теперь, открыв браузерную консоль, мы увидим, что все три поста загружены также и в MiniMongo:
~~~js
❯ Posts.find().fetch();
~~~
<%= caption "Browser console" %>
Чтобы добавить эти данные к нашем HTML шаблонам, мы можем использовать т.н. *template helpers*. В Главе 3 мы показали, каким образом Meteor позволяет привязывать контекст данных к шаблонам Handlebars, чтобы построить HTML представление, исходя из простых структур данных. Мы можем таким же образом привязать к представлениям данные из наших коллекций. Давайте заменим статичный объект 'postsData' на динамический из нашей коллекции.
Просто удалите код 'postsData', вот как теперь должен выглядеть файл 'posts_list.js':
~~~js
Template.postsList.helpers({
posts: function() {
return Posts.find();
}
});
~~~
<%= caption "client/views/posts/posts_list.js" %>
<%= highlight "2~4" %>
<%= commit "4-3", "Wired collection into `postsList` template." %>
<% note do %>
### Find и Fetch
В Meteor, метод 'find()' возвращает курсор, который является реактивным источником данных. Когда мы хотим вывести его содержимое, мы можем использовать на нем метод 'fetch()', который трансформирует его содержимое в массив.
На деле же, Meteor достаточно умен, чтобы знать, как обращаться с курсорами без нашего вмешательства, вроде трансформации курсора в массив. По-этому вы нечасто будете использовать 'fetch()' в коде Meteor-приложения (в коде нашего Microscope в том числе).
<% end %>
Теперь, вместо того, чтобы доставать из курсора список постов в виде массива, мы просто возвращаем курсор в нашем helper’е 'posts'. И чего же мы добьемся таким образом? Если вернемся в браузер, то мы увидим:
<%= screenshot "4-3", "Using live data" %>
Отчетливо видно, что наш helper '{{#each}}' успешно пробежался по всем документам в коллекции Posts и вывел их на экран. Коллекция на сервере достала посты из MongoDB, передала их в коллекцию уже на клиенте, и далее наш helper передал эти данные в шаблон.
Давайте пойдем еще дальше, добавив следующий пост через консоль:
~~~js
❯ Posts.insert({
title: 'Meteor Docs',
author: 'Tom Coleman',
url: 'http://docs.meteor.com'
});
~~~
<%= caption "Browser console" %>
Теперь вернемся в браузер, и увидим следующее:
<%= screenshot "4-4", "Adding posts via the console" %>
Вы только что увидили реактивность в действии. Когда мы сказали handlebars пробежаться по курсору 'Posts.find()', он также автоматически начал следить за состоянием этого курсора, и в случае его изменения, будет обновлять наш HTML, отображая на экране актуальные данные.
<% note do %>
### Отслеживаем изменения DOM
В данном случае, самый простой способ внести изменения - просто добавить еще один `<div class="post">...</div>`. Если вы хотите убедиться, что Meteor действительно сделал именно это, то откройте DOM inspector и выберите один из существующих постов.
Далее, добавьте с помощью консоли еще один пост. Когда вы вернетесь обратно в инспектор, то увидите еще один `<div>`, отражающий только что созданный пост, но при этом у вас останется выбранным существующий `<div>`. Это очень действенный способ проверить, какой элемент был обновлен, а какой остался нетронутым.
<% end %>
### Синхронизируем коллекции: публикации и подписки
До этого момента, у нас был включен модуль *autopublish*, который не предназначен для production-режима. Как и можно понять из его имени, этот модуль просто говорит приложению, что каждая серверная коллекция должна быть полностью синхронизирована с каждой коллекцией на клиенте. Это не совсем то, чего мы хотим, так что давайте его отключим.
Откройте новое окно терминала и введите:
~~~bash
$ meteor remove autopublish
~~~
Это произведет мгновенный эффект. Если вы сейчас откроете браузер, то увидете, что все наши посты больше не отображаются! Все это потому, что мы полагались на *autopublish*, чтобы быть уверенными в том, что коллекции на клиенте - это зеркальное отражение коллекций в нашей базе данных.
В конечном итоге мы конечно же будем передавать на клиент только те посты, которые пользователь должен видеть (это относится, например, к пагинации). Но сейчас мы просто опубликуем коллекцию 'Posts' целиком:
~~~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." %>
Если мы снова проверим браузер, то увидим, что наши посты вернулись. Ура!
### Заключение
Чего же мы в итоге добились? Хотя у нас пока нет пользовательского интерфейса, наше приложение уже полностью функционально. Мы уже можем опубликовать его в интернете, и (используя консоль браузера) начать добавлять новые посты, которые будут появляться в браузерах других пользователей по всему миру.