Mongoose-Scenario
Write scenario files which quickly allow you to populate Mongo / Mongoose model contents.
Installation
Use NPM to install:
npm install --save-dev mongoose-scenario
Basic usage
Scenario can be used a variety of ways but the below is the most typical example.
var mongoose = require('mongoose');
var scenario = require('mongoose-scenario');
scenario.import({
users: [
{
name: 'John User',
role: 'user',
widgets: ['widget.foo', 'widget.baz'] // These are looked up via the `_ref` property in the widgets collection
},
{
name: 'Joe Admin',
role: 'admin',
widgets: ['widget.bar']
},
],
widgets: [
{
_ref: 'widget.foo',
name: 'Widget foo',
content: 'This is the foo widget'
},
{
_ref: 'widget.bar',
name: 'Widget bar',
content: 'This is the bar widget'
},
{
_ref: 'widget.baz',
name: 'Widget baz',
content: 'This is the baz widget'
},
]
}, {
connection: db
});
In the above a number of users documents are created each refering to an array of widgets. Scenario will create all these records - in the correct order - substituting the 'real' document references as they are created.
Comparison with Mongo insert()
The 'usual' method to insert records into Mongo is to set the _id
value of documents to a referencable value in code:
// Setup a user record that has three items which are widgets
db.widgets.insert({_id: "widget-foo", name: "Widget Foo"});
db.widgets.insert({_id: "widget-bar", name: "Widget Bar"});
db.widgets.insert({_id: "widget-baz", name: "Widget Baz"});
db.users.insert({
name: "John User",
items: ['widget-foo', 'widget-bar']
});
However this method has the following disadvantages:
- Its entirely code based - all the inserts must be done in JavaScript rather than a more data friendly format such as JSON
- The
_id
fields cannot be auto-generated - this means you can't use the hash format Mongo would normally generate. Instead your fake record IDs would stand out compared to the hash values of 'real' documents.
Options
Options are specified when including the module via require()
:
var scenario = require('mongoose-scenario')({}, { // NOTE: pass a blank scenario THEN the options
/* Options */
});
scenario.import({
collectionFoo: [],
collectionBar: []
}, function(err, data) {
if (err) {
console.log('Failed to create scenario');
} else {
console.log('Scenario created', data);
}
});
Option | Type | Default | Description |
---|---|---|---|
connection | object | none | The Mongoose connection object to use - normally this is mongoose.connection |
keys | object | See code | The names of the fields Mongoose with reference (see Meta fields section) |
nuke | array | none | Array of models to clear out (i.e. remove all records) before starting |
omitFields | array | See code | Array of fields which should be purged from the row data before its passed to Mongoose |
reset | boolean | true | Whether Scenario should disguard references between runs. Set to false to keep previously created row references |
Exporting
Scenario also includes an experimental method to output database contents into a JSON stream using the .export()
method.
var mongoose = require('mongoose');
var scenario = require('mongoose-scenario');
scenario.export({
connection: db
}, function(err, json) {
// Do something with `json`
});
Examples
Populate multiple models
Similar to the single model example above, you can also populate multiple models. Either by calling the single model invocation (see above) multiple times or by passing a hash structure to scenario:
var mongoose = require('mongoose');
var scenario = require('mongoose-scenario');
scenario.import({
users: [
{
name: 'John User',
role: 'user'
},
{
name: 'Joe Admin',
role: 'admin'
},
],
widgets: [
{
name: 'Widget foo',
content: 'This is the foo widget'
},
{
name: 'Widget bar',
content: 'This is the bar widget'
},
{
name: 'Widget baz',
content: 'This is the baz widget'
},
]
}, {
connection: db
});
You can cross-link objects in the next section.
Cross-model linkages
Due to the nature of model linkages Scenario provides a simple way to refer to records via the _ref
property.
This property is not actually saved to the Mongo schema. It is used for records to refer to one another during setup.
In the below example a users and widgets model are created where a user can have multiple widgets allocated in users.items
.
var mongoose = require('mongoose');
var scenario = require('mongoose-scenario');
scenario.import({
users: [
{
name: 'John User',
role: 'user',
items: ['widget-foo']
},
{
name: 'Joe Admin',
role: 'admin',
items: ['widget-foo', 'widget-baz']
},
],
widgets: [
{
_ref: 'widget-foo',
name: 'Widget foo',
content: 'This is the foo widget'
},
{
_ref: 'widget-bar',
name: 'Widget bar',
content: 'This is the bar widget'
},
{
_ref: 'widget-baz',
name: 'Widget foo',
content: 'This is the baz widget'
},
]
}, {
connection: db
});
Model specifications and documents can be in any order (i.e. forward or backward-refs are allowed). Scenario will process all records it has and keep pointers to refered records it has not seen yet.
Scenario will process any dangling references at the end of each call to its main function so you can call scenario()
as many times as needed from as many internal stacks as needed and it should do-the-right-thing(tm).
Use within tests
Scenario can also be used to quickly setup database layouts for tests.
Heres an example using the Tape test harness:
// Setup our inital options
var scenario = require("./index.js")({}, {
connection: mongoose.connection,
nuke: true
});
tape('User setup', function(assert) {
assert.plan(1);
scenario.import({
users: [
{
name: 'Wendy User',
role: 'user',
favourite: 'widget-quz',
items: ['widget-quz']
},
]
}, function(err, data) {
if (err) return assert.fail(err);
assert.pass('Scenario setup');
});
});
tape('User setup - verify', function(assert) {
assert.plan(4);
User
.find()
.exec(function(err, data) {
if (err) return assert.fail(err);
assert.pass('Got user data');
assert.equal(data.length, 1, 'User row count');
assert.equal(data[0].name, 'Wendy User');
assert.equal(data[0].role, 'user');
});
});
Look at the test.js file for more detailed examples.
Meta field refernce
The following table describes the fields Scenario will example when processing data:
Field | Type | Description |
---|---|---|
_ref |
string | The reference used by other schema definitions (see examples) |
_after |
string / array | Indicates that this item should only be created after the referenced items. This is useful if you need rows created in a specific order |
TODO
- Setting to use selectors e.g.
widget-foo-*
as multiple glob refs - Setting to use
_id
as_ref
- Auto populate data using increment functionality e.g. '{{firstname}} {{lastname}}' fetches some fake first and last names from somewhere
- Cope with missing dependents (e.g. if record a has missing _ref but record B has it as a dependency)