除了與 Sails 框架的內建用法之外,Waterline 也可以作為獨立模組使用。
警告: 文件的此章節是針對相當進階的 Node.js 使用者。如果您不打算在 Sails 應用程式之外使用 Waterline(例如,建構您自己的框架),您可能想要跳過此頁面並回到模型與 ORM。
Waterline 可透過 NPM 取得。
$ npm install --save waterline
Waterline 在出貨時不包含任何 adapters,因此您需要另外安裝它們。例如
$ npm install --save sails-mysql
$ npm install --save-dev sails-disk
您可以將任意數量的 adapters 安裝到您的應用程式中。
sails-disk
adapter 是開發和測試的常見選擇。
如果您是 Node 的新手,請前往開始使用以了解如何在您偏好的平台上安裝 Node。
要開始將 Waterline 作為獨立模組使用,我們需要兩個要素:adapters 和模型定義。
最簡單的 adapter 是 sails-disk
adapter。讓我們在一個空的目錄中安裝它和 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
工廠物件、一個 adapter 的實例,以及 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
,以便稍後可以引用它,並宣告我們將要使用哪個資料儲存區 (datastore)。
資料儲存區 (datastore) 是 adapter 的一個實例。例如,您可以為您使用的每種儲存類型(檔案、MySQL 等)設置一個資料儲存區 (datastore)。您甚至可以為同一類型的 adapter 設置多個資料儲存區 (datastore)。
attributes
定義了模型的屬性。在傳統的資料庫中,這些屬性會與表格中的欄位對齊。我們的範例 pets
有點不同,因為它定義了一個關聯,允許使用者擁有複數的寵物。
在關聯式資料庫中,
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
實例本身中。
最後,但同樣重要的是,我們必須設定資料儲存區 (datastores)
var config = {
adapters: {
'disk': sailsDiskAdapter
},
datastores: {
default: {
adapter: 'disk'
}
}
};
在這裡,我們指定將要使用的 adapters
—每種我們打算使用的儲存類型各一個—以及我們的 datastores
,它通常將包含目標儲存系統的資料儲存區 (datastore) 詳細資訊(登入詳細資訊、檔案路徑等)。每個資料儲存區 (datastore) 都可以命名;在本例中,為了簡單起見,我們將我們的資料儲存區 (datastore) 命名為「default」。根據 adapter 的不同,datastores
中的項目可能會有更多設定可用。例如,sails-disk
adapter 允許設定 dir
和 inMemoryOnly
設定。有關更多資訊,請參閱sails-disk adapter 參考。
好的,現在是時候啟動並使用資料儲存區 (datastore) 了。首先,我們將初始化 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 實例。這會連接資料儲存區 (datastores)(可能會登入一兩個資料庫伺服器)、解析任何尋找關聯的模型,並執行大量其他的炫技操作。當所有這些都完成後,它會延遲到我們在第二個參數中傳遞的回呼函數。
在檢查錯誤之後,ontology
變數會收集使用者和寵物的集合物件。在接下來的幾行中,我們以 User
和 Pet
的形式為這些集合物件添加了一些捷徑變數。
我們通常以單數形式命名模型;也就是說,對於您從查詢中取回的物件的類型。
接下來,我們使用一些 await
的優點來建立使用者和寵物,並看看我們可以從資料儲存區 (datastore) 中取回什麼。
我們首先使用 create
方法來建立一個新使用者。我們只需要提供使用者的屬性,即可取得已建立記錄的副本。
注意:除非您另行指定,否則 Waterline 預設會新增一個
id
主鍵。
然後我們建立一個新的寵物。請注意,我們可以將在上一步中建立的使用者的 id
與該寵物建立關聯。這是透過直接設定 owner
欄位來完成的。
一旦寵物被建立,關聯的雙方都準備好了。要加入它們,我們只需將寵物添加到新使用者中的 pets
陣列中。然後我們只需使用模型上的 save
方法儲存記錄。
請注意,
save
僅在查詢傳回的模型物件上可用。我們的User
集合物件無法存取此方法。
最後,我們想看看實際塞進資料庫的是什麼,所以我們使用 User.find
從資料儲存區 (datastore) 中取得所有 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 的工具。對於間諜 (spying)、存根 (stubbing) 和模擬 (mocking),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
adapter,但此處的測試正在覆寫該設定以使用 disk adapter。我們這樣做是因為它很快,而且因為它可以偵測到我們是否嘗試在我們的模型中使用「魔法」,而這些「魔法」可能無法跨資料庫儲存移植。
teardown
函數會清除 adapters,以便未來的測試可以從乾淨的狀態開始(它允許您安全地將 -w
選項與 Mocha 一起使用)。請注意,teardown
假設您正在使用 Node 0.12;如果您不是,您要么需要使用 promise 函式庫(如 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)