-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy path04-collections.md.erb
executable file
·331 lines (214 loc) · 26.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
---
title: Колекція
slug: collections
date: 0004/01/01
number: 4
contents: Ознайомитесь із основною особливістю Meteor – realtime-колекціями.|Зрозумієте як працює синхронізація баних у Meteor.|Підключите колекції до наших шаблонів.|Зміните наш базовий пропотит на додаток, що функціонує у realtime режимі!
paragraphs: 78
---
У першому розділі ми говорили про базовий елемент Meteor та як Метеор автоматично синхронізує дані між клієнтом і сервером.
У цьому розділі, ми побачимо як це працює і розглянемо ключову базову технологію, яка робить це можливим – `Meteor.Collection`.
Оскільки ми розробляємо додаток соціальних новин, то перше, що ми хочемо зробити – це створити список посилань, які люди публікують. Ми називатимемо кожен з цих пунктів "post".
Звичайно, ми повинні десь зберігати ці "posts". На момент написання цього, Meteor поставляється в комплекті з базою даних MongoDB, що виконується на сервері - це і буде наше *постійне* сховище даних.
Браузери користувачів можуть перебевати в відповідних станах на сервері кожного разу змінюючи його відровідно до взаємодії із користувачем (наприклад, на якій вони сторінці зараз користувач або який коментар вони наразі набирають). Особливо цей стан відображається в MongoDB, яка включає в себе *канонічні*, тобто загальнодоступні для всіх користувачів дані: користувачі можуть бути на різних сторінках, але загальний список сторінок для всіх однаковий. Тобто користувач 1 може переглядати статтю на сторінці №5, коли користувач 2 може перебувати на сторінці, яка відображає статтю №9, для обох користувачів внутрішній стан браузера буде різний, коли список статтів для обох користувачів буде одинаковий, тобто кожен матиме змогу передивитися кожну статтю на сайті.
Ці дані зберігаються в Meteor в **Колекції** (Collection). Колекція – це особлива структура даних, яка за допомогою публікацій (publications) в реальному часі здійснює синхронізацію даних між браузерами користувачів і MongoDB. Давайте подивимося як саме це відбувається.
Ми хочемо, щоб наші пости, будучи створеними, були постійними і одинаковими для всіх користувачів, так що ми почнемо з того, що створимо колекцію під назвою `Posts` для їх збереження. Якщо у вас до цього часу ще немає директорії з `collections/` у кореневій папці вашого додатку, то створіть всередині файл `posts.js` і додайте код:
~~~js
Posts = new Meteor.Collection('posts');
~~~
<%= caption "collections/posts.js" %>
<%= commit "4-1", "Додана Колекція 'Post'" %>
Код всередині папок, які не є `client/` або `server/` буде працювати в *обох* середовищах. Таким чином, колекція `Posts` буде доступною для клієнта і сервера. Однак поведінка колекції дуже відрізнятиметься у кожному середовищі.
<% note do %>
### Var чи не Var?
У Meteor ключове слово `var` обмежує зону видимості змінної поточним файлом. Оскільки ми хочемо, щоб колекція `Posts` була доступною для всього нашого додатку, то ми *не* будемо вживати це ключове слово `var`.
<% end %>
На сервері, колекції повинні спілкуватися з базою даних MongoDB, читати чи записувати будь-які зміни. У цьому сенсі її можна порівняти із звичайною бібліотекою для роботи з базою даних. Проте колекція на клієнті – це *безпечна* копія *підмножини* реальної, загальнодоступної колекції що є на сервері. Колекції на клієнті постійно і (найчастіше) непомітно синхронізуються з тією підмножиною в реальному часі.
<% note do %>
### Console vs Console vs Console
У цьому розділі ми почнемо використовувати **консолі браузера**, який не слід плутати з **терміналом** (terminal) або з **Mongo shell**. Ось короткий підручник по кожному з них.
#### Terminal
<%= screenshot "terminal", "The Terminal" %>
- Викликається з вашої операційної системи.
- **Дані, виведені** за допомогою `console.log()` **на сервері**, відобразяться тут.
- Prompt: `$`.
- Також відомий як Shell, Bash
#### Браузерна консоль
<%= screenshot "browser-console", "The Browser Console" %>
- Викликається всередині браузера та виконує код JavaScript.
- Дані, **виведені** за допомогою `console.log()`**на клієнті**, відобразяться тут.
- Prompt: `❯`.
- Також відомий як JavaScript Console, DevTools Console
#### Mongo Shell
<%= screenshot "mongo-shell", "The Mongo Shell" %>
- Відкривається в терміналі командами `meteor mongo` або `mrt mongo`.
- Дозволяє безпосередньо проводити операції з базою.
- Prompt: `>`.
- Також відомий як Mongo Console
Зауважте, що вам не потрібно вводити символ prompt (`$`, `❯`, or `>`) як частину команди. Як ви можете помітити, кожен рядок, що *не* починається з prompt - це вивід попередньої команди.
<% end %>
### Колекції на сервері
На сервері колекції працюють в якості API для вашої бази даних MongoDB. Це дозволяє нам на сервері виконувати Mongo-команди такі як: `Posts.insert()` або `Posts.update()`, які вноситимуть зміни в колекцію `Posts` у базі даних.
Щоб заглянути всередину нашої бази даних, відкрийте нову вкладку терміналу (у той час як сам `meteor` виконується в першій вкладці), перейдіть в директорію вашого додатку. Потім введіть команду `meteor mongo`, щоб увійти в Монго Shell. Після запуску Mongo Shell ми зможемо виконувати стандартні команди MongoDB (і як зазвичай, щоб вийти натисніть `ctrl+c`). Для прикладу, давайте додамо новий 'post':
~~~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 `meteor mongo myApp`.
У цей час ви також можете вивести логи вашого додатку, ввівши `meteor logs myApp`.
<% end %>
Синтаксис Mongo знайомий багатьом, тому що він використовує інтерфейс JavaScript. Надалі ми більше не будемо працювати з нашою базою даних за допомогою консолі Mongo, але іноді туди доречно зайти, щоб перевірити поточний стан MongoDB.
### Колекції на клієнті
Набагато цікавіше працювати з колекціями на стороні клієнта. Коли ви виконуєте команду `Posts = new Meteor.Collection('posts');` на клієнті, ви створюєте локальну браузерну MongoDB кеш-копію цієї колекції. Коли ми говоримо, що колекція на клієнті – це "кеш", ми маємо на увазі те, що вона містить підмножину даних і дозволяє отримувати до них доступ дуже *швидко*.
Важливо розуміти, що це фундаментальна особливість Meteor. В цілому, колекція на клієнті складається з частини всіх документів колекції, що знаходяться з MongoDB (зрештою, ми, як правило, не хочемо відправляти *всю* базу даних на клієнт).
По-друге, ці документи зберігаються *в пам'яті браузера*, що означає, що ми практично миттєво можемо отримати доступ до них. Тож, більше ніяких повільних запитів на сервер, щоб дістати дані з бази даних, коли ви викликаєте `Posts.find()` на клієнті, оскільки дані вже передзавантажені.
<% note do %>
### Знайомство з MiniMongo
В Meteor, Mongo на клієнті називається MiniMongo. Це поки не досконала розробка і ви можете зіткнутись з деякими функціями Mongo, які не працюють в MiniMongo. Незважаючи на це, всі функції, яких ми торкаємося у цій книзі, працюють однаково як в MongoDB, так і в MiniMongo.
<% end %>
### Спілкування Клієнт-Сервер
Найважливіша частина всього – це спосіб, за допомогою якого колекція на клієнті синхронізує свої дані із однойменною колекцією на сервері (в нашому випадку `posts`).
Замість того, щоб детальніше це пояснювати, давайте просто подивимося, як це відбувається.
Почнемо з того, що відкриємо два вікна браузера, і в кожному з них відкриємо консоль браузера. Далі, запускаємо Mongo shell у командному рядку. Зараз ми повинні побачити єдиний документ, який ми створили раніше, у всіх трьох відкритих контекcтах.
~~~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" %>
Тепер давайте створимо новий post, ввівши в одному з вікон браузера команду:
~~~js
❯ Posts.find().count();
1
❯ Posts.insert({title: "A second post"});
'xxx'
❯ Posts.find().count();
2
~~~
<%= caption "First browser console" %>
На даний момент post опублікований у локальній колекції. Тепер давайте перевіримо MongoBD:
~~~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')`). Ви мабуть помітили що ми передали Meteor'у '_id' замість існуючого в MongoDB ObjectId. Але це ще не все!
Відкрийте ще браузер ще в одному вікні та ведіть в його консолі наступне:
~~~js
❯ Posts.find().count();
2
~~~
<%= caption "Second browser console" %>
Цей пост там також! Навіть якщо ми не оновлювали це вікно і не писали ніякого коду для того, щоб він там з'явився. Це відбулося за домопогою автоматичної синхронізації, і при цьому практично миттєво, детальніше яким чином все це здійснилося ми дізнаємось пізніше.
Сталося так, що колекція на клієнті повідомила колекцію на сервері, що у неї з'явився новий post, і колекція на сервері в свою чергу додала новий пост безпосередньо в базу даних MongoBD, і відправила назад до всіх інших колекцій в інших браузерах, які пов’язані з `post` на клієнті.
Отримувати документи через браузерну консоль - не дуже корисна справа. Ми навчимося, як пов'язувати дані з шаблонами і перетворювати наш простий прототип HTML в повністю функціональний додаток реального часу.
### Тримати все в реальному часі
Бачити вміст наших колекцій через браузерну консоль це добре, але ми хочемо відображати дані та їх зміни на екрані. Цим ми перетворимо наш додаток з колекції статичних *сторінок* на *додаток* в реальному часі з даними, які автоматично синхронізуються із даними на сервері, тобто коли один користувач змінить дані на сервері, інший користувач побачить ці зміни у себе на екрані навіть не оновлюючи вікно браузера.
Давайте дізнаємося, як це зробити.
### Наповнення бази данних
Перше, що ми зробимо – це додамо деякі дані у нашу базу даних. Тож створимо 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", "Додані дані в колекцію Posts" %>
Ми помістили цей файл в директорію `server/`, так що він ніколи не буде завантажений ні в однин браузер користувача. Код виконається, коли сервер буде запущений, і за допомогою `insert` додасть три простих поста в нашу колекцію `Posts`. Оскільки ми поки не створили ніяких механізмів безпеки даних, зараз немає різниці, де цей ми запустимо цей код, на сервері чи в браузері клієнта.
Тепер знову запустіть сервер командою `meteor`, і три пости будуть завантажені в базу даних.
### Вивід інформації в HTML за допомогою meteor helpers
Тепер, відкривши браузерну консоль, ми побачимо, що всі три поста завантажені також і в MiniMongo:
~~~js
❯ Posts.find().fetch();
~~~
<%= caption "Browser console" %>
Щоб додати ці дані до HTML, ми можемо використати 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", "Зв'язана колекція collection into `postsList` template." %>
<% note do %>
### Find та Fetch
У Meteor, метод `find()` повертає *курсор*, який є [реактивним джерелом даних](http://docs.meteor.com/#find). Коли ми хочемо вивести його вміст, ми можемо використовувати над ним метод `fetch()`, який трансформує його вміст в масив.
Meteor достатньо розумний, щоб знати, як поводитися з курсорами без нашого втручання, без явних трансформацій курсора в масив. Тому ви не часто будете використовувати `fetch()` в коді Meteor (тому ми і не бачили його у попередньому прикладі).
<% end %>
Тепер, замість того, щоб діставати список постів у вигляді масиву з курсору, ми просто повертаємо курсор в нашому шаблоні-помічнику `posts`.
<%= screenshot "4-3", "Using live data" %>
Чітко видно, що наш helper `{{#each}}` успішно пробігся по всіх нашим `Posts` і вивів їх на екран. Колекція на сервері дістала пости з MongoDB, передала їх до колекції вже на клієнті, і далі наш Handlebar 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" %>
Ви тільки що вперше побачили реактивність в дії. Коли ми сказали Spacebars пробігтися по курсору `Posts.find()`, він почав відслідковувати зміни цього курсору, і в разі його зміни, буде оновлювати наш HTML, відображаючи актуальні дані на екран.
<% note do %>
### Інспектування зміни DOM
У цьому випадку, найпростіший спосіб внести зміни – це просто додати ще один `<div class="post">...</div>`. Якщо ви хочете переконатися, що Meteor дійсно зробив саме це, то відкрийте DOM inspector і виберіть `<div>`, який відповідає одному з вже існуючих постів.
Тепер за допомогою консолі JavaScript, додайте ще один пост. Коли ви повернетеся назад у інспектор, то побачите ще один `<div>`, що відповідає новому посту, але при цьому у вас залишиться *таким самим* обраним існуючий `<div>`. Це дуже дієвий спосіб знати коли елемент оновлюються і коли залишається незмінним.
<% end %>
### Синхронізуємо колекції: публікації та підписки
До цього моменту у нас був включений пакет `autopublish`, який не призначений для робочого режиму. Судячи з його назви, цей пакет просто каже, що кожна серверна колекція повинна бути повністю синхронізована з кожною колекцією на клієнті. Це не зовсім те, чого ми хочемо, так що давайте його відключимо.
Відкрийте нове вікно терміналу і введіть:
~~~bash
$ meteor remove autopublish
~~~
Це має миттєвий ефект. Якщо ви зараз поглянете на ваш браузер, то побачите, що всі наші пости зникли! Все це тому, що ми покладалися на `autopublish`, щоб бути впевненими в тому, що колекції на клієнті – це дзеркальне відображення колекцій у нашій базі даних.
Зрештою нам потрібно буде бути впевненими, що ми передаватимемо тільки ті пости, які користувач повинен бачити (це стосується таких речей як скажімо пагінація). Але наразі ми лише встановимо `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." %>
Якщо ми знову перевіримо браузер, то побачимо, що наші пости повернулися.
### Висновок
Чого ж ми в результаті досягли? Що ж, хоча у нас поки немає інтерфейсу користувача, проте ми маємо функціонуючий додаток. Ми вже можемо опублікувати його в інтернеті і (використовуючи консоль браузера) почати постити у ньому. Ці пости буду з'являтися в браузерах інших користувачів по всьому світі.