-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy path04-collections.md.erb
454 lines (327 loc) · 21.6 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
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
---
title: Collections
slug: collections
date: 0004/01/01
number: 4
points: 5
photoUrl: http://www.flickr.com/photos/73449134@N04/8270793784/
photoAuthor: Mike Lewinski
contents: Meteorの基本的な機能であるリアルタイムコレクションについて学ぶ。|どのようにMeteorのデータが同期するのか理解する。|コレクションとテンプレートと連動させる。|ここまでで作った試作品をリアルタイムで動くアプリにする!
paragraphs: 72
version: 1.7
---
1章では、クライアントとサーバー間のデータを自動的に同期させる Meteor の特徴についてお話しました。
この章では、これがどのように動いているのかもう少し詳しく見ていき、
データの自動同期を実現させる上で鍵となる Meteor**コレクション**について見ていきます。
コレクションは、永続的な特殊なデータ構造です。サーバー側のMongoDBデータベース内のデータを格納し、
リアルタイムで接続された各ユーザのブラウザと同期処理を行います。
投稿が永続的にユーザー間で共有されるようにしたいので、
それらを保存するために`Posts`と呼ばれるコレクションを作成することから始めます。
コレクションはたいていのアプリにおいてコアとなる部分になります。
ですので常に最初に`lib`ディレクトリの中に配置することから始めます。
まずは、`lib`ディレクトリ内に`collections/` ディレクトリを作成し、中に`posts.js`を格納します。
そして次のように書き込みます。:
~~~js
Posts = new Meteor.Collection('posts');
~~~
<%= caption "lib/collections/posts.js" %>
<%= commit "4-1", "Added a posts collection" %>
<% note do %>
### Varを使うか使わないか
Meteorでは、`var` はオブジェクトのスコープを現在のファイル内に限定します。
ここで私たちはアプリ全体で使える`Posts`コレクションを作りたいので、
`var`を*使っていない*というわけです。
<% end %>
### データの保存について
Webアプリケーションは、それぞれが異なる役割を埋める彼らの自由にデータを格納する3つの基本的な方法があります:
- **ブラウザのメモリ領域:**
JavaScript変数など、*永続的*ではないブラウザのメモリに格納します:
現ブラウザタブに対して局所的で、ブラウザタブを閉じると消えてしまいます。
- **ブラウザのストレージ:** ブラウザでもクッキーを使用するか、
[ローカルストレージ](http://diveintohtml5.info/storage.html)を使うことで、
データをより永続的に記憶することができます。
このデータはセッション間で維持されますが、
それは現在のユーザーに*ローカル*(ただし、タブ全体で利用可能な)だし簡単に他のユーザーと共有することはできません。
- **サーバーサイドデータベース**:
複数のユーザーが使用できるようにする永続的データのための最適な場所は、古き良きデータベースにあります。
(MongoDB Meteorアプリケーションのためのデフォルトのソリューションです)
Meteorは、すべての3つを利用して、(進めればすぐにわかります)時々ある場所から別の場所へデータを同期します。
それでも、データベースは、データのマスターコピーが含まれている「標準的な」データソースのままです。
### クライアントとサーバ
`client/` と `server/`以外のフォルダ内のコードは、*両方*のコンテキスト(サーバとクライアント)で実行されます。
ですので、`Posts`コレクションは、クライアントとサーバーの両方で使用できます。
しかし、コレクションは、各環境でかなり異なる可能性があります。
サーバー上では、コレクションは、MongoDBのデータベースと対話し、
読み込みや変更を書き込む処理をします。
この意味で、それは、標準的なデータベースライブラリと比較することができます。
クライアント上では、コレクションは本物の、標準的なコレクションの*一部*のコピーです。
クライアント側のコレクションは常に存在し、(ほぼ)透過的にリアルタイムで最新の状態に維持されます。
<% note do %>
### コンソール vs コンソール vs コンソール
この章では、ブラウザのコンソールを使っていきます。
これは**ターミナル**や **Mongo シェル**とは違います。
ここでは、そのあたりについてざっくりと解説していきます。
#### ターミナル
<%= screenshot "terminal", "The Terminal" %>
- オペレーティングシステムから呼び出される。
- **サーバーサイド**の`console.log()`は、ここに出力される。
- プロンプト:`$`
- 別名:Shell、Bash
#### ブラウザコンソール
<%= screenshot "browser-console", "The Browser Console" %>
- ブラウザで JavaScript のコードを実行する。
- **クライアントサイド**の `console.log()`をここに出力する。
- プロンプト:`❯`
- 別名:JavaScript Console、DevTools Console
#### Mongoシェル
<%= screenshot "mongo-shell", "The Mongo Shell" %>
- ターミナルから`meteor mongo` と打つと、呼び出される。
- 作っているアプリのデータベースに直接アクセスできる。
- プロンプト:`>`
- 別名:Mongoコンソール
ここで留意すべきことは、プロンプト文字(`$`,`❯`,`>`)をコマンドで入力しなくて良いということです。
プロンプトより先の文字で始まっていないものは、それより先に行ってたコマンドが出力したものと見なすことができます。
<% end %>
###サーバーサイドのコレクション
サーバーでのコレクションは MongoDB の API のような役割を果たします。
サーバーサイドのコードで`Posts.insert()`や`Posts.update()`のようなMongoコマンドを書くことができます。
すると、MongoDB 内に格納されている`posts`コレクションは変化します。
MongoDB 内を見るために、2つ目のターミナルウィンドウを開きます。
(1つ目のターミナルではまだ`meteor`が動いている状態です。)
そうしたら、アプリのディレクトリへ行きましょう。
そして、Mongo シェルを起動するために`meteor mongo`コマンドを実行します。
Mongo シェルでは、通常の Mongo コマンドを入力することができます。
(また、いつものように`ctrl+c`で停止することができます。)
例として、新しい投稿を挿入しましょう。:
~~~bash
meteor mongo
> db.posts.insert({title: "A new post"});
> db.posts.find();
{ "_id": ObjectId(".."), "title" : "A new post"};
~~~
<%= caption "The Mongo Shell" %>
<% note do %>
### Mongo on Meteor.com
*.meteor.com にアプリをホスティングする際は、
`meteor mongo myApp`と入力することでデプロイされたアプリのMongoシェルにアクセスすることができます。
さらに、その状態で`meteor logs myApp`と入力すること、アプリのログを見ることができます。
<% end %>
MongoDB の構文は、JavaScript インターフェースを使っているため、親しみやすくなっています。
私たちはこれ以上 Mongoシェルでデータ操作をしませんが、MongoDB内に何があるのか確認するために時々のぞき見をするかもしれません。
### クライアントサイドのコレクション
コレクションはクライアントサイドでは、より面白くなってきます。
クライアント上では、あなたが作成している実際のMongoのコレクションの*ローカルなブラウザ内のキャッシュ*が作成されます。
クライアント側のコレクションが「キャッシュ」であるというのは、
それがサーバサイドデータの*一部*であり、このデータへの*高速*アクセスを提供している。ところから述べています。
この点を理解することは大事なことです。
というのは、これが Meteor が動作の基本だからです。
一般的に、クライアントサイドのコレクションは Mongo コレクションに格納されているすべてのドキュメントの一部分から構成されます。
(結局のところ、私たちは*すべての*データベースをクライアントに送りたいわけではありません。)
次に、こうしたドキュメントは*ブラウザのメモリ*に保存されているので、
このドキュメントには基本的に一瞬でアクセスするということを意味しています。
つまり、データを取ってくるためにクライアントで`Posts.find()`を呼び出す際、
データは事前に読み込まれているのでサーバーやデータベースへのアクセスは速いのです。
<% note do %>
### Introducing MiniMongo
Meteor のクライアントサイドでの実装は MiniMongo と呼ばれます。
まだ完全な実装ではないので、通常の MongoDB の機能が MiniMongo で動かないことがあるかもしれません。
とはいえ、本書でカバーしているすべての機能は MongoDB と MiniMongo で同じように動きます。
<% end %>
### クライアントとサーバーの通信
ここで重要な点は、どのようにクライアントサイドのコレクションが同じ名前のサーバーサイドのコレクションと同期するのかということです。
(この場合では、`'posts'`)
この点は詳細に説明するよりも、実際に何が起こるのか見る方が良いでしょう。
まず2つのブラウザウィンドウを開いて、両方でブラウザコンソールにアクセスしましょう。
それから、コマンドラインで Mongo シェルを開きます。
この時点で、我々は3つのコンテキスト全てで、先ほど作った1つのドキュメントを見つけることができるはずです。
(私たちのアプリの*ユーザーインターフェース*はまだ他に3つのダミーポストが表示されているはずですが、今だけ無視してください。)
~~~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" %>
新しい投稿を作りましょう。
ブラウザウィンドウの一つで、insert コマンドを実行します:
~~~js
❯ Posts.find().count();
1
❯ Posts.insert({title: "A second post"});
'xxx'
❯ Posts.find().count();
2
~~~
<%= caption "First browser console" %>
当然のように、投稿はローカルのコレクションに作られました。Mongo をチェックしましょう:
~~~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')` という一行のコードを書きました。)
しかし、話はここで終わりません!。
2つ目のブラウザーウィンドウを立ち上げて、ブラウザーコンソールに次のように入力します:
~~~js
❯ Posts.find().count();
2
~~~
<%= caption "Second browser console" %>
先ほどの投稿がここにもあります!
私たちは更新もせず、2つ目のブラウザーと情報をやりとりもしていないのにも関わらず、
更新情報を転送するコードも書いていません。魔法のように一瞬にして、このことが起こります。
この点は、章を進めるにつれ理解できるようになります。
何が起きたかというと、サーバー側のコレクションに
クライアント・コレクションの新しい投稿によって通知されたということです。
MongoDB内の投稿配信のタスクを引き受け、すべての接続された`post`コレクションへデータを配信します。
ブラウザーコンソールで投稿を取ってくることはそれほど役立ちません。
テンプレートに、このデータをつなげていき、単純なHTMLプロトタイプを機能的なリアルタイムWebアプリケーションにする過程をすぐに学びます。
### データベースにデータを追加する
ブラウザコンソール上でコレクションの内容を見ると、その表示処理は
本当にやりたいことである画面上へのデータ、データへの変更と表示を行っています。
このように、静的データを単純なWeb*ページ*から、動的にデータが変化するリアルタイムWebアプリケーションへ切り替えて行きましょう。
最初にデータベースにデータを入れます。
サーバーの初回起動時に、`Posts`コレクションに構造化データの固定ファイルを読み込み設定する処理を行います。
まず、データベースの中が何もないようにしましょう。
`meteor reset`を使うことで、データベースを削除してプロジェクトをリセットします。
当然のことながら、あなたが実際のプロジェクトに取り組み始めたら、このコマンドに対して十分注意が必要です。
`ctrl-c`を押してMeteorのサーバーを止めてから、コマンドラインで動かします。
~~~bash
$ meteor reset
~~~
リセットコマンドは MongoDB を完全に空っぽにします。
これはデータベースが一貫性のない状態に陥る可能性が高い開発では便利なコマンドです。
Meteorを再起動しましょう
~~~bash
$ meteor
~~~
データベースが空になったので、次のコードを書き込みましょう。
これでサーバーが起動して空のPostsコレクションを見つけると、3つの投稿が読み込みます。
~~~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/`ディレクトリにあるので、他のユーザーのブラウザで読み込まれることはありません。
このコードは、サーバーが起動すると瞬時に実行され、データベース上で`insert`を呼び出し、`Posts`コレクション内に3つの投稿を追加します。
再び`meteor`でサーバーを起動すると、この3つの投稿はデータベースで読み込まれます。
### 動的データ
これでブラウザーコンソールを開くと、MiniMongo 内で読み込まれた3つの投稿を見ることができます。
~~~js
❯ Posts.find().fetch();
~~~
<%= caption "Browser console" %>
この投稿をレンダリングした HTML にするためにテンプレートヘルパーを使います。
3章では、Meteor がどのように*データコンテキスト*をSpacebarsテンプレートと結びつけるのか見てきました。
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()`は、[リアクティブデータソース](http://docs.meteor.com/#find)である`カーソル`を返します。
その中身の記録を取り出したい場合は、カーソルを配列に変換する`fetch()`を使います。
アプリ内のMeteorは明示的に配列に変換することなく、カーソルを繰り返し処理することができます。
そのような理由で、実際の Meteor コードで`fetch()`を見る機会はそれほど多くないでしょう。
(また、その理由から上記の例で`fetch()`を使いませんでした。)
<% end %>
ここでは、変数から静的な配列の投稿リストを呼び出すのではなく、
カーソルを`posts`ヘルパーに返しています。
(我々はまだ、同じデータを使用しているので、見た目上は違いがわからないかもしれません。)
<%= screenshot "4-3", "Using live data" %>
`{{#each}}`ヘルパーが`Posts`のすべてを繰り返し処理して、
スクリーン上で表示していることがはっきりとわかります。
サーバーサイドコレクションMongoDBから投稿を呼び出して、クライアントサイドのコレクションに引き渡します。
それから、Spacebars ヘルパーがそれらをテンプレートに引き渡します。
さらに、もう一歩踏み込みます。コンソールからもう一つ投稿を加えましょう。
~~~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()`カーソルを繰り返し処理する命令をすると、
Spacebarsはそのカーソルの変化を見つけて、スクリーン上で正しいデータを表示するために、
とてもシンプルな方法で HTMLに適用します。
<% note do %>
### DOM変化の分析
この場合、最も簡単な変化の可能性はもう一つ`<div class="post">...</div>`を追加することでした。
これが本当に起こったことだと確かめたい場合は、DOM inspector を開いて、
投稿に対応する`<div>`要素を選択します。
ここで、JavaScript コンソールで、もう一つの投稿を挿入します。
DOM inspector に戻ると、新しい投稿に対応する`<div>`が見つかりますが、
まだ選択されたままの*同じ*`<div>`も存在しています。
これはいつ要素が再レンダリングしたか、要素がそのままなのか、見分ける便利な方法です。
<% end %>
### コレクションをつなげる: パブリケーションとサブスクリプション
今までは、製品としてのアプリには向いていない`autopublish`パッケージが有効となっていました。
名前が意味しているように、`autopublish`パッケージは各コレクションの中のすべてをそれぞれつながったクライアントと共有させます。
私たちはこうしたことをしたくないので、`autopublish`を停止させましょう。
新しいターミナルを開いて、次のように打ち込みます:
~~~bash
$ meteor remove autopublish
~~~
これはすぐに有効化されます。
今ブラウザーを見ると、すべての投稿が消えています!
それは私たちが`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." %>
再びブラウザーをチェックすると、投稿が元に戻っています。ふぅ!
### 結論
それで、私たちは何を達成したのでしょうか?
ええと、まだユーザーインターフェースはありませんが、私たちが作ったものは実用的なウェブアプリケーションです。
私たちはこのアプリケーションをインターネットにデプロイすることができます。
また、(ブラウザコンソールを使って)新しい投稿をおこない、世界中のユーザーのブラウザに投稿を表示することができます。