100
106

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Meteorが1.0になったので入門してみた(1/2)

Last updated at Posted at 2014-11-01

##Meteorってなに

NodeJSのフルスタックなリアクティブ・フレームワークです。
裏でWebSocketを使っており、構築中にWeb画面がリアルタイムに変わっていく様子はかなり新鮮です。

すごいところは、フレームワーク内にMongoDBが内蔵されており、そのdbにクライアントサイドでもアクセスできます。(MiniMongoというclientサイドのDB機能が含まれているので、そういうことが可能であるみたいです。)

Baasのfirebaseも感動しましたが、あれはサーバサイドのCollectionとクライアントサイドのCollectionの同期は手動で紐付ける必要がありました。
MeteorはサーバからのCollectionの変更もクライアントからの変更も瞬時にお互いにミラーされ、
サーバサイドも含めての一つのアプリを作っているみたいです。

リアクティブプログラミングが可能なフレームワークでもあり、
例えばSessionの特定の値を監視して、変更された時だけログ出力するといったことも簡単にできます。

最近1.0になったよってアナウンスがあったので、チュートリアルをやってみました。

まだ書きかけでチュートリアルやりながらメモしつつという感じです。

##DiscoverMeteorをやってみる

DiscoverMeteorをやりながらチュートリアルしてみます
尚、DiscoverMeteor日本語訳版が出来つつあります。

インストール

インストールはすごく簡単ですがNodeJsはインストール済みであることが前提かもしれません。

インストールコマンド
curl https://install.meteor.com | sh

まずはチュートリアルに従い「microscope」とかいう「ソーシャルニュース」アプリを作ってみます。

環境作り
$ meteor create microscope

meteor create <<プロジェクト名>>でプロジェクトを作ります。プロジェクト名のディレクトリが作られます。

アプリ起動の仕方

起動
$ cd microscope
$ meteor

簡単なアプリが動き出しましたね。
localhost:3000にアクセスします。
Ctrl+Cでアプリを終了します。

パッケージの使い方を学ぶ

meteorのパッケージ管理ツールでパッケージを追加していきます。

パッケージ追加
meteor add mizzao:bootstrap-3
meteor add underscore

mizzao:botstrap-3はこちらを参照
underscore packageの方はofficialなパッケージです。

meteorにはパッケージは5種類があります

  1. platform package:Meteor coreから分割された
  2. First-party package:Meteorチームがメンテしてる
  3. Third-party package:ここで探すパッケージ
  4. Local package:自作の/packagesに置いたカスタムパッケージ
  5. NPM package:NodeJsのモジュールパッケージ

ファイル構造

作ったばかりの構成はこんな感じです。

作成時のファイル構成
.meteor
microscope.css  
microscope.html 
microscope.js

上記のうち.meteor以外は削除します。

代わりに以下のデイレクトリを作成します。

作成デイレクトリ 役割
/server サーバサイドで動くコードを置きます
/client クライアントサイドで動くコードを置きます
/public 画像とか静的なファイルを置きます
/lib 後述します

Meteorにおいて、あるルールで自動でファイルがロードされます。
なのでそのルールに従ってファイルをおけば勝手に読み込まれて組み込まれます。

ちなみに/server/clientでjsファイルを置くと、
それぞれサーバサイドとクライアントサイドでロードされますが、
それ以外のデイレクトリに置いた場合は 両方でロードされます。

ロードされるルールは以下となります。

  1. /libに置いたファイルが最優先でロード
  2. main.*のファイルがその次にロード
  3. ファイル名のアルファベット順にロード

あと.meteor の中についてですが、
.meteor/packages .meteor/releaseがそれぞれ、
インストールされているパッケージ情報、meteorのバージョンがわかります。
それ以外はさわらないほうがいいとのこと。

以下のCSSファイルを作成しておきます。

client/stylesheets/style.css

.grid-block, .main, .post, .comments li, .comment-form {
  background: #fff;
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  -ms-border-radius: 3px;
  -o-border-radius: 3px;
  border-radius: 3px;
  padding: 10px;
  margin-bottom: 10px;
  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
  -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); }

body {
  background: #eee;
  color: #666666; }

.navbar {
  margin-bottom: 10px; }
  /* line 32, ../sass/style.scss */
  .navbar .navbar-inner {
    -webkit-border-radius: 0px 0px 3px 3px;
    -moz-border-radius: 0px 0px 3px 3px;
    -ms-border-radius: 0px 0px 3px 3px;
    -o-border-radius: 0px 0px 3px 3px;
    border-radius: 0px 0px 3px 3px; }

#spinner {
  height: 300px; }

.post {
  /* For modern browsers */
  /* For IE 6/7 (trigger hasLayout) */
  *zoom: 1;
  position: relative;
  opacity: 1; }
  .post:before, .post:after {
    content: "";
    display: table; }
  .post:after {
    clear: both; }
  .post.invisible {
    opacity: 0; }
  .post.instant {
    -webkit-transition: none;
    -moz-transition: none;
    -o-transition: none;
    transition: none; }
  .post.animate{
    -webkit-transition: all 300ms 0ms;
    -webkit-transition-delay: ease-in;
    -moz-transition: all 300ms 0ms ease-in;
    -o-transition: all 300ms 0ms ease-in;
    transition: all 300ms 0ms ease-in; }
  .post .upvote {
    display: block;
    margin: 7px 12px 0 0;
    float: left; }
  .post .post-content {
    float: left; }
    .post .post-content h3 {
      margin: 0;
      line-height: 1.4;
      font-size: 18px; }
      .post .post-content h3 a {
        display: inline-block;
        margin-right: 5px; }
      .post .post-content h3 span {
        font-weight: normal;
        font-size: 14px;
        display: inline-block;
        color: #aaaaaa; }
    .post .post-content p {
      margin: 0; }
  .post .discuss {
    display: block;
    float: right;
    margin-top: 7px; }

.comments {
  list-style-type: none;
  margin: 0; }
  .comments li h4 {
    font-size: 16px;
    margin: 0; }
    .comments li h4 .date {
      font-size: 12px;
      font-weight: normal; }
    .comments li h4 a {
      font-size: 12px; }
  .comments li p:last-child {
    margin-bottom: 0; }

.dropdown-menu span {
  display: block;
  padding: 3px 20px;
  clear: both;
  line-height: 20px;
  color: #bbb;
  white-space: nowrap; }

.load-more {
  display: block;
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  -ms-border-radius: 3px;
  -o-border-radius: 3px;
  border-radius: 3px;
  background: rgba(0, 0, 0, 0.05);
  text-align: center;
  height: 60px;
  line-height: 60px;
  margin-bottom: 10px; }
  .load-more:hover {
    text-decoration: none;
    background: rgba(0, 0, 0, 0.1); }

.posts .spinner-container{
  position: relative;
  height: 100px;
}

.jumbotron{
  text-align: center;
}
.jumbotron h2{
  font-size: 60px;
  font-weight: 100;
}

@-webkit-keyframes fadeOut {
  0% {opacity: 0;}
  10% {opacity: 1;}
  90% {opacity: 1;}
  100% {opacity: 0;}
}

@keyframes fadeOut {
  0% {opacity: 0;}
  10% {opacity: 1;}
  90% {opacity: 1;}
  100% {opacity: 0;}
}

.errors{
  position: fixed;
  z-index: 10000;
  padding: 10px;
  top: 0px;
  left: 0px;
  right: 0px;
  bottom: 0px;
  pointer-events: none;
}
.alert {
          animation: fadeOut 2700ms ease-in 0s 1 forwards;
  -webkit-animation: fadeOut 2700ms ease-in 0s 1 forwards;
     -moz-animation: fadeOut 2700ms ease-in 0s 1 forwards;
  width: 250px;
  float: right;
  clear: both;
  margin-bottom: 5px;
  pointer-events: auto;
}

Templateの使い方を学ぶ

Meteorの開発手法としてガワから作っていって最後に内部を作り込むという手法がオススメだそうです。

まずは/clientの中で作業します

以下のHTMLファイルを作成

client/main.html
<head>
  <title>Microscope</title>
</head>
<body>
  <div class="container">
    <header class="navbar navbar-default" role="navigation"> 
      <div class="navbar-header">
        <a class="navbar-brand" href="/">Microscope</a>
      </div>
    </header>
    <div id="main" class="row-fluid">
      {{> postsList}}
    </div>
  </div>
</body>

ポイントは{{> postsList}} です。

ここには postsListテンプレートが入ります。
この後にpostsListテンプレートを作ります。

まずはテンプレートを置くディレクトリを作成しますね。

client/templates
さらにclient/templates/postsも作って以下のファイルを置いていきます。

client/templates/posts/posts_list.html

<template name="postsList">
  <div class="posts">
    {{#each posts}}
      {{> postItem}}
    {{/each}}
  </div>
</template>
client/templates/posts/post_item.html
<template name="postItem">
  <div class="post">
    <div class="post-content">
      <h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
    </div>
  </div>
</template>

注意点としてtemplateエレメントのnameであるpostsListはどのテンプレート上でも使えます

このテンプレートエンジンはSpacebarsと言います。詳細はこちらで

まあ他のテンプレートエンジンに似ているので、なんとなく見てれば動きはわかります。

これを実際に動かすには、postsオブジェクトをどこかで用意しないと・・・

ということで、以下のファイルを作成します。

client/templates/posts/posts_list.js

var postsData = [
  {
    title: 'Introducing Telescope',
    url: 'http://sachagreif.com/introducing-telescope/'
  }, 
  {
    title: 'Meteor',
    url: 'http://meteor.com'
  }, 
  {
    title: 'The Meteor Book',
    url: 'http://themeteorbook.com'
  }
];
Template.postsList.helpers({
  posts: postsData
});

ポイントはTemplate.postsList.helpersです。
templateのpostsListと紐づいています。

ちなみにpost_item.htmlを見ていただくと
{{domain}}が足りません。domain helperを作りましょう

client/templates/posts/post_item.js
Template.postItem.helpers({
  domain: function() {
    var a = document.createElement('a');
    a.href = this.url;
    return a.hostname;
  }
});

ちなみに

js技
    var a = document.createElement('a');
    a.href = this.url;
    return a.hostname;

上記部分はDomain部分だけを取り出せるというjs技だそうです。

それと上記コードの **this**について言及する必要があります。
これは、posts_list.html内の{{#each posts}}にて取り出される各要素がthisに代入されています。

Meteor Collectionについて学ぶ

さっきまではクライアントサイドだったのでサーバサイドってことですね。

ちょっとここで感動ポイントきました。いったいなんなんだこれはッッ!!

まずは、以下のようにファイルを作成します。

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

ここであれ?っとなりました。/serverに置いてない・・・。

まずはアプリが起動中だと思うんで別タブを開いて新規Terminalから該当デイレクトリに移動します。

$ cd microscope
$ meteor mongo

これでmeteor専用のmongo環境に入ります。

以下のように
db.posts.insert({title: "A new post"});,
db.posts.find();とコマンドを打ってみてください。

mongodbShell
> db.posts.insert({title: "A new post"});

> db.posts.find();
{title: "A new post", _id: ObjectId("..")};

次におもむろにブラウザのコンソールを開いてください。

Posts.findOne(); と打ち込みます。

ブラウザコンソール
Posts.findOne();
{title: "A new post", _id: LocalCollection._ObjectID};

すごくないですか・・・どういう仕組みかわからないですが、
一気にブラウザまで反映されてます。

もしかして・・・・と思いおもむろに、ブラウザのコンソールから以下を叩きます。

Posts.insert({title:"Second post"});

ブラウザコンソール
Posts.insert({title:"Second post"});
"tsCY2Wk4L2p7GvGnn"

なんかIDっぽいのが返ってきた・・・。

Terminalに戻ってコマンドdb.posts.find()を打ちます。

mongodbShell
meteor:PRIMARY> db.posts.find();
{ "_id" : ObjectId("545439d965f913c98bf37b97"), "title" : "A new post" }
{ "title" : "Second post", "_id" : "tsCY2Wk4L2p7GvGnn" }

サーバにも反映された・・・・すごすぎ。
このクライアントサイドで動いているmongoDBはminiMongoというらしいです。

引き続きチュートリアルを続けるため、作ったDBの情報をリセットします。
アプリが起動中のTerminalでctrl+Cで抜けて以下のコマンドを実行します。

reset
$ meteor reset
$ meteor #再起動

以下のファイルを作成

server/fixtures.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'
  });
}

client/templates/posts/posts_list.jsを書き換えます。

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

書き換えた瞬間に変わるのはすごい・・・。

ところで、ちょっと復習なんですが

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

変数宣言にvarが付いてないのわかりますでしょうか?
varをつけると そのファイル内でスコープが閉じられるそうです。このPostsはアプリ全体で使いたいので、
varは付けずに変数宣言を行います。

もう一点。

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

上記のPosts.find()では実はArrayではなくcursorが返ります。今流行りのリアクティブプログラミングというものらしいです。cursorはまだこの段階ではDBから値を取得しておらず、テンプレートエンジンでレンダリング直前に取得するようです。
このようにして透過的にサーバからの変更内容をクライアントに通知するわけですね。

なので、ログ出力する場合はPosts.find().fetch()でArrayに変換してください。
詳細はこちら

CollectionがClientと全共有されている問題の解決

標準では、autopublishパッケージが有効になっています。
autopublishはclientのCollectionとServerのCollectionがミラーリングされます。

meteor remove autopublish

削除することによってこの機能をOffにします。

代わりに以下のファイル追加によってCollection単位で有効化します。

この辺の詳しい仕組みはdiscovermeteor:Publications and Subscriptionsを読むと良さそうです。(そのうち翻訳したい)

サーバサイドの公開処理

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

クライアントサイドの受け入れ処理

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

ルーテイングの設定を行います。

以下のコマンドでルーテイングのパッケージを導入します。

terminal
meteor add iron:router

ルーテイングを行うにあたりヘッダやポスト一覧の表示などは
urlが変わっても基本的に同じ構成にしたいです。

構成イメージ

まずはmain.htmlファイルを作成します。
今回使うiron-routerはbodyタグの中を作りこんでいきますので、それ以外をmain.htmlに作成します。

client/main.html
<head>
<title>Microsope</title>
</head>

次は"layout" Templateを作成します。

client/templates/application/layout.html
<template name="layout">
  <div class="container">
    <header class="navbar navbar-default" role="navigation"> 
      <div class="navbar-header">
         <a class="navbar-brand" href="{{pathFor 'postsList'}}">Microscope</a>
      </div>
    </header>
    <div id="main" class="row-fluid">
      {{> yield}}
    </div>
  </div>
</template>

ルーテイングの設定で常に上記Templateが呼ばれるように設定します。そしてurlによって{{> yield}}の箇所が変わるようにします。

以下のファイルを追加

lib/router.js
Router.configure({
  layoutTemplate: 'layout'
});

Router.route('/', {name: 'postsList'});

nameプロパティに設定したTemplateが{{> yield}}に入るようになります。

それにしてもrouter.jslibに入れるんですね。/server/clientのどちらかに入れると思ってました。

/libに入れるのはアプリが何かする前に呼ばれることを保証するためだそうです。でも共用コードに含まれることになるのは気持ちが悪いような・・・・。

ローディングインジケータを表示

ページを最初に開いたとき、Meteor.subscribe('posts')が実行完了するまで時間がかかるため(サーバからデータを取得するまで完了しないため)空欄表示になってしまいます。

それを防ぐための処理を追加していきます。
Iron Routerの機能を利用します。

まずはmain.js内のMeteor.subscribe('posts');をこちらに 移動させます

lib/router.js
  Router.configure({
    layoutTemplate: 'layout',
+   waitOn: function() { return Meteor.subscribe('posts'); }
  });

  Router.route('/', {name: 'postsList'});

この変更によって、Iron Routerはそのルートへの表示準備が完了したことが
わかるようになったわけです。

さて準備中は組み込みのローディング中表示が出るわけですが、こちらも自分好みに当然修正可能です。

また、lib/router.jsを変更します。

lib/router.js
  Router.configure({
    layoutTemplate: 'layout',
+   loadingTemplate: 'loading',
    waitOn: function() { return Meteor.subscribe('posts'); }
  });

  Router.route('/', {name: 'postsList'});

ちなみににこの設定は全ルート共通にwaitOn関数を定義しているため、
本アプリに一番最初にアクセスした瞬間にだけ実行される点に注意してください。
ブラウザに記憶されたら、以後実行されません。

loading Templateを設定しましょう。

スピナー表示のためのモジュールを導入します。

Terminal
$ meteor add sacha:spin

以下のファイルも作成

client/templates/includes/loading.html
<template name="loading">
  {{>spinner}}
</template>

ですがスピナーの変化わからない・・・

post詳細画面の追加

postの詳細画面を追加してみます。

まずRouteを追加します。

lib/router.js
	Router.configure({
	  layoutTemplate: 'layout',
	  loadingTemplate: 'loading',
	  waitOn: function() { return Meteor.subscribe('posts'); }
	});

   Router.route('/', {name: 'postsList'});
+  Router.route('/posts/:_id', {
+	  name: 'postPage',
+     data: function(){return Posts.findOne(this.params._id);}
+	});

lib/router.jsdata関数で取得した結果が
postPage Templateのthisと紐付きます。

/posts/:_idの記述はよくあるやつですね。
urlの:_idに対応する文字列がthis.params._idで取得できると。

対応するTemplateを追加

client/templates/posts/post_page.html
<template name="postPage">
  {{> postItem}}
</template>

全然詳細画面じゃない。postItem Template流用ですね。

ところでthisとtemplateの対応について補足があります。

{{#each widgets}}
  {{> widgetItem}}
{{/each}}

上記の場合widgetsがCollectionで各要素がwidgetItem Templateのthisに入ります。
これはいつものやつですね。

他にもありまして、

{{#with myWidget}}
  {{> widgetPage}}
{{/with}}

{{> widgetPage myWidget}}

は全く同じ意味で、 myWidgetwidgetPageTemplateのthisに入ります

これでとりあえず詳細画面はできましたが、そこに遷移するためのリンクがないです。
それを作ります。

まずはpostsList画面にDiscussボタンを作りそこからリンクします。

client/templates/posts/post_item.html
	<template name="postItem">
	  <div class="post">
	    <div class="post-content">
	      <h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
	    </div>
+	    <a href="{{pathFor 'postPage'}}" class="discuss btn btn-default">Discuss</a>
	  </div>
	</template>

これで動くようになります。・・・
でも実はちょっと変なところがあります。
a href="{{pathFor 'postPage'}}"って変換されるとa href="/posts"ですよね・・・。
これで動くって変じゃないですか・・。
これはIron Router が頭がいいことにrouter設定を元にidが必要だということを読み取って対応してくれたらしいです。

NotFoundページへの遷移を追加

Templateから作成

client/templates/application/not_found.html
<template name="notFound">
  <div class="not-found jumbotron">
    <h2>404</h2>
    <p>Sorry, we couldn't find a page at this address.</p>
  </div>
</template>

ルーテイング変更

 Router.configure({
   layoutTemplate: 'layout',
   loadingTemplate: 'loading',
+  notFoundTemplate: 'notFound',
   waitOn: function() { return Meteor.subscribe('posts'); }
 });

//...

適当に遷移してみてちゃんと表示されることを確認します。

でもこれだとlocalhost:3000/posts/xyzにアクセスしてもNotFoundにならないです。

データが存在しない場合にもNotFoundに行きたい場合は以下のように追加します。

lib/router.js
//一番下に追記
Router.onBeforeAction('dataNotFound', {only: 'postPage'});

これはdata関数がnull, false, undefined,emptyのいずれかを返した場合に
notFound 扱いにします。

sessionの話

sessionはグローバル変数みたいで嫌うことも多いですが、クライアント固有の情報、
例えば一時的にクライアント上非表示にしているなど、クライアント固有の情報保存には必要です。

使い方は簡単です。
ブラウザのコンソールから以下のように実行してください。

ブラウザコンソール
Session.set('pageTitle', 'A different title'); //値の設定
Session.get('pageTitle'); //値の取得

ここですごいSession.setの話。
セットする値が設定済みの値と同じ場合は関数呼び出しを避ける仕組みになっているそうです。(仕組みが想像できない)

もう一つ、内部的な仕組みが想像できないけどすごい機能です。
Autorunです。

以下のコードをブラウザコンソールから実行してみてください。

ブラウザコンソール
Tracker.autorun( function() { console.log('Value is: ' + Session.get('pageTitle')); } );
Session.set('pageTitle', 'Yet another value');
Value is: Yet another value

設定した瞬間に出力されとる・・・。

autorunブロック内のコードが内部のSession.get('pagetitle')の変更状態を監視して、変更されると自動実行されるというものです。

ドキュメント曰くsessionはreactive data sourcesだから可能とのこと。詳細はこちらで

あと最後に補足です。 開発中はリロードボタンを押すとセッション情報消えます。

ユーザ管理

ユーザ管理の使い方です。
bootstrapを使っているので以下のモジュールを導入します。

Terminal
meteor add ian:accounts-ui-bootstrap-3 #accounts-uiのbootstrap版
meteor add accounts-password

headerを拡張してアカウントログインボタンを組み込みます。

以下のようにファイルを変更

client/templates/application/layout.html
 <template name="layout">
   <div class="container">
-    <header class="navbar navbar-default" role="navigation">
-      <div class="navbar-header">
-         <a class="navbar-brand" href="{{pathFor 'postsList'}}">Microscope</a>
-      </div>
-    </header>
+   {{> header}}
     <div id="main" class="row-fluid">
       {{> yield}}
     </div>
   </div>

header Templateを追加

client/templates/includes/header.html
<template name="header">
  <nav class="navbar navbar-default" role="navigation">
    <div class="container-fluid">
      <div class="navbar-header">
        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navigation">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="{{pathFor 'postsList'}}">Microscope</a>
      </div>
      <div class="collapse navbar-collapse" id="navigation">
        <ul class="nav navbar-nav navbar-right">
          {{> loginButtons}}
        </ul>
      </div>
    </div>
  </nav>
</template>

ポイントは{{> loginButtons}}の箇所ですね。詳細はこちらで

これで組み込みのaccount-ui画面が出るようになります。

実際にアカウントを作ってログインしてみましょう
アカウントは作成できましたか?

実はaccountsパッケージを入れることで、自動でCollectionが作成されます。Meteor.usersです。

実際にブラウザコンソールからアクセスしてみましょう。

ブラウザコンソール
Meteor.users.findOne();
Object {_id: "foRHJjMWe8L9H4Bdi", profile: Object, username: "abe"}

帰ってきた結果を見てみましょう。
_isとusername,profileくらいしかないのが確認できると思います。
ちなみにMeteor.user()でも同じ結果が返ります。

サインアウトしてもう一つアカウントを作ってみます。
そのアカウントで再ロングインしてください。

今度は以下のコマンドをブラウザコンソールから実行します。

ブラウザコンソール
Meteor.users.find().count();
1

2じゃなくて1が返ってきます。

今度はMongoshell(Terminalからmeteor mongo)から見てみましょう

mongoShell
meteor:PRIMARY>db.users.count()
2

こっちはちゃんと2が返ってきました。
これはなんでか?

実はautopublishパッケージを削除したことを覚えていますか?
これを削除することによってサーバ側のCollectionとクライアント側のCollectionのミラーを停止させたはずです。

そしてpublicationsubscriptionの設定を行ったことも。
なのにMeteor.usersCollectionは見えましたね。

実はaccountパッケージがやってくれたんです。便利ですね。
accountパッケージは現在のユーザ情報だけをpublishにしてくれたわけです。クライアントからは自分以外の情報が見えてはいけないですからね。
それだけではなくフィールドのフィルタも行ってくれてます。
見てみましょう

mongoShell
meteor:PRIMARY> db.users.findOne()
{
	"_id" : "55vyZRn3Na2uietY8",
	"createdAt" : ISODate("2014-11-01T13:49:52.625Z"),
	
//なんかたくさんでる・・
	}
}

これでクライアント側はフィールドがフィルタされてるってことがわかったかと思います。

詳細はこちらで

リアクティブプログラミングを学ぶ

リアクティブプログラミングについて調べると、
大抵例としてExcelの話が出ます。Excelの各Cellには値だけでなく関数を記述すると思います。
そして依存しているCellの値を書き換えると瞬時に関連するCellの値が再計算されます。
これをフレームワークとしてプログラミングの世界でも適用しようというのがMetelorの思想になります。

javascriptで値監視をする場合は.observe()関数を使うと思います。

例えばこんな風に書くとか

Posts.find().observe({
  added: function(post) {
    // when 'added' callback fires, add HTML element
    $('ul').append('<li id="' + post._id + '">' + post.title + '</li>');
  },
  changed: function(post) {
    // when 'changed' callback fires, modify HTML element's text
    $('ul li#' + post._id).text(post.title);
  },
  removed: function(post) {
    // when 'removed' callback fires, remove HTML element
    $('ul li#' + post._id).remove();
  }
});

もう見ただけで将来破綻することが目に見えています。

Meteorでは宣言的アプローチを行います。
オブジェクト間の関係性を記述し、自動で値監視を行うようにします。
例えば以下のように記述します。

<template name="postsList">
  <ul>
    {{#each posts}}
      <li>{{title}}</li>
    {{/each}}
  </ul>
</template>
Template.postsList.helpers({
  posts: function() {
    return Posts.find();
  }
});

上記のように記述しておくことによってMeteorが.observe()関数をつかって値の変化とHTMLの要素の対応を覚えてくれます。

計算処理についてもmeteorは監視してくれます。
計算処理の中にreactive data sourceが含まれていればその計算は該当するreactive data sourceの変化に影響を受けることは明らかなので依存対象に自動で含めてくれます。

例を挙げてみます。

Meteor.startup(function() {
  Tracker.autorun(function() {
    console.log('There are ' + Posts.find().count() + ' posts');
  });
});

上記のMeteor.startup()ブロック内のコードはPost Collectionがロード完了した際に一度だけ実行されます。

見るべきポイントはautorunブロックです。
Posts.find()reactive data sourceであるcursorを返します。つまりブロック内の計算はPosts.find()の変化に影響するため、依存対象に含まれます。

結果、postsの内容が変化するたびに自動で実行されるわけです。

以下実行例を示します。

> Posts.insert({title: 'New Post'});
There are 4 posts.

長くなったので続きはこっちで

100
106
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
100
106

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?