体感!JavaScriptで超速アプリケーション開発 -Meteor完全解説

第8回MeteorのデータベースAPI

前回は、Meteorのデータベースアーキテクチャを説明しました。Meteorがサーバデータをクライアントにキャッシュすることで、クライアントでもサーバでも同様のAPIを用いることができるため、クライアントとサーバのコード共有が最大限に促進されます。

では、今回はMeteorのデータベースAPIを紹介していきたいと思います。ただし字数の都合上、全てのAPIを紹介することはできません。完全なリストはMeteorの公式ドキュメントを参照してください。

また、MeteorのデータベースAPIはMongoDBを理解していると、より理解が促進されます。ちょうど、gihyo.jpでも連載記事があるようなので、参照することをお勧めします。

また、前回使用したサンプルに機能を追加して、データの追加・更新・削除を行えるようにしたサンプルを用意しました。

図1 サンプル8-1
図1 サンプル8-1

上部のフォームに値を入力して「追加」ボタンを押すと、データベースに値が追加されます。一覧のチェックボックスを選択すると、上部のフォームに値が読み出されます。そのまま値を修正して更新を行うことができます。また、チェックした行のレコードをまとめて削除することができます。

こちらのサンプルは、コード量が多少かさんでしまったので、サンプルの全文は掲載しません。以下からダウンロード可能にしておきますので、必要に応じて参照してください。

サンプル8-1(sample8-1.zip)のダウンロードはこちらから

コレクション

前回もご説明した通り、データベースに格納するオブジェクトの集まりを「コレクション」と呼びます。一つのコレクションには任意のJavaScriptオブジェクトを格納できますが、通常、同様のプロパティを持つ同じ型のオブジェクトを格納します。また、MongoDBではコレクションに格納するオブジェクトのことを「ドキュメント」と呼び表しますので、以下それに従います。

new Meteor.Collection(name, options)

コレクションを生成するためのコンストラクタです。第一引数にはコレクションの名前を表す文字列、第二引数にはオプションを指定します(オプションについては、ここでは説明しません⁠⁠。nameを省略すると、サーバ・クライアント間での同期が行われないコレクションとなります。

サンプルにおいては、従業員データを格納するためのコレクションをemployeesという名前で作成しています。

// (1) コレクションの生成
var Employees = new Meteor.Collection('employees');

find(selector, options)

コレクション内で、条件に合致するドキュメントを検索して返します。検索条件は、第一引数のselectorで指定します。たとえば、ID_idプロパティ)の値が一致することを表す条件は以下のようになります。

Employees.find({ _id: '...' });

複数のプロパティが一致する条件を指定することもできます。

Employees.find({ name: '...', age: '...' });

また、値の大小に基づく比較条件や条件の否定(NOT)なども行うことができます。

// ageの値が10以上
Employees.find({ age: { $gt: 10 } });

// nameの値が'しゅんぺい'以外のレコード
Employees.find({ name: { $not: 'しゅんぺい' } });

検索条件を指定しない(すべて取得する)場合には、空のオブジェクトを指定します。

Employees.find({});

その他にもたくさんの条件指定方法があります。詳しくはMongoDBの公式リファレンスを参照してください。

第二引数には、以下の様なプロパティを持つJavaScriptオブジェクトを指定します。

  • sort…ソートの指定。ソートするプロパティの順序を配列で指定します。

    // aの昇順、bの降順
    [["a", "asc"], ["b", "desc"]]
    // 上と同様。昇順の場合はプロパティ名の指定のみでOK
    ["a", ["b", "desc"]]
  • skip…結果をスキップする件数

  • limit…最大の取得結果数

  • fields…結果に含めるフィールド(サーバ上でのみ有効⁠⁠。結果に含めるプロパティには1を、含めないプロパティには0を指定します。

    // ageプロパティを除外する
    Employees.find({}, {fields: {age: 0}})
  • reactive…結果をリアクティブとするかどうか。デフォルトはtrue(リアクティビティについては、後の連載記事をお待ちください)

サンプルにおいては、テンプレート内の変数employeesの値として、関数の戻り値に指定しています。

    // テンプレート内のemployeesという変数の値を返す
    Template.employeeList.employees = function() {
        // (2) コレクション内の全データを返す
        var cursor = Employees.find();
        return cursor;
    };

返却されるオブジェクトは、検索結果を辿ることのできるカーソルMeteor.Collection.Cursorです。カーソルには以下のようなメソッドが定義されています。

  • forEach(callback)…先頭から順にドキュメントを処理します。以下のように使用します。

    var cursor = Employees.find();
    // 一件ずつ処理する
    cursor.forEach(function(employee) {
      ...
    });
  • map(callback)…カーソルの内容から、新たな配列を生成します。配列の要素は、コールバック引数の戻り値で構成されます。

    // 従業員IDのみで構成される
    var idList = cursor.map(function(employee) {
      return employee._id;
    });
  • fetch()…カーソルから全てのデータを読み込み、配列を返します。

  • count()…検索結果の総数を返します。

  • rewind()…カーソルを初期状態に戻します。

  • observe(callbacks)…カーソルの状態を監視します。以下の利用法を見てください。

    // (3) カーソルの状態を監視してログを残す
    cursor.observe({
        // 検索結果が増やされた
        added: function(document, beforeIndex) {
            console.log('added(追加された位置:' + beforeIndex + ')');
        },
        // 検索結果が変更された
        changed: function(newDocument, atIndex, oldDocument) {
            console.log('changed(位置:' + atIndex + ')');
        },
        // 検索結果内でオブジェクトのインデックスが変わった
        moved: function(document, oldIndex, newIndex) {
            console.log('moved(前の位置:' + oldIndex + ' 後の位置:' + newIndex + ')');
        },
        // 検索結果からオブジェクトが減った
        removed: function(oldDocument, atIndex) {
            console.log('removed(位置:' + atIndex + ')');
        }
    });

findOne(selector, options)

検索結果の1件目を取得して返します。find(selector, options).fetch()[0]と同様です。サンプルにおいては、一覧のチェックボックスをチェックされた際、DBからオブジェクトを読みだして入力フォームにセットする処理で使用しています。

// (4) チェックされた行のオブジェクトを取得
var selectedEmployee = Employees.findOne({_id: selectedEmployeeId});

insert(doc, callback)

コレクションにドキュメントを追加します。

第一引数のドキュメントには、_idプロパティを含めてはなりません。同プロパティは、MongoDBによって自動的に生成されます。

第二引数は処理が完了した際に呼び出されるコールバックです。サーバ上では、このコールバックを省略すると、処理が終了するまでメソッド呼び出しがブロックします。サーバ上であってもコールバックを引数に指定した場合、もしくはクライアント上で呼び出した場合は、メソッド呼び出しがブロックすることはありません。

メソッドの戻り値は、新たに生成されたID文字列です。

サンプルにおいては、⁠追加」ボタンをクリックされた際に、insert()メソッドを使用してデータを追加しています。

'click #addButton': function(e, template) {
    // (5) コレクションに新たなデータを追加
    Employees.insert({
        name: nameElem.value,
        age: ageElem.valueAsNumber
    }, function(error, result) {
        if (error) {
            alert('エラーが発生しました');
        }
    });
},

update(selector, modifier, options, callback)

コレクション内のドキュメントを更新します。引数selectorで更新対象を絞込み、modifierで更新方法を指定します。

selectorの指定方法は、find()におけるものと同様です。modifierの指定方法の例を幾つか挙げます。完全なリストはMongoDBの公式リファレンスを参照してください。

  • プロパティに値をセットするには$setモディファイアを指定します。

    // 年齢に40歳を指定する
    Employees.update({ _id: id }, { $set: { age: 40 }};
  • 数値プロパティの値を増減させるには、$incを指定します(マイナス値も指定可能⁠⁠。

    // ageプロパティの値を2増加させる
    Employees.update({ _id: id }, { $inc: { age: 2 }};
  • 上記のような$プロパティを持たないオブジェクトを指定すると、オブジェクトの完全な入れ替えとなります。サンプルでは、更新ボタンを押した際の処理として、updateメソッドを使用しています。

    // (6) コレクションの要素を更新する
    Employees.update(
        { _id: selectedIdElem.value },
        {
            name: nameElem.value,
            age: ageElem.valueAsNumber
        });

第三引数のoptionsに、multiというプロパティに真偽値を指定することで、マッチしたドキュメント全てを更新するのか、一件のみ更新するのかを指定できます。デフォルトは{ multi: false }(一件のみ更新)です。

最後の引数callbackには、処理が終了した際のコールバックを指定します。サーバ上で実行する際には、コールバックを省略すると同期型のメソッドとなり、処理が終了するまでブロックします。それ以外の場合はブロックしません。

remove(selector, callback)

引数selectorに合致したドキュメントを全て削除します。selectorの指定方法は、find()におけるものと同様です。

最後の引数callbackには、処理が終了した際のコールバックを指定します。サーバ上で実行する際には、コールバックを省略すると同期型のメソッドとなり、処理が終了するまでブロックします。それ以外の場合はブロックしません。

サンプルでは、削除ボタンをクリックされた際に、チェックされた行のレコードを全て削除するためにremoveメソッドを使用しています。$inセレクタを用いて、削除対象となるドキュメントを絞り込んでいます。

// 削除ボタンをクリックされた際の処理
'click #removeButton': function(e, template) {
    if (confirm('選択されているレコードを削除してもよろしいですか')) {
        // チェックされているチェックボックスを全て取得
        var selected = template.findAll('[name="selectedEmployees"]:checked');
        // IDの配列に変換
        var ids = selected.map(function(id) { return id.value; });
        // (7) コレクションの要素を削除する
        Employees.remove({_id: { $in: ids }});
        // 入力フォームをクリア
        selectedIdElem.value = '';
        nameElem.value = '';
        ageElem.value = '';
    }
},

まとめ

MeteorのデータベースAPIは、MongoDBのAPIをかなり踏襲しています。そのため、MongoDBの強みである検索条件の柔軟な指定方法などをそのまま利用できるのが利点です。

次回は、今回説明しなかったデータベースAPI、そしてMeteorプログラミングの最も先進的な部分とも言える「リアクティビティ」について少し触れる予定です。

おすすめ記事

記事・ニュース一覧