Sails フレームワークでの組み込み利用に加えて、Waterline はスタンドアロンモジュールとして使用できます。
警告: このドキュメントセクションは、かなり高度な Node.js ユーザー向けです。Sails アプリケーション以外で Waterline を使用する予定がない場合 (例: 独自のフレームワークを構築する場合)、このページをスキップして モデルと ORM に戻ることをお勧めします。
Waterline は NPM 経由で入手できます。
$ npm install --save waterline
Waterline にはアダプターが付属していないため、別途インストールする必要があります。例えば
$ npm install --save sails-mysql
$ npm install --save-dev sails-disk
アプリケーションに任意の数のアダプターをインストールできます。
sails-disk
アダプターは、開発とテストによく使用されます。
Node を初めて使用する場合は、はじめに にアクセスして、お好みのプラットフォームへの Node のインストール方法を学習してください。
スタンドアロンモジュールとして Waterline を使い始めるには、アダプターとモデル定義の 2 つの要素が必要です。
最も単純なアダプターは sails-disk
アダプターです。空のディレクトリにそれをインストールし、Waterline をインストールしてみましょう。
mkdir my-tool
cd my-tool
npm init
# ...
npm install waterline sails-disk
次に、サンプルコードが必要です。ここから Waterline の生の使用法を示すサンプルコード を、waterline
および sails-disk
パッケージがインストールされているのと同じディレクトリ内のファイルにコピーします。
実行する前に、その仕組みを見てみましょう。
var Waterline = require('waterline');
var sailsDiskAdapter = require('sails-disk');
var waterline = new Waterline();
ここでは、主要なオブジェクトをブートストラップしています。Waterline
ファクトリオブジェクト、アダプターのインスタンス、および waterline
自体のインスタンスを設定しています。
次に、ユーザーモデルの仕様を次のように定義します
var userCollection = Waterline.Collection.extend({
identity: 'user',
datastore: 'default',
primaryKey: 'id',
attributes: {
id: {
type: 'number',
autoMigrations: {autoIncrement: true}
},
firstName: {type:'string'},
lastName: {type:'string'},
// Add a reference to Pets
pets: {
collection: 'pet',
via: 'owner'
}
}
});
ここで重要なのは、そのファクトリメソッドに渡しているオブジェクトです。
モデルに後で参照できる identity
を指定し、使用するデータストアも宣言する必要があります。
データストアはアダプターのインスタンスです。たとえば、使用するストレージの種類ごとに 1 つのデータストアを用意できます (ファイル、MySQL など)。同じタイプのアダプターに対して複数のデータストアを用意することもできます。
attributes
はモデルのプロパティを定義します。従来のデータベースでは、これらの属性はテーブルの列に対応します。この例では、pets
はユーザーが複数のペットを所有できるようにする関連付けを定義しているため、少し異なります。
リレーショナルデータベースでは、
pets
属性は列として表示されません。むしろ、これから定義するペットモデルとの仮想的な 1 対多の関連付けを確立します。
次に、ペットとは何かを定義する必要があります
var petCollection = Waterline.Collection.extend({
identity: 'pet',
datastore: 'default',
primaryKey: 'id'
attributes: {
id: {
type: 'number',
autoMigrations: {autoIncrement: true}
},
breed: {type:'string'},
type: {type:'string'},
name: {type:'string'},
// Add a reference to User
owner: {
model: 'user'
}
}
});
ほとんどの構造はユーザーと同じですが、このペットの所有者を指定する追加の owner
フィールドがあります。
この例では、ペットは 1 人の所有者しか持つことができず、
owner
フィールド内に関連付けられたモデル (この場合はuser
) を提供します。モデルの名前は、モデルに付けられたidentity
と一致する必要があることに注意してください。また、この例では、リレーショナルデータベースがuser
テーブルへの外部キーを含むowner
という列を作成することに注意してください。
次に、さらに退屈なセットアップ作業がいくつかあります
waterline.registerModel(userCollection);
waterline.registerModel(petCollection);
ここでは、モデルの仕様を waterline
インスタンス自体に追加しています。
最後に、データストアを構成する必要があります
var config = {
adapters: {
'disk': sailsDiskAdapter
},
datastores: {
default: {
adapter: 'disk'
}
}
};
ここでは、使用する adapters
(使用するストレージの種類ごとに 1 つ) と、通常はターゲットストレージシステムのデータストアの詳細 (ログイン詳細、ファイルパスなど) を含む datastores
を指定します。各データストアには名前を付けることができます。この場合は、簡潔にするためにデータストアに「default」という名前を付けました。アダプターによっては、datastores
内の項目に対してさらに構成が可能になる場合があります。たとえば、sails-disk
アダプターでは、dir
および inMemoryOnly
設定を構成できます。詳細については、sails-disk アダプターリファレンス を参照してください。
さて、データストアを起動して操作する時が来ました。最初に waterline
インスタンスを初期化し、次に作業に取り掛かることができます
waterline.initialize(config, (err, ontology)=>{
if (err) {
console.error(err);
return;
}
// Tease out fully initialized models.
var User = ontology.collections.user;
var Pet = ontology.collections.pet;
// Since we're using `await`, we'll scope our selves an async IIFE:
(async ()=>{
// First we create a user
var user = await User.create({
firstName: 'Neil',
lastName: 'Armstrong'
});
// Then we create the pet
var pet = await Pet.create({
breed: 'beagle',
type: 'dog',
name: 'Astro',
owner: user.id
});
// Then we grab all users and their pets
var users = await User.find().populate('pets');
console.log(users);
})()
.then(()=>{
// All done.
})
.catch((err)=>{
console.error(err);
});//_∏_
});
かなりの量のコードなので、少しずつ見ていきましょう。
最初に、Waterline インスタンスを initialize
します。これはデータストアを接続し (おそらく 1 つまたは 2 つのデータベースサーバーにログインし)、関連付けを探してモデルを解析し、その他多くの処理を行います。それがすべて完了すると、2 番目の引数で渡したコールバックに委任されます。
エラーをチェックした後、ontology
変数はユーザーとペットのコレクションオブジェクトを収集します。次の行では、User
および Pet
の形式で、それらのコレクションオブジェクトへのショートカット変数を追加します。
通常、モデルには単数形で名前を付けます。つまり、クエリから返される *オブジェクト* の *タイプ* です。
次に、await
を使用してユーザーとペットを作成し、データストアから何を取り戻せるかを確認します。
最初に create
メソッドを使用して新しいユーザーを作成します。作成されたレコードのコピーを取得するには、ユーザーの属性を指定するだけです。
注: 特に指定しない限り、Waterline はデフォルトで
id
主キーを追加します。
次に、新しいペットを作成します。前の手順で作成されたユーザーの id
をそのペットに関連付けることができることに注意してください。これは、owner
フィールドを直接設定することで行われます。
ペットが作成されると、関連付けの両側が準備完了です。それらを結合するには、新しいユーザーの pets
配列にペットを追加するだけです。次に、モデルの save
メソッドを使用してレコードを保存します。
save
は、クエリによって返されたモデルオブジェクトでのみ使用できることに注意してください。User
コレクションオブジェクトは、これにアクセスできません。
最後に、データベースに実際に何が詰め込まれたかを確認したいので、User.find
を使用してデータストアからすべての User
レコードを取得します。また、クエリでペットの関連付けを解決したいので、populate
メソッドを追加して、各ユーザーのペットレコードを取得するようにクエリに指示します。
そのシンプルなアプリケーションを実行すると、次のようになります
$ node getting-started.js
[ { pets:
[ { breed: 'beagle',
type: 'dog',
name: 'Astro',
owner: 1,
createdAt: Thu May 07 2015 20:44:37 GMT+1000 (AEST),
updatedAt: Thu May 07 2015 20:44:37 GMT+1000 (AEST),
id: 1 } ],
firstName: 'Neil',
lastName: 'Armstrong',
createdAt: Thu May 07 2015 20:44:37 GMT+1000 (AEST),
updatedAt: Thu May 07 2015 20:44:37 GMT+1000 (AEST),
id: 1 } ]
モデルに指定された属性があり、自動的に生成された主キーを確認できます。また、Waterline がデフォルトの createdAt
および updatedAt
タイムスタンプを追加していることもわかります。クール!
他のグローバルまたはモデルごとの設定オプションを使用して、タイムスタンプをオフにすることができます。
このセクションでは、Waterline モデルの統合テストの実行について説明します。Sails アプリケーションでのテストに関するドキュメントについては、概念 > テスト を参照してください。
テストを実行するには、テストフレームワークが必要です。いくつかありますが、この例では Mocha を使用します。これは、コマンドラインで次のようにインストールするのが最善です
$ npm install -g mocha
コードカバレッジに興味がある場合は、Istanbul というツールを確認することをお勧めします。スパイ、スタブ、モックには、Sinon が適しています。HTTP リクエストのシミュレーションには、nock を検討する価値があります。
次の例は、Waterline モデルをテストする方法を示しています。これは、次の非常に単純なアプリケーション構造を想定しています
root
|- models
| |- Pet.js
| `- User.js
`- test
|- mocha.opts
`- UserModelTest.js
Pet.js
これは標準の Pet モデルの例です
module.exports = {
identity: 'pet',
datastore: 'default',
attributes: {
breed: 'string',
type: 'string',
name: 'string',
// Add a reference to User
owner: {
model: 'user'
}
}
};
User.js
そして、これは標準の User モデルの例です
module.exports = {
identity: 'user',
datastore: 'default',
attributes: {
firstName: 'string',
lastName: 'string',
// Add a reference to Pets
pets: {
collection: 'pet',
via: 'owner'
}
}
};
UserModelTest.js
User
モデルをテストする方法は次のとおりです。
setup
関数は、Waterline インスタンスをモデルに接続し、初期化します。モデルは default
アダプターを使用していますが、ここではテストでその構成をオーバーライドしてディスクアダプターを使用しています。これは高速であるため、また、データベースストレージ間で移植できない可能性のあるモデルで「マジック」を使用しようとしている場所を検出できる可能性があるためです。
teardown
関数は、将来のテストがきれいな状態から開始できるようにアダプターをクリアします (Mocha で -w
オプションを安全に使用できるようにします)。 teardown
は Node 0.12 を使用していると想定していることに注意してください。そうでない場合は、Bluebird などのプロミスライブラリを使用するか、メソッドを async
などを使用して変換する必要があります。
最後に、ユーザーを作成して基本的なアサーションを実行しようとするテストメソッドに到達します
var assert = require('assert');
var Waterline = require('waterline');
var sailsDiskAdapter = require('sails-disk');
suite('UserModel', function () {
var waterline = new Waterline();
var config = {
adapters: {
'sails-disk': sailsDiskAdapter
},
datastores: {
default: {
adapter: 'sails-disk'
}
}
}
setup(function (done) {
waterline.loadCollection(
Waterline.Collection.extend(require('../models/User.js'))
);
waterline.loadCollection(
Waterline.Collection.extend(require('../models/Pet.js'))
);
waterline.initialize(config, function (err, ontology) {
if (err) {
return done(err);
}
done();
});
});
teardown(function () {
var adapters = config.adapters || {};
var promises = [];
Object.keys(adapters)
.forEach(function (adapter) {
if (adapters[adapter].teardown) {
var promise = new Promise(function (resolve) {
adapters[adapter].teardown(null, resolve);
});
promises.push(promise);
}
});
return Promise.all(promises);
});
test('should be able to create a user', function () {
var User = waterline.collections.user;
return User.create({
firstName: 'Neil',
lastName: 'Armstrong'
})
.then(function (user) {
assert.equal(user.firstName, 'Neil', 'should have set the first name');
assert.equal(user.lastName, 'Armstrong', 'should have set the last name');
assert.equal(user.pets.length, 0, 'should have no pets');
});
});
});
明らかに、モデルのテストファイルを追加するにつれて、コードをユーティリティライブラリにリファクタリングする余地はたくさんあります。
あとはテストを実行するだけです
$ mocha
UserModel
✓ should be able to create a user
1 passing (83ms)