除了与 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 用作独立模块,我们需要两种成分:适配器和模型定义。
最简单的适配器是 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
,以便以后可以引用它,并声明我们将使用哪个数据存储。
数据存储是适配器的实例。例如,您可能为正在使用的每种存储类型(文件、MySQL 等)都有一个数据存储。您甚至可能为同一类型的适配器拥有多个数据存储。
attributes
定义模型的属性。在传统数据库中,这些属性将与表中的列一致。我们的示例 pets
有点不同,因为它定义了一种关联,允许用户拥有多个宠物。
在关系数据库中,
pets
属性不会显示为列。相反,它建立了一个与我们将要定义的宠物模型的虚拟一对多关联。
我们现在必须定义什么是宠物
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
字段,它指定了该宠物的所有者。
在我们的示例中,宠物只能有一个主人,我们在
owner
字段中提供了关联的模型(在本例中为user
)。请注意,模型的名称需要与赋予模型的identity
相匹配。同样,关系数据库将在本示例中创建一个名为owner
的列,其中包含指向user
表的外键。
接下来,我们有一些更无聊的设置工作
waterline.registerModel(userCollection);
waterline.registerModel(petCollection);
在这里,我们将模型规范添加到 waterline
实例本身中。
最后但并非最不重要的一点是,我们必须配置数据存储
var config = {
adapters: {
'disk': sailsDiskAdapter
},
datastores: {
default: {
adapter: 'disk'
}
}
};
在这里,我们指定了将使用的 adapters
——每个类型为我们打算采用的存储类型——以及我们的 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);
});//_∏_
});
这是一大块代码,所以让我们逐段分解它。
首先,我们 initialize
Waterline 实例。这将连接数据存储(可能登录到一个或多个数据库服务器),解析任何寻找关联的模型,并执行大量其他神奇操作。当所有这些都完成后,它将委托给我们在第二个参数中传递的回调。
在检查是否有错误之后,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 } ]
这些是赋予模型的属性,我们可以看到为我们自动生成的 primary key。我们还可以看到 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
函数清除适配器,以便未来的测试可以从干净的状态开始(它允许您安全地将 -w
选项与 Mocha 一起使用)。请注意,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)