LoginSignup
2
6

More than 5 years have passed since last update.

Meteorのあんちょこ꒰。・ω・`;꒱ データベースアーキテクチャ

Last updated at Posted at 2016-07-16

本流


参考

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)

sample.js

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(先頭から逐次実行)

sample.js
var cursor = Employees.find();
cursor.forEach(function(employee) {
  ...
});

map

sample.js
// 従業員IDのみで構成される
var idList = cursor.map(function(employee) {
  return employee._id;
});

fetch

count(結果総数)

rewind(カーソル初期化)

observe(カーソルの状態を監視)

sample.js
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)

sample
var employee = Employees.findOne({_id: selected_employee_id});

insert(データ挿入)

sample.js
'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)
sample.js

// 年齢に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セレクタを用いて,削除対象となるドキュメントを絞り込んでいます。

sample.js
// 削除ボタンをクリックされた際の処理
'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 = '';
    }
},

サンプルコード

imports/api/employees.js
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)
imports/ui/employee.js
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);
        }
    },
});
imports/ui/employee.html
<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部
imports/ui/body.js
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);
    },
});

imports/ui/body.html
<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>&nbsp;</td>
        <td><input class="name" type="text" value="" placeholder="名前"></td>
        <td><input class="age" type="text" value="" placeholder="年齢"></td>
        <td>&nbsp;</td>
        <td><button class="add">追加</button></td>
      </tr>
      {{#each employees}}
        {{ > employee }}
      {{/each}}
    </tbody>
  </table>
</template>

  • サーバメイン
server/main.js
import { Employees } from '../imports/api/employees.js';

Meteor.startup(() => {
    Meteor.call('employees.init');
});

  • クライアントメイン
client/main.js
import '../imports/ui/body.js';
client/main.html
<head>
  <title>database</title>
</head>

DBアクセス

console
$ 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全データがクライアントにキャッシュされる。
  • データが膨大になるなどの場合に対応できない。
  • 全クライアントから、全データにアクセスできてしまう。
console
$ 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データを展開すると膨大となる。
api/employees.js

export const Employees = new Meteor.Collection('employees');

if (Meteor.isServer) {
    // 全データを公開する。
    Meteor.publish('all-employees', function () {
      return Employees.find();
    });
}

  • autopublish解除により、展開できるデータをすぼめる。
ui/body.js
Template.body.onCreated(function bodyOnCreated() {
    Meteor.subscribe('all-employees');
});
  • クライアントサイドでDBのCRUD操作が可能では危ない。
  • insecureを解除すると、クライアントから直接挿入はできなくなる。
  • Meteor.callによるサーバー関数の呼び出しによって実施する。
body.js
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

バックアップ

console
$ mongodump -h 127.0.0.1 --port 3001 -d meteor
  • dumpフォルダが出来上がるので、それがバックアップファイル(jsonとbsonのデータ)となる

復元

  • meteorで空のDBを作成しておいて、そこに下記のコマンドを流すとリストアが正常終了した。
  • RobomongoでDB作成して、そのまま、下記のコマンドを流してもエラーとなった(詳細不明)。
console
$ mongorestore -h 127.0.0.1 --port 3001 -d meteor dump/meteor(path to dump)
2
6
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
2
6