本流
参考
DB接続先
- 127.0.0.1:3001 DB名=meteor(Robomongoでアクセスできた)
概要
- サーバとクライアントと同じコードでDBにアクセスできる。
- Meteorのクライアントはサーバデータのキャッシュを持っている。
- クライアントでデータを変更すると、クライアントのキャッシュが更新され、即時にサーバのDBデータも更新される。(別のクライアントの変更が瞬時に伝わるため、共同編集が可能となる。)
DB操作
コレクションの生成(コンストラクタ)
new Meteor.Collection(name, [options])
- DBのコレクション名をnameに指定すると、サーバ・クライアント間での同期が行われれるコレクションとなる。
- DBのコレクション名をnameに指定しないと、サーバ・クライアント間での同期が行われないコレクションとなる。
コレクション内を検索
find
find(selector, options)
var Employees = new Meteor.Collection('employees');
// IDで検索
Employees.find({ _id: '...' });
// 複数条件で検索(name='...'、かつ、age='...'の項目を検索)
Employees.find({ name: '...', age: '...' });
// ageの値が10以上
Employees.find({ age: { $gt: 10 } });
// nameの値が'しゅんぺい'以外のレコード
Employees.find({ name: { $not: 'しゅんぺい' } });
// 部分一致
const name = '太郎';
Employees.find({name : new RegExp(name, 'i')});
// 全検索
Employees.find({});
sort(結果をソートする)
name(昇順)、age(降順)で検索
Employees.find({}, {sort: [['name', 'asc'], ['age', 'desc']]});
skip(結果をスキップする件数)
5件スキップ
Employees.find({}, {skip: 5});
limit(最大の取得結果数)
5件のみ取得
Employees.find({}, {limit: 5});
fields(フィールド有効・無効)
- 結果に含めるプロパティには1
- 結果に含めないプロパティには0
// ageプロパティを除外する
Employees.find({}, {fields: {age: 0}})
reactive(結果をリアクティブ採否)
- デフォルトはtrue(リアクティビティについては,後の連載記事をお待ちください)
forEach(先頭から逐次実行)
var cursor = Employees.find();
cursor.forEach(function(employee) {
...
});
map
// 従業員IDのみで構成される
var idList = cursor.map(function(employee) {
return employee._id;
});
fetch
count(結果総数)
rewind(カーソル初期化)
observe(カーソルの状態を監視)
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)
var employee = Employees.findOne({_id: selected_employee_id});
insert(データ挿入)
'click #addButton': function(e, template) {
// (5) コレクションに新たなデータを追加
Employees.insert({
name: nameElem.value,
age: ageElem.valueAsNumber
}, function(error, result) {
if (error) {
alert('エラーが発生しました');
}
});
},
update(データ更新)
- update(selector, modifier, options, callback)
// 年齢に40歳を指定する。
Employees.update({ _id: id }, { $set: { age: 40 }};
// ageプロパティの値を2増加させる。
Employees.update({ _id: id }, { $inc: { age: 2 }};
// コレクションの要素を更新する(完全入替)
Employees.update(
{ _id: selectedIdElem.value },
{
name: nameElem.value,
age: ageElem.valueAsNumber
}
);
-
第三引数のoptions
-
multiというプロパティにtrueを設定すると、マッチしたドキュメント全てを更新する
-
falseを設定すると一件のみ更新する。デフォルトは{ multi: false }
-
最後の引数callback
-
処理が終了した際のコールバックを指定する。
-
サーバ上で実行する際には、コールバックを省略すると同期型のメソッドとなる。
-
処理が終了するまでブロックします。それ以外の場合はブロックしない。
remove(データ更新)
-
remove(selector, callback)
-
引数selectorに合致したドキュメントを全て削除する。
-
最後の引数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; });
// コレクションの要素を削除する
Employees.remove({_id: { $in: ids }});
// 入力フォームをクリア
selectedIdElem.value = '';
nameElem.value = '';
ageElem.value = '';
}
},
サンプルコード
-
importでJavascriptファイルを分割できる。
-
DB操作については、MongoDB 公式リファレンスを参照。
-
DB操作(API)
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
export const Employees = new Meteor.Collection('employees');
Meteor.methods({
// 初期化
'employees.init'() {
// Employees.remove({});
// // 決め打ちでデータを登録する。
// var data = [
// { name: 'しゅんぺい', age: '34' },
// { name: 'たえこ', age: '33' },
// { name: 'こうたろう', age: '4' },
// { name: 'ちほ', age: '2' }
// ];
// // データをMongoDBに挿入する。
// data.forEach(function (emp) {
// Employees.insert(emp);
// })
},
// 追加
'employees.insert'(name, age) {
check(name, String);
check(age, String);
//console.log(name, age);
Employees.insert({name: name, age :age});
},
// 編集
'employees.update' (id, name, age) {
const employee = Employees.findOne(id);
console.log(employee)
console.log('employees.update', id, name, age);
Employees.update(id, { $set: { name: name, age: age } });
},
// 削除
'employees.remove'(employeeId) {
check(employeeId, String);
const employee = Employees.findOne(employeeId);
Employees.remove(employeeId);
},
});
- 見かけ(UI)
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import './employee.html';
Template.employee.events({
'click .edit'(event) {
const target = event.target;
const name = $(target).parents('tr').find('.name').val();
// this.nameではDBに登録された値が取得される。
const age = $(target).parents('tr').find('.age').val();
Meteor.call('employees.update', this._id, name, age);
},
'click .del'() {
if (confirm('削除しますか?')) {
Meteor.call('employees.remove', this._id);
}
},
});
<template name="employee">
<tr>
<td class="id">{{_id}}</td>
<td><input type="text" class="name" value="{{name}}"/></td>
<td><input type="text" class="age" value="{{age}}" /></td>
<td><button class="edit">編集</button></td>
<td><button class="del">削除</button></td>
</tr>
</template>
- body部
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { Employees } from '../api/employees.js';
import './body.html';
import './employee.js';
Template.employees.helpers({
employees() {
return Employees.find();
},
});
Template.employees.events({
'click .add'(event) {
event.preventDefault();
const target = event.target;
const name = $(target).parents('tr').find('.name').val();
const age = $(target).parents('tr').find('.age').val();
// console.log(name, age);
Meteor.call('employees.insert', name, age);
$(target).parents('tr').find('.name').val('');
$(target).parents('tr').find('.age').val('');
// Meteor.call('employees.remove', this._id);
},
});
<body>
<h1>Database</h1>
{{> employees}}
</body>
<template name="employees">
<table id="employees">
<thead>
<th>System ID</th>
<th>名前</th>
<th>年齢</th>
<th>編集</th>
<th>削除</th>
</thead>
<tbody>
<tr>
<td> </td>
<td><input class="name" type="text" value="" placeholder="名前"></td>
<td><input class="age" type="text" value="" placeholder="年齢"></td>
<td> </td>
<td><button class="add">追加</button></td>
</tr>
{{#each employees}}
{{ > employee }}
{{/each}}
</tbody>
</table>
</template>
- サーバメイン
import { Employees } from '../imports/api/employees.js';
Meteor.startup(() => {
Meteor.call('employees.init');
});
- クライアントメイン
import '../imports/ui/body.js';
<head>
<title>database</title>
</head>
DBアクセス
-
第9回 データベースに関するその他の話題 @ 体感!JavaScriptで超速アプリケーション開発 -Meteor完全解説
-
「meteor mongo」で確認する。
$ meteor mongo -h
Options:
--url, -U return a Mongo database URL
$ meteor mongo -U
mongodb://127.0.0.1:3001/meteor
$ meteor mongo
MongoDB shell version: 2.6.7
connecting to: 127.0.0.1:3001/meteor
meteor:PRIMARY> db.employees.find()
{ "_id" : "jzqxufPYG3bDCCzfh", "name" : "しゅんぺい", "age" : "34" }
{ "_id" : "BEGgx8mJm93yqoGuu", "name" : "たえこ", "age" : "33" }
{ "_id" : "aB7PPJXsvzB3fYPuP", "name" : "こうたろう", "age" : "4" }
{ "_id" : "2KFbPkBrKsjLANog4", "name" : "ちほ", "age" : "2" }
meteor:PRIMARY> exit
bye
# mongodb標準のmongoクライアントツールからアクセスする
$ mongo 127.0.0.1:3001/meteor
MongoDB shell version: 3.2.8
connecting to: 127.0.0.1:3001/meteor
meteor:PRIMARY> show dbs
local 0.063GB
meteor 0.031GB
meteor:PRIMARY> exit
bye
- ローカルで実行する際、DBは一律「127.0.0.1:3001/meteor」になるようだ。。。
セキュリティ向上
- ここまでの設定の場合、DB全データがクライアントにキャッシュされる。
- データが膨大になるなどの場合に対応できない。
- 全クライアントから、全データにアクセスできてしまう。
$ meteor remove autopublish insecure
Changes to your project's package version selections:
autopublish removed from your project
insecure removed from your project
autopublish: removed dependency
insecure: removed dependency
- クライアントサイドのキャッシュに全DBデータを展開すると膨大となる。
export const Employees = new Meteor.Collection('employees');
if (Meteor.isServer) {
// 全データを公開する。
Meteor.publish('all-employees', function () {
return Employees.find();
});
}
- autopublish解除により、展開できるデータをすぼめる。
Template.body.onCreated(function bodyOnCreated() {
Meteor.subscribe('all-employees');
});
- クライアントサイドでDBのCRUD操作が可能では危ない。
- insecureを解除すると、クライアントから直接挿入はできなくなる。
- Meteor.callによるサーバー関数の呼び出しによって実施する。
Template.employees.events({
'click .add button.add'(event) {
event.preventDefault();
const target = event.target;
const name = $(target).parents('tr').find('.name').val();
const age = $(target).parents('tr').find('.age').val();
if (name && age) {
// Employees.insert({name : name, age : age}, function () { alert('error'); });
// ↓直接、データ挿入せず、サーバ側の関数を経由する
Meteor.call('employees.insert', name, age);
$(target).parents('tr').find('.name').val('');
$(target).parents('tr').find('.age').val('');
}
},
});
バックアップ&復元
meteor mongo
バックアップ
$ mongodump -h 127.0.0.1 --port 3001 -d meteor
- dumpフォルダが出来上がるので、それがバックアップファイル(jsonとbsonのデータ)となる
復元
- meteorで空のDBを作成しておいて、そこに下記のコマンドを流すとリストアが正常終了した。
- RobomongoでDB作成して、そのまま、下記のコマンドを流してもエラーとなった(詳細不明)。
$ mongorestore -h 127.0.0.1 --port 3001 -d meteor dump/meteor(path to dump)