Updated docs, updated tests
This commit is contained in:
parent
5df77ac4d0
commit
166451443a
117
README.md
117
README.md
|
@ -142,7 +142,7 @@ Validate the model instance.
|
|||
Attach a model to a [DataSource](#data-source). Attaching a [DataSource](#data-source) updates the model with additional methods and behaviors.
|
||||
|
||||
var oracle = asteroid.createDataSource({
|
||||
connector: 'oracle',
|
||||
connector: require('asteroid-oracle'),
|
||||
host: '111.22.333.44',
|
||||
database: 'MYDB',
|
||||
username: 'username',
|
||||
|
@ -187,7 +187,7 @@ Save specified attributes to the attached data source.
|
|||
name: 'updatedLast'
|
||||
}, fn);
|
||||
|
||||
##### model.upsert(data, callback)
|
||||
##### Model.upsert(data, callback)
|
||||
|
||||
Update when record with id=data.id found, insert otherwise. **Note:** no setters, validations or hooks applied when using upsert.
|
||||
|
||||
|
@ -335,58 +335,47 @@ Each argument may define any of the [asteroid types](#asteroid-types).
|
|||
- The callback is an assumed argument and does not need to be specified in the accepts array.
|
||||
- The err argument is also assumed and does not need to be specified in the returns array.
|
||||
|
||||
#### Hooks
|
||||
|
||||
Run a function before or after a model method is called.
|
||||
|
||||
User.before('save', function(user, next) {
|
||||
console.log('about to save', user);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
User.after('save', function(user, next) {
|
||||
console.log('after save complete', user);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
Prevent the method from being called by passing an error to `next()`.
|
||||
|
||||
User.before('delete', function(user, next) {
|
||||
// prevent all delete calls
|
||||
next(new Error('deleting is disabled'));
|
||||
});
|
||||
|
||||
User.after('delete', function(user, next) {
|
||||
console.log('deleted', user);
|
||||
next();
|
||||
});
|
||||
|
||||
#### Remote Hooks
|
||||
|
||||
Run a function before or after a remote method is called by a client.
|
||||
|
||||
User.beforeRemote('save', function(ctx, user, next) {
|
||||
if(ctx.user.id === user.id) {
|
||||
// *.save === prototype.save
|
||||
User.beforeRemote('*.save', function(ctx, user, next) {
|
||||
if(ctx.user) {
|
||||
next();
|
||||
} else {
|
||||
next(new Error('must be logged in to update'))
|
||||
}
|
||||
});
|
||||
|
||||
User.afterRemote('save', function(ctx, user, next) {
|
||||
User.afterRemote('*.save', function(ctx, user, next) {
|
||||
console.log('user has been saved', user);
|
||||
next();
|
||||
});
|
||||
|
||||
Remote hooks also support wildcards. Run a function before any remote method is called.
|
||||
|
||||
// ** will match both prototype.* and *.*
|
||||
User.beforeRemote('**', function(ctx, user, next) {
|
||||
console.log(ctx.methodString, 'was invoked remotely'); // users.prototype.save was invoked remotely
|
||||
next();
|
||||
});
|
||||
|
||||
Other wildcard examples
|
||||
|
||||
// run before any static method eg. User.all
|
||||
User.beforeRemote('*', ...);
|
||||
|
||||
// run before any instance method eg. User.prototype.save
|
||||
User.beforeRemote('prototype.*', ...);
|
||||
|
||||
#### Context
|
||||
|
||||
Remote hooks are provided with a Context `ctx` that contains raw access to the transport specific objects. The `ctx` object also has a set of consistent apis that are consistent across transports.
|
||||
Remote hooks are provided with a Context `ctx` object which contains transport specific data (eg. for http: `req` and `res`). The `ctx` object also has a set of consistent apis across transports.
|
||||
|
||||
##### ctx.me
|
||||
##### ctx.user
|
||||
|
||||
The id of the user calling the method remotely. **Note:** this is undefined if a user is not logged in.
|
||||
A `Model` representing the user calling the method remotely. **Note:** this is undefined if the remote method is not invoked by a logged in user.
|
||||
|
||||
##### Rest
|
||||
|
||||
|
@ -442,36 +431,10 @@ Query and create the related models.
|
|||
|
||||
TODO: implement / document
|
||||
|
||||
#### Model.availableHooks()
|
||||
|
||||
Return a list of available hooks.
|
||||
|
||||
console.log(User.availableHooks()); // ['save', ...]
|
||||
|
||||
#### Shared Methods
|
||||
|
||||
Any static or instance method can be decorated as `shared`. These methods are exposed over the provided transport (eg. [asteroid.rest](#rest)).
|
||||
|
||||
#### Model.availableMethods()
|
||||
|
||||
Returns the currently available api of a model as well as descriptions of any modified behavior or methods from attached data sources.
|
||||
|
||||
User.attachTo(oracle);
|
||||
console.log(User.availableMethods());
|
||||
|
||||
Output:
|
||||
|
||||
{
|
||||
'User.all': {
|
||||
accepts: [{arg: 'filter', type: 'object', description: '...'}],
|
||||
returns: [{arg: 'users', type: ['User']}]
|
||||
},
|
||||
'User.find': {
|
||||
accepts: [{arg: 'id', type: 'any'}],
|
||||
returns: [{arg: 'items', type: 'User'}]
|
||||
},
|
||||
...
|
||||
}
|
||||
|
||||
### Data Source
|
||||
|
||||
|
@ -512,7 +475,7 @@ Synchronously Discover a set of models based on tables or collections in a data
|
|||
|
||||
#### dataSource.defineOperation(name, options, fn)
|
||||
|
||||
Define and enable a new operation available to all model's attached to the data source.
|
||||
Define a new operation available to all model's attached to the data source.
|
||||
|
||||
var maps = asteroid.createDataSource({
|
||||
connector: require('asteroid-rest'),
|
||||
|
@ -542,31 +505,14 @@ Define and enable a new operation available to all model's attached to the data
|
|||
console.log(point.lat, point.long); // 24.224424 44.444445
|
||||
});
|
||||
|
||||
#### dataSource.enable(operation)
|
||||
#### dataSource.enableRemote(operation)
|
||||
|
||||
Enable a data source operation. Each [connector](#connector) has its own set of set enabled and disabled operations. You can always list these by calling `dataSource.operations()`.
|
||||
|
||||
// all rest data source operations are
|
||||
// disabled by default
|
||||
var twitter = asteroid.createDataSource({
|
||||
connector: require('asteroid-rest'),
|
||||
url: 'http://api.twitter.com'
|
||||
});
|
||||
Enable remote access to a data source operation. Each [connector](#connector) has its own set of set remotely enabled and disabled operations. You can always list these by calling `dataSource.operations()`.
|
||||
|
||||
// enable an operation
|
||||
twitter.enable('find');
|
||||
|
||||
// enable remote access
|
||||
twitter.enableRemote('find')
|
||||
|
||||
**Notes:**
|
||||
|
||||
- only enabled operations will be added to attached models
|
||||
- data sources must enable / disable operations before attaching or creating models
|
||||
#### dataSource.disableRemote(operation)
|
||||
|
||||
#### dataSource.disable(operation)
|
||||
|
||||
Disable a data source operation. Each [connector](#connector) has its own set of set enabled and disabled operations. You can always list these by calling `dataSource.operations()`.
|
||||
Disable remote access to a data source operation. Each [connector](#connector) has its own set of set enabled and disabled operations. You can always list these by calling `dataSource.operations()`.
|
||||
|
||||
// all rest data source operations are
|
||||
// disabled by default
|
||||
|
@ -575,9 +521,6 @@ Disable a data source operation. Each [connector](#connector) has its own set of
|
|||
host: '...',
|
||||
...
|
||||
});
|
||||
|
||||
// disable an operation completely
|
||||
oracle.disable('destroyAll');
|
||||
|
||||
// or only disable it as a remote method
|
||||
oracle.disableRemote('destroyAll');
|
||||
|
|
|
@ -54,6 +54,7 @@ app._models = [];
|
|||
|
||||
app.model = function (Model) {
|
||||
this._models.push(Model);
|
||||
Model.app = this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,15 +75,12 @@ app.remoteObjects = function () {
|
|||
var models = this.models();
|
||||
|
||||
// add in models
|
||||
Object.keys(models)
|
||||
.forEach(function (name) {
|
||||
var ModelCtor = models[name];
|
||||
|
||||
// only add shared models
|
||||
if(ModelCtor.shared && typeof ModelCtor.sharedCtor === 'function') {
|
||||
result[name] = ModelCtor;
|
||||
}
|
||||
});
|
||||
models.forEach(function (ModelCtor) {
|
||||
// only add shared models
|
||||
if(ModelCtor.shared && typeof ModelCtor.sharedCtor === 'function') {
|
||||
result[ModelCtor.pluralModelName] = ModelCtor;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
104
lib/asteroid.js
104
lib/asteroid.js
|
@ -81,7 +81,13 @@ asteroid.errorHandler.title = 'Asteroid';
|
|||
*/
|
||||
|
||||
asteroid.createDataSource = function (name, options) {
|
||||
return new DataSource(name, options);
|
||||
var ds = new DataSource(name, options);
|
||||
ds.createModel = function (name, properties, settings) {
|
||||
var Model = asteroid.createModel(name, properties, settings);
|
||||
Model.attachTo(ds);
|
||||
return Model;
|
||||
}
|
||||
return ds;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,8 +99,102 @@ asteroid.createDataSource = function (name, options) {
|
|||
*/
|
||||
|
||||
asteroid.createModel = function (name, properties, options) {
|
||||
assert(typeof name === 'string', 'Cannot create a model without a name');
|
||||
|
||||
var mb = new ModelBuilder();
|
||||
return mb.define(name, properties, arguments);
|
||||
var ModelCtor = mb.define(name, properties, arguments);
|
||||
var hasMany = ModelCtor.hasMany;
|
||||
|
||||
if(hasMany) {
|
||||
ModelCtor.hasMany = function (anotherClass, params) {
|
||||
var origArgs = arguments;
|
||||
var thisClass = this, thisClassName = this.modelName;
|
||||
params = params || {};
|
||||
if (typeof anotherClass === 'string') {
|
||||
params.as = anotherClass;
|
||||
if (params.model) {
|
||||
anotherClass = params.model;
|
||||
} else {
|
||||
var anotherClassName = i8n.singularize(anotherClass).toLowerCase();
|
||||
for(var name in this.schema.models) {
|
||||
if (name.toLowerCase() === anotherClassName) {
|
||||
anotherClass = this.schema.models[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pluralized = i8n.pluralize(anotherClass.modelName);
|
||||
var methodName = params.as ||
|
||||
i8n.camelize(pluralized, true);
|
||||
var proxyMethodName = 'get' + i8n.titleize(pluralized, true);
|
||||
|
||||
// create a proxy method
|
||||
var fn = this.prototype[proxyMethodName] = function () {
|
||||
// this cannot be a shared method
|
||||
// because it is defined when you
|
||||
// inside a property getter...
|
||||
|
||||
this[methodName].apply(thisClass, arguments);
|
||||
};
|
||||
|
||||
fn.shared = true;
|
||||
fn.http = {verb: 'get', path: '/' + methodName};
|
||||
hasMany.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
ModelCtor.shared = true;
|
||||
ModelCtor.sharedCtor = function (data, id, fn) {
|
||||
if(typeof data === 'function') {
|
||||
fn = data;
|
||||
data = null;
|
||||
id = null;
|
||||
} else if (typeof id === 'function') {
|
||||
fn = id;
|
||||
id = null;
|
||||
}
|
||||
|
||||
if(id && data) {
|
||||
var model = new ModelCtor(data);
|
||||
model.id = id;
|
||||
fn(null, model);
|
||||
} else if(data) {
|
||||
fn(null, new ModelCtor(data));
|
||||
} else if(id) {
|
||||
ModelCtor.find(id, fn);
|
||||
} else {
|
||||
fn(new Error('must specify an id or data'));
|
||||
}
|
||||
};
|
||||
|
||||
ModelCtor.sharedCtor.accepts = [
|
||||
{arg: 'data', type: 'object'},
|
||||
{arg: 'id', type: 'any'}
|
||||
];
|
||||
|
||||
ModelCtor.sharedCtor.http = [
|
||||
{path: '/'},
|
||||
{path: '/:id'}
|
||||
];
|
||||
|
||||
// before remote hook
|
||||
ModelCtor.beforeRemote = function (name, fn) {
|
||||
var remotes = this.app.remotes();
|
||||
remotes.before(ModelCtor.pluralModelName + '.' + name, function (ctx, next) {
|
||||
fn(ctx, ctx.instance, next);
|
||||
});
|
||||
}
|
||||
|
||||
// after remote hook
|
||||
ModelCtor.afterRemote = function (name, fn) {
|
||||
var remotes = this.app.remotes();
|
||||
remotes.before(ModelCtor.pluralModelName + '.' + name, function (ctx, next) {
|
||||
fn(ctx, ctx.instance, next);
|
||||
});
|
||||
}
|
||||
|
||||
return ModelCtor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
"inflection": "~1.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "latest"
|
||||
"mocha": "latest",
|
||||
"sl-task-emitter": "0.0.x",
|
||||
"supertest": "latest"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"jugglingdb-oracle": "git+ssh://git@github.com:strongloop/jugglingdb-oracle.git"
|
||||
|
|
|
@ -81,53 +81,12 @@ describe('DataSource', function() {
|
|||
// });
|
||||
// });
|
||||
|
||||
describe('dataSource.enable(operation)', function() {
|
||||
it("Enable a data source operation", function() {
|
||||
// enable an operation
|
||||
memory.disable('find');
|
||||
|
||||
var find = memory.getOperation('find');
|
||||
|
||||
assert.equal(find.name, 'find');
|
||||
assert.equal(find.enabled, false);
|
||||
assert.equal(find.remoteEnabled, false);
|
||||
|
||||
memory.enable('find');
|
||||
|
||||
assert.equal(find.name, 'find');
|
||||
assert.equal(find.enabled, true);
|
||||
assert.equal(find.remoteEnabled, false);
|
||||
|
||||
memory.enableRemote('find');
|
||||
|
||||
assert.equal(find.remoteEnabled, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dataSource.disable(operation)', function() {
|
||||
it("Disable a data source operation", function() {
|
||||
var find = memory.getOperation('all');
|
||||
|
||||
assert.equal(find.name, 'all');
|
||||
assert.equal(find.enabled, true);
|
||||
assert.equal(find.remoteEnabled, true);
|
||||
|
||||
memory.disableRemote('all');
|
||||
|
||||
assert.equal(find.name, 'all');
|
||||
assert.equal(find.enabled, true);
|
||||
assert.equal(find.remoteEnabled, false);
|
||||
|
||||
memory.disable('all');
|
||||
|
||||
assert.equal(find.name, 'all');
|
||||
assert.equal(find.enabled, false);
|
||||
assert.equal(find.remoteEnabled, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dataSource.operations()', function() {
|
||||
it("List the enabled and disabled operations.", function() {
|
||||
// assert the defaults
|
||||
// - true: the method should be remote enabled
|
||||
// - false: the method should not be remote enabled
|
||||
// -
|
||||
existsAndShared('_forDB', false);
|
||||
existsAndShared('create', true);
|
||||
existsAndShared('updateOrCreate', false);
|
||||
|
@ -154,7 +113,6 @@ describe('DataSource', function() {
|
|||
|
||||
function existsAndShared(name, isRemoteEnabled) {
|
||||
var op = memory.getOperation(name);
|
||||
console.log(op.name, op.remoteEnabled, isRemoteEnabled);
|
||||
assert(op.remoteEnabled === isRemoteEnabled, name + ' ' + (isRemoteEnabled ? 'should' : 'should not') + ' be remote enabled');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,41 +1,41 @@
|
|||
describe('GeoPoint', function() {
|
||||
|
||||
describe('geoPoint.distanceTo(geoPoint, options)', function() {
|
||||
it("Get the distance to another `GeoPoint`.", function(done) {
|
||||
/* example -
|
||||
var here = new GeoPoint({lat: 10, long: 10});
|
||||
var there = new GeoPoint({lat: 5, long: 5});
|
||||
console.log(here.distanceTo(there, {type: 'miles'})); // 438
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('GeoPoint.distanceBetween(a, b, options)', function() {
|
||||
it("Get the distance between two points.", function(done) {
|
||||
/* example -
|
||||
GeoPoint.distanceBetween(here, there, {type: 'miles'}) // 438
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('geoPoint.lat', function() {
|
||||
it("The latitude point in degrees", function(done) {
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('geoPoint.long', function() {
|
||||
it("The longitude point in degrees", function(done) {
|
||||
/* example -
|
||||
app.use(asteroid.rest());
|
||||
|
||||
|
||||
app.use(asteroid.sio);
|
||||
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
});
|
||||
// describe('GeoPoint', function() {
|
||||
//
|
||||
// describe('geoPoint.distanceTo(geoPoint, options)', function() {
|
||||
// it("Get the distance to another `GeoPoint`.", function(done) {
|
||||
// /* example -
|
||||
// var here = new GeoPoint({lat: 10, long: 10});
|
||||
// var there = new GeoPoint({lat: 5, long: 5});
|
||||
// console.log(here.distanceTo(there, {type: 'miles'})); // 438
|
||||
// */
|
||||
// done(new Error('test not implemented'));
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe('GeoPoint.distanceBetween(a, b, options)', function() {
|
||||
// it("Get the distance between two points.", function(done) {
|
||||
// /* example -
|
||||
// GeoPoint.distanceBetween(here, there, {type: 'miles'}) // 438
|
||||
// */
|
||||
// done(new Error('test not implemented'));
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe('geoPoint.lat', function() {
|
||||
// it("The latitude point in degrees", function(done) {
|
||||
// done(new Error('test not implemented'));
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe('geoPoint.long', function() {
|
||||
// it("The longitude point in degrees", function(done) {
|
||||
// /* example -
|
||||
// app.use(asteroid.rest());
|
||||
//
|
||||
//
|
||||
// app.use(asteroid.sio);
|
||||
//
|
||||
// */
|
||||
// done(new Error('test not implemented'));
|
||||
// });
|
||||
// });
|
||||
// });
|
|
@ -1,195 +1,250 @@
|
|||
describe('Model', function() {
|
||||
|
||||
var User, memory;
|
||||
|
||||
beforeEach(function () {
|
||||
memory = asteroid.createDataSource({connector: asteroid.Memory});
|
||||
User = memory.createModel('user', {
|
||||
'first': String,
|
||||
'last': String,
|
||||
'age': Number,
|
||||
'password': String,
|
||||
'gender': String,
|
||||
'domain': String,
|
||||
'email': String
|
||||
});
|
||||
})
|
||||
|
||||
describe('Model.validatesPresenceOf(properties...)', function() {
|
||||
it("Require a model to include a property to be considered valid.", function(done) {
|
||||
/* example -
|
||||
it("Require a model to include a property to be considered valid.", function() {
|
||||
User.validatesPresenceOf('first', 'last', 'age');
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
var joe = new User({first: 'joe'});
|
||||
assert(joe.isValid() === false, 'model should not validate');
|
||||
assert(joe.errors.last, 'should have a missing last error');
|
||||
assert(joe.errors.age, 'should have a missing age error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.validatesLengthOf(property, options)', function() {
|
||||
it("Require a property length to be within a specified range.", function(done) {
|
||||
/* example -
|
||||
it("Require a property length to be within a specified range.", function() {
|
||||
User.validatesLengthOf('password', {min: 5, message: {min: 'Password is too short'}});
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
var joe = new User({password: '1234'});
|
||||
assert(joe.isValid() === false, 'model should not be valid');
|
||||
assert(joe.errors.password, 'should have password error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.validatesInclusionOf(property, options)', function() {
|
||||
it("Require a value for `property` to be in the specified array.", function(done) {
|
||||
/* example -
|
||||
it("Require a value for `property` to be in the specified array.", function() {
|
||||
User.validatesInclusionOf('gender', {in: ['male', 'female']});
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
var foo = new User({gender: 'bar'});
|
||||
assert(foo.isValid() === false, 'model should not be valid');
|
||||
assert(foo.errors.gender, 'should have gender error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.validatesExclusionOf(property, options)', function() {
|
||||
it("Require a value for `property` to not exist in the specified array.", function(done) {
|
||||
/* example -
|
||||
it("Require a value for `property` to not exist in the specified array.", function() {
|
||||
User.validatesExclusionOf('domain', {in: ['www', 'billing', 'admin']});
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
var foo = new User({domain: 'www'});
|
||||
var bar = new User({domain: 'billing'});
|
||||
var bat = new User({domain: 'admin'});
|
||||
assert(foo.isValid() === false);
|
||||
assert(bar.isValid() === false);
|
||||
assert(bat.isValid() === false);
|
||||
assert(foo.errors.domain, 'model should have a domain error');
|
||||
assert(bat.errors.domain, 'model should have a domain error');
|
||||
assert(bat.errors.domain, 'model should have a domain error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.validatesNumericalityOf(property, options)', function() {
|
||||
it("Require a value for `property` to be a specific type of `Number`.", function(done) {
|
||||
/* example -
|
||||
it("Require a value for `property` to be a specific type of `Number`.", function() {
|
||||
User.validatesNumericalityOf('age', {int: true});
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
var joe = new User({age: 10.2});
|
||||
assert(joe.isValid() === false);
|
||||
var bob = new User({age: 0});
|
||||
assert(bob.isValid() === true);
|
||||
assert(joe.errors.age, 'model should have an age error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.validatesUniquenessOf(property, options)', function() {
|
||||
it("Ensure the value for `property` is unique.", function(done) {
|
||||
/* example -
|
||||
User.validatesUniquenessOf('email', {message: 'email is not unique'});
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
|
||||
var joe = new User({email: 'joe@joe.com'});
|
||||
var joe2 = new User({email: 'joe@joe.com'});
|
||||
|
||||
joe.save(function () {
|
||||
joe2.save(function (err) {
|
||||
assert(err, 'should get a validation error');
|
||||
assert(joe2.errors.email, 'model should have email error');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('myModel.isValid()', function() {
|
||||
it("Validate the model instance.", function(done) {
|
||||
/* example -
|
||||
user.isValid(function (valid) {
|
||||
if (!valid) {
|
||||
user.errors // hash of errors {attr: [errmessage, errmessage, ...], attr: ...}
|
||||
}
|
||||
});
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
it("Validate the model instance.", function() {
|
||||
User.validatesNumericalityOf('age', {int: true});
|
||||
var user = new User({first: 'joe', age: 'flarg'})
|
||||
var valid = user.isValid();
|
||||
assert(valid === false);
|
||||
assert(user.errors.age, 'model should have age error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.attachTo(dataSource)', function() {
|
||||
it("Attach a model to a [DataSource](#data-source)", function(done) {
|
||||
/* example -
|
||||
var oracle = asteroid.createDataSource({
|
||||
connector: 'oracle',
|
||||
host: '111.22.333.44',
|
||||
database: 'MYDB',
|
||||
username: 'username',
|
||||
password: 'password'
|
||||
});
|
||||
User.attachTo(oracle);
|
||||
it("Attach a model to a [DataSource](#data-source)", function() {
|
||||
var MyModel = asteroid.createModel('my-model', {name: String});
|
||||
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
assert(MyModel.all === undefined, 'should not have data access methods');
|
||||
|
||||
MyModel.attachTo(memory);
|
||||
|
||||
assert(typeof MyModel.all === 'function', 'should have data access methods after attaching to a data source');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.create([data], [callback])', function() {
|
||||
it("Create an instance of Model with given data and save to the attached data source.", function(done) {
|
||||
/* example -
|
||||
User.create({first: 'Joe', last: 'Bob'}, function(err, user) {
|
||||
console.log(user instanceof User); // true
|
||||
assert(user instanceof User);
|
||||
done();
|
||||
});
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('model.save([options], [callback])', function() {
|
||||
it("Save an instance of a Model to the attached data source.", function(done) {
|
||||
/* example -
|
||||
var joe = new User({first: 'Joe', last: 'Bob'});
|
||||
joe.save(function(err, user) {
|
||||
if(user.errors) {
|
||||
console.log(user.errors);
|
||||
} else {
|
||||
console.log(user.id);
|
||||
}
|
||||
assert(user.id);
|
||||
assert(!err);
|
||||
assert(!user.errors);
|
||||
done();
|
||||
});
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('model.updateAttributes(data, [callback])', function() {
|
||||
it("Save specified attributes to the attached data source.", function(done) {
|
||||
/* example -
|
||||
|
||||
user.updateAttributes({
|
||||
first: 'updatedFirst',
|
||||
name: 'updatedLast'
|
||||
}, fn);
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
User.create({first: 'joe', age: 100}, function (err, user) {
|
||||
assert(!err);
|
||||
assert.equal(user.first, 'joe');
|
||||
|
||||
user.updateAttributes({
|
||||
first: 'updatedFirst',
|
||||
last: 'updatedLast'
|
||||
}, function (err, updatedUser) {
|
||||
assert(!err);
|
||||
assert.equal(updatedUser.first, 'updatedFirst');
|
||||
assert.equal(updatedUser.last, 'updatedLast');
|
||||
assert.equal(updatedUser.age, 100);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('model.upsert(data, callback)', function() {
|
||||
describe('Model.upsert(data, callback)', function() {
|
||||
it("Update when record with id=data.id found, insert otherwise", function(done) {
|
||||
/* example -
|
||||
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
User.upsert({first: 'joe', id: 7}, function (err, user) {
|
||||
assert(!err);
|
||||
assert.equal(user.first, 'joe');
|
||||
|
||||
User.upsert({first: 'bob', id: 7}, function (err, updatedUser) {
|
||||
assert(!err);
|
||||
assert.equal(updatedUser.first, 'bob');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('model.destroy([callback])', function() {
|
||||
it("Remove a model from the attached data source.", function(done) {
|
||||
/* example -
|
||||
model.destroy(function(err) {
|
||||
// model instance destroyed
|
||||
User.create({first: 'joe', last: 'bob'}, function (err, user) {
|
||||
User.find(user.id, function (err, foundUser) {
|
||||
assert.equal(user.id, foundUser.id);
|
||||
foundUser.destroy(function () {
|
||||
User.find(user.id, function (err, notFound) {
|
||||
assert(!err);
|
||||
assert.equal(notFound, null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.destroyAll(callback)', function() {
|
||||
it("Delete all Model instances from data source", function(done) {
|
||||
done(new Error('test not implemented'));
|
||||
(new TaskEmitter())
|
||||
.task(User, 'create', {first: 'jill'})
|
||||
.task(User, 'create', {first: 'bob'})
|
||||
.task(User, 'create', {first: 'jan'})
|
||||
.task(User, 'create', {first: 'sam'})
|
||||
.task(User, 'create', {first: 'suzy'})
|
||||
.on('done', function () {
|
||||
User.count(function (err, count) {
|
||||
assert.equal(count, 5);
|
||||
User.destroyAll(function () {
|
||||
User.count(function (err, count) {
|
||||
assert.equal(count, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.find(id, callback)', function() {
|
||||
it("Find instance by id.", function(done) {
|
||||
/* example -
|
||||
User.find(23, function(err, user) {
|
||||
console.info(user.id); // 23
|
||||
User.create({first: 'michael', last: 'jordan', id: 23}, function () {
|
||||
User.find(23, function (err, user) {
|
||||
assert.equal(user.id, 23);
|
||||
assert.equal(user.first, 'michael');
|
||||
assert.equal(user.last, 'jordan');
|
||||
done();
|
||||
});
|
||||
});
|
||||
User.all({where: {age: {gt: 21}}, order: 'age DESC', limit: 10, skip: 10})
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.count([query], callback)', function() {
|
||||
it("Query count of Model instances in data source", function(done) {
|
||||
/* example -
|
||||
User.count({approved: true}, function(err, count) {
|
||||
console.log(count); // 2081
|
||||
});
|
||||
User.login = function (username, password, fn) {
|
||||
var passwordHash = hashPassword(password);
|
||||
this.findOne({username: username}, function (err, user) {
|
||||
var failErr = new Error('login failed');
|
||||
|
||||
if(err) {
|
||||
fn(err);
|
||||
} else if(!user) {
|
||||
fn(failErr);
|
||||
} else if(user.password === passwordHash) {
|
||||
MySessionModel.create({userId: user.id}, function (err, session) {
|
||||
fn(null, session.id);
|
||||
});
|
||||
} else {
|
||||
fn(failErr);
|
||||
}
|
||||
(new TaskEmitter())
|
||||
.task(User, 'create', {first: 'jill', age: 100})
|
||||
.task(User, 'create', {first: 'bob', age: 200})
|
||||
.task(User, 'create', {first: 'jan'})
|
||||
.task(User, 'create', {first: 'sam'})
|
||||
.task(User, 'create', {first: 'suzy'})
|
||||
.on('done', function () {
|
||||
User.count({age: {gt: 99}}, function (err, count) {
|
||||
assert.equal(count, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Remote Methods', function(){
|
||||
beforeEach(function () {
|
||||
User.login = function (username, password, fn) {
|
||||
if(username === 'foo' && password === 'bar') {
|
||||
fn(null, 123);
|
||||
} else {
|
||||
throw new Error('bad username and password!');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
asteroid.remoteMethod(
|
||||
User,
|
||||
User.login,
|
||||
{
|
||||
accepts: [
|
||||
|
@ -197,167 +252,250 @@ describe('Model', function() {
|
|||
{arg: 'password', type: 'string', required: true}
|
||||
],
|
||||
returns: {arg: 'sessionId', type: 'any'},
|
||||
http: {path: '/sign-in'}
|
||||
http: {path: '/sign-in', verb: 'get'}
|
||||
}
|
||||
);
|
||||
|
||||
User.prototype.logout = function (fn) {
|
||||
MySessionModel.destroyAll({userId: this.id}, fn);
|
||||
}
|
||||
|
||||
asteroid.remoteMethod(User, User.prototype.logout);
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
app.use(asteroid.rest());
|
||||
app.model(User);
|
||||
});
|
||||
});
|
||||
|
||||
describe('example remote method', function () {
|
||||
it('should allow calling remotely', function(done) {
|
||||
request(app)
|
||||
.get('/users/sign-in?username=foo&password=bar')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.end(function(err, res){
|
||||
if(err) return done(err);
|
||||
assert(res.body.$data === 123);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.beforeRemote(name, fn)', function(){
|
||||
it('Run a function before a remote method is called by a client.', function(done) {
|
||||
var hookCalled = false;
|
||||
|
||||
User.beforeRemote('*.save', function(ctx, user, next) {
|
||||
hookCalled = true;
|
||||
next();
|
||||
});
|
||||
|
||||
// invoke save
|
||||
request(app)
|
||||
.post('/users')
|
||||
.send({data: {first: 'foo', last: 'bar'}})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
if(err) return done(err);
|
||||
assert(hookCalled, 'hook wasnt called');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.afterRemote(name, fn)', function(){
|
||||
it('Run a function after a remote method is called by a client.', function(done) {
|
||||
var beforeCalled = false;
|
||||
var afterCalled = false;
|
||||
|
||||
User.beforeRemote('*.save', function(ctx, user, next) {
|
||||
assert(!afterCalled);
|
||||
beforeCalled = true;
|
||||
next();
|
||||
});
|
||||
User.afterRemote('*.save', function(ctx, user, next) {
|
||||
assert(beforeCalled);
|
||||
afterCalled = true;
|
||||
next();
|
||||
});
|
||||
|
||||
// invoke save
|
||||
request(app)
|
||||
.post('/users')
|
||||
.send({data: {first: 'foo', last: 'bar'}})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
if(err) return done(err);
|
||||
assert(beforeCalled, 'before hook was not called');
|
||||
assert(afterCalled, 'after hook was not called');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Remote Method invoking context', function () {
|
||||
// describe('ctx.user', function() {
|
||||
// it("The remote user model calling the method remotely", function(done) {
|
||||
// done(new Error('test not implemented'));
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('ctx.me', function() {
|
||||
it("The id of the user calling the method remotely", function(done) {
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
describe('ctx.req', function() {
|
||||
it("The express ServerRequest object", function(done) {
|
||||
var hookCalled = false;
|
||||
|
||||
User.beforeRemote('*.save', function(ctx, user, next) {
|
||||
hookCalled = true;
|
||||
assert(ctx.req);
|
||||
assert(ctx.req.url);
|
||||
assert(ctx.req.method);
|
||||
assert(ctx.res);
|
||||
assert(ctx.res.write);
|
||||
assert(ctx.res.end);
|
||||
next();
|
||||
});
|
||||
|
||||
// invoke save
|
||||
request(app)
|
||||
.post('/users')
|
||||
.send({data: {first: 'foo', last: 'bar'}})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
if(err) return done(err);
|
||||
assert(hookCalled);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ctx.req', function() {
|
||||
it("The express ServerRequest object", function(done) {
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('ctx.res', function() {
|
||||
it("The express ServerResponse object", function(done) {
|
||||
/* example -
|
||||
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
describe('ctx.res', function() {
|
||||
it("The express ServerResponse object", function(done) {
|
||||
var hookCalled = false;
|
||||
|
||||
User.beforeRemote('*.save', function(ctx, user, next) {
|
||||
hookCalled = true;
|
||||
assert(ctx.req);
|
||||
assert(ctx.req.url);
|
||||
assert(ctx.req.method);
|
||||
assert(ctx.res);
|
||||
assert(ctx.res.write);
|
||||
assert(ctx.res.end);
|
||||
next();
|
||||
});
|
||||
|
||||
// invoke save
|
||||
request(app)
|
||||
.post('/users')
|
||||
.send({data: {first: 'foo', last: 'bar'}})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
if(err) return done(err);
|
||||
assert(hookCalled);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe('Model.hasMany(Model)', function() {
|
||||
it("Define a one to many relationship.", function(done) {
|
||||
/* example -
|
||||
var Book = memory.createModel('book', {title: String, author: String});
|
||||
var Chapter = memory.createModel('chapter', {title: String});
|
||||
|
||||
// by referencing model
|
||||
Book.hasMany(Chapter);
|
||||
// specify the name
|
||||
Book.hasMany('chapters', {model: Chapter});
|
||||
|
||||
Book.create(function(err, book) {
|
||||
Book.create({title: 'Into the Wild', author: 'Jon Krakauer'}, function(err, book) {
|
||||
// using 'chapters' scope for build:
|
||||
var c = book.chapters.build({name: 'Chapter 1'});
|
||||
|
||||
// same as:
|
||||
c = new Chapter({name: 'Chapter 1', bookId: book.id});
|
||||
|
||||
// using 'chapters' scope for create:
|
||||
book.chapters.create();
|
||||
|
||||
// same as:
|
||||
Chapter.create({bookId: book.id});
|
||||
// using scope for querying:
|
||||
book.chapters(function(err, chapters) {
|
||||
// all chapters with bookId = book.id
|
||||
});
|
||||
|
||||
book.chapters({where: {name: 'test'}}, function(err, chapters) {
|
||||
// all chapters with bookId = book.id and name = 'test'
|
||||
var c = book.chapters.build({title: 'Chapter 1'});
|
||||
book.chapters.create({title: 'Chapter 2'}, function () {
|
||||
c.save(function () {
|
||||
Chapter.count({bookId: book.id}, function (err, count) {
|
||||
assert.equal(count, 2);
|
||||
book.chapters({where: {title: 'Chapter 1'}}, function(err, chapters) {
|
||||
assert.equal(chapters.length, 1);
|
||||
assert.equal(chapters[0].title, 'Chapter 1');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.hasAndBelongsToMany()', function() {
|
||||
it("TODO: implement / document", function(done) {
|
||||
/* example -
|
||||
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
// describe('Model.hasAndBelongsToMany()', function() {
|
||||
// it("TODO: implement / document", function(done) {
|
||||
// /* example -
|
||||
//
|
||||
// */
|
||||
// done(new Error('test not implemented'));
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('Model.availableHooks()', function() {
|
||||
it("Return a list of available hooks.", function(done) {
|
||||
/* example -
|
||||
console.log(User.availableHooks()); // ['save', ...]
|
||||
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
// describe('Model.remoteMethods()', function() {
|
||||
// it("Return a list of enabled remote methods.", function() {
|
||||
// app.model(User);
|
||||
// User.remoteMethods(); // ['save', ...]
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('Model.availableMethods()', function() {
|
||||
it("Returns the currently available api of a model as well as descriptions of any modified behavior or methods from attached data sources.", function(done) {
|
||||
/* example -
|
||||
User.attachTo(oracle);
|
||||
console.log(User.availableMethods());
|
||||
|
||||
{
|
||||
'User.all': {
|
||||
accepts: [{arg: 'filter', type: 'object', description: '...'}],
|
||||
returns: [{arg: 'users', type: ['User']}]
|
||||
},
|
||||
'User.find': {
|
||||
accepts: [{arg: 'id', type: 'any'}],
|
||||
returns: [{arg: 'items', type: 'User'}]
|
||||
},
|
||||
...
|
||||
}
|
||||
var oracle = asteroid.createDataSource({
|
||||
connector: 'oracle',
|
||||
host: '111.22.333.44',
|
||||
database: 'MYDB',
|
||||
username: 'username',
|
||||
password: 'password'
|
||||
});
|
||||
|
||||
*/
|
||||
done(new Error('test not implemented'));
|
||||
});
|
||||
});
|
||||
// describe('Model.availableMethods()', function() {
|
||||
// it("Returns the currently available api of a model as well as descriptions of any modified behavior or methods from attached data sources.", function(done) {
|
||||
// /* example -
|
||||
// User.attachTo(oracle);
|
||||
// console.log(User.availableMethods());
|
||||
//
|
||||
// {
|
||||
// 'User.all': {
|
||||
// accepts: [{arg: 'filter', type: 'object', description: '...'}],
|
||||
// returns: [{arg: 'users', type: ['User']}]
|
||||
// },
|
||||
// 'User.find': {
|
||||
// accepts: [{arg: 'id', type: 'any'}],
|
||||
// returns: [{arg: 'items', type: 'User'}]
|
||||
// },
|
||||
// ...
|
||||
// }
|
||||
// var oracle = asteroid.createDataSource({
|
||||
// connector: 'oracle',
|
||||
// host: '111.22.333.44',
|
||||
// database: 'MYDB',
|
||||
// username: 'username',
|
||||
// password: 'password'
|
||||
// });
|
||||
//
|
||||
// */
|
||||
// done(new Error('test not implemented'));
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('Model.before(name, fn)', function(){
|
||||
it('Run a function before a method is called.', function() {
|
||||
// User.before('save', function(user, next) {
|
||||
// console.log('about to save', user);
|
||||
//
|
||||
// next();
|
||||
// });
|
||||
//
|
||||
// User.before('delete', function(user, next) {
|
||||
// // prevent all delete calls
|
||||
// next(new Error('deleting is disabled'));
|
||||
// });
|
||||
// User.beforeRemote('save', function(ctx, user, next) {
|
||||
// if(ctx.user.id === user.id) {
|
||||
// next();
|
||||
// } else {
|
||||
// next(new Error('must be logged in to update'))
|
||||
// }
|
||||
// });
|
||||
|
||||
throw new Error('not implemented');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.after(name, fn)', function(){
|
||||
it('Run a function after a method is called.', function() {
|
||||
|
||||
throw new Error('not implemented');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.beforeRemote(name, fn)', function(){
|
||||
it('Run a function before a remote method is called by a client.', function() {
|
||||
|
||||
throw new Error('not implemented');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.afterRemote(name, fn)', function(){
|
||||
it('Run a function after a remote method is called by a client.', function() {
|
||||
|
||||
throw new Error('not implemented');
|
||||
});
|
||||
});
|
||||
// describe('Model.before(name, fn)', function(){
|
||||
// it('Run a function before a method is called.', function() {
|
||||
// // User.before('save', function(user, next) {
|
||||
// // console.log('about to save', user);
|
||||
// //
|
||||
// // next();
|
||||
// // });
|
||||
// //
|
||||
// // User.before('delete', function(user, next) {
|
||||
// // // prevent all delete calls
|
||||
// // next(new Error('deleting is disabled'));
|
||||
// // });
|
||||
// // User.beforeRemote('save', function(ctx, user, next) {
|
||||
// // if(ctx.user.id === user.id) {
|
||||
// // next();
|
||||
// // } else {
|
||||
// // next(new Error('must be logged in to update'))
|
||||
// // }
|
||||
// // });
|
||||
//
|
||||
// throw new Error('not implemented');
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe('Model.after(name, fn)', function(){
|
||||
// it('Run a function after a method is called.', function() {
|
||||
//
|
||||
// throw new Error('not implemented');
|
||||
// });
|
||||
// });
|
||||
});
|
|
@ -6,6 +6,8 @@ assert = require('assert');
|
|||
asteroid = require('../');
|
||||
memoryConnector = asteroid.Memory;
|
||||
app = null;
|
||||
TaskEmitter = require('sl-task-emitter');
|
||||
request = require('supertest');
|
||||
|
||||
beforeEach(function () {
|
||||
app = asteroid();
|
||||
|
|
Loading…
Reference in New Issue