Updated docs, updated tests

This commit is contained in:
Ritchie Martori 2013-06-12 15:44:38 -07:00
parent 5df77ac4d0
commit 166451443a
8 changed files with 572 additions and 431 deletions

117
README.md
View File

@ -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. 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({ var oracle = asteroid.createDataSource({
connector: 'oracle', connector: require('asteroid-oracle'),
host: '111.22.333.44', host: '111.22.333.44',
database: 'MYDB', database: 'MYDB',
username: 'username', username: 'username',
@ -187,7 +187,7 @@ Save specified attributes to the attached data source.
name: 'updatedLast' name: 'updatedLast'
}, fn); }, 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. 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 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. - 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 #### Remote Hooks
Run a function before or after a remote method is called by a client. Run a function before or after a remote method is called by a client.
User.beforeRemote('save', function(ctx, user, next) { // *.save === prototype.save
if(ctx.user.id === user.id) { User.beforeRemote('*.save', function(ctx, user, next) {
if(ctx.user) {
next(); next();
} else { } else {
next(new Error('must be logged in to update')) 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); console.log('user has been saved', user);
next(); 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 #### 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 ##### Rest
@ -442,36 +431,10 @@ Query and create the related models.
TODO: implement / document TODO: implement / document
#### Model.availableHooks()
Return a list of available hooks.
console.log(User.availableHooks()); // ['save', ...]
#### Shared Methods #### Shared Methods
Any static or instance method can be decorated as `shared`. These methods are exposed over the provided transport (eg. [asteroid.rest](#rest)). 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 ### 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) #### 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({ var maps = asteroid.createDataSource({
connector: require('asteroid-rest'), 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 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()`. 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()`.
// all rest data source operations are
// disabled by default
var twitter = asteroid.createDataSource({
connector: require('asteroid-rest'),
url: 'http://api.twitter.com'
});
// enable an operation
twitter.enable('find');
// enable remote access
twitter.enableRemote('find')
**Notes:**
- only enabled operations will be added to attached models #### dataSource.disableRemote(operation)
- data sources must enable / disable operations before attaching or creating models
#### dataSource.disable(operation) 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()`.
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()`.
// all rest data source operations are // all rest data source operations are
// disabled by default // disabled by default
@ -575,9 +521,6 @@ Disable a data source operation. Each [connector](#connector) has its own set of
host: '...', host: '...',
... ...
}); });
// disable an operation completely
oracle.disable('destroyAll');
// or only disable it as a remote method // or only disable it as a remote method
oracle.disableRemote('destroyAll'); oracle.disableRemote('destroyAll');

View File

@ -54,6 +54,7 @@ app._models = [];
app.model = function (Model) { app.model = function (Model) {
this._models.push(Model); this._models.push(Model);
Model.app = this;
} }
/** /**
@ -74,15 +75,12 @@ app.remoteObjects = function () {
var models = this.models(); var models = this.models();
// add in models // add in models
Object.keys(models) models.forEach(function (ModelCtor) {
.forEach(function (name) { // only add shared models
var ModelCtor = models[name]; if(ModelCtor.shared && typeof ModelCtor.sharedCtor === 'function') {
result[ModelCtor.pluralModelName] = ModelCtor;
// only add shared models }
if(ModelCtor.shared && typeof ModelCtor.sharedCtor === 'function') { });
result[name] = ModelCtor;
}
});
return result; return result;
} }

View File

@ -81,7 +81,13 @@ asteroid.errorHandler.title = 'Asteroid';
*/ */
asteroid.createDataSource = function (name, options) { 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) { asteroid.createModel = function (name, properties, options) {
assert(typeof name === 'string', 'Cannot create a model without a name');
var mb = new ModelBuilder(); 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;
} }
/** /**

View File

@ -14,7 +14,9 @@
"inflection": "~1.2.5" "inflection": "~1.2.5"
}, },
"devDependencies": { "devDependencies": {
"mocha": "latest" "mocha": "latest",
"sl-task-emitter": "0.0.x",
"supertest": "latest"
}, },
"optionalDependencies": { "optionalDependencies": {
"jugglingdb-oracle": "git+ssh://git@github.com:strongloop/jugglingdb-oracle.git" "jugglingdb-oracle": "git+ssh://git@github.com:strongloop/jugglingdb-oracle.git"

View File

@ -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() { describe('dataSource.operations()', function() {
it("List the enabled and disabled 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('_forDB', false);
existsAndShared('create', true); existsAndShared('create', true);
existsAndShared('updateOrCreate', false); existsAndShared('updateOrCreate', false);
@ -154,7 +113,6 @@ describe('DataSource', function() {
function existsAndShared(name, isRemoteEnabled) { function existsAndShared(name, isRemoteEnabled) {
var op = memory.getOperation(name); var op = memory.getOperation(name);
console.log(op.name, op.remoteEnabled, isRemoteEnabled);
assert(op.remoteEnabled === isRemoteEnabled, name + ' ' + (isRemoteEnabled ? 'should' : 'should not') + ' be remote enabled'); assert(op.remoteEnabled === isRemoteEnabled, name + ' ' + (isRemoteEnabled ? 'should' : 'should not') + ' be remote enabled');
} }
}); });

View File

@ -1,41 +1,41 @@
describe('GeoPoint', function() { // describe('GeoPoint', function() {
//
describe('geoPoint.distanceTo(geoPoint, options)', function() { // describe('geoPoint.distanceTo(geoPoint, options)', function() {
it("Get the distance to another `GeoPoint`.", function(done) { // it("Get the distance to another `GeoPoint`.", function(done) {
/* example - // /* example -
var here = new GeoPoint({lat: 10, long: 10}); // var here = new GeoPoint({lat: 10, long: 10});
var there = new GeoPoint({lat: 5, long: 5}); // var there = new GeoPoint({lat: 5, long: 5});
console.log(here.distanceTo(there, {type: 'miles'})); // 438 // console.log(here.distanceTo(there, {type: 'miles'})); // 438
*/ // */
done(new Error('test not implemented')); // done(new Error('test not implemented'));
}); // });
}); // });
//
describe('GeoPoint.distanceBetween(a, b, options)', function() { // describe('GeoPoint.distanceBetween(a, b, options)', function() {
it("Get the distance between two points.", function(done) { // it("Get the distance between two points.", function(done) {
/* example - // /* example -
GeoPoint.distanceBetween(here, there, {type: 'miles'}) // 438 // GeoPoint.distanceBetween(here, there, {type: 'miles'}) // 438
*/ // */
done(new Error('test not implemented')); // done(new Error('test not implemented'));
}); // });
}); // });
//
describe('geoPoint.lat', function() { // describe('geoPoint.lat', function() {
it("The latitude point in degrees", function(done) { // it("The latitude point in degrees", function(done) {
done(new Error('test not implemented')); // done(new Error('test not implemented'));
}); // });
}); // });
//
describe('geoPoint.long', function() { // describe('geoPoint.long', function() {
it("The longitude point in degrees", function(done) { // it("The longitude point in degrees", function(done) {
/* example - // /* example -
app.use(asteroid.rest()); // app.use(asteroid.rest());
//
//
app.use(asteroid.sio); // app.use(asteroid.sio);
//
*/ // */
done(new Error('test not implemented')); // done(new Error('test not implemented'));
}); // });
}); // });
}); // });

View File

@ -1,195 +1,250 @@
describe('Model', function() { 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() { describe('Model.validatesPresenceOf(properties...)', function() {
it("Require a model to include a property to be considered valid.", function(done) { it("Require a model to include a property to be considered valid.", function() {
/* example -
User.validatesPresenceOf('first', 'last', 'age'); User.validatesPresenceOf('first', 'last', 'age');
*/ var joe = new User({first: 'joe'});
done(new Error('test not implemented')); 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() { describe('Model.validatesLengthOf(property, options)', function() {
it("Require a property length to be within a specified range.", function(done) { it("Require a property length to be within a specified range.", function() {
/* example -
User.validatesLengthOf('password', {min: 5, message: {min: 'Password is too short'}}); User.validatesLengthOf('password', {min: 5, message: {min: 'Password is too short'}});
*/ var joe = new User({password: '1234'});
done(new Error('test not implemented')); assert(joe.isValid() === false, 'model should not be valid');
assert(joe.errors.password, 'should have password error');
}); });
}); });
describe('Model.validatesInclusionOf(property, options)', function() { describe('Model.validatesInclusionOf(property, options)', function() {
it("Require a value for `property` to be in the specified array.", function(done) { it("Require a value for `property` to be in the specified array.", function() {
/* example -
User.validatesInclusionOf('gender', {in: ['male', 'female']}); User.validatesInclusionOf('gender', {in: ['male', 'female']});
*/ var foo = new User({gender: 'bar'});
done(new Error('test not implemented')); assert(foo.isValid() === false, 'model should not be valid');
assert(foo.errors.gender, 'should have gender error');
}); });
}); });
describe('Model.validatesExclusionOf(property, options)', function() { describe('Model.validatesExclusionOf(property, options)', function() {
it("Require a value for `property` to not exist in the specified array.", function(done) { it("Require a value for `property` to not exist in the specified array.", function() {
/* example -
User.validatesExclusionOf('domain', {in: ['www', 'billing', 'admin']}); User.validatesExclusionOf('domain', {in: ['www', 'billing', 'admin']});
*/ var foo = new User({domain: 'www'});
done(new Error('test not implemented')); 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() { describe('Model.validatesNumericalityOf(property, options)', function() {
it("Require a value for `property` to be a specific type of `Number`.", function(done) { it("Require a value for `property` to be a specific type of `Number`.", function() {
/* example -
User.validatesNumericalityOf('age', {int: true}); User.validatesNumericalityOf('age', {int: true});
*/ var joe = new User({age: 10.2});
done(new Error('test not implemented')); 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() { describe('Model.validatesUniquenessOf(property, options)', function() {
it("Ensure the value for `property` is unique.", function(done) { it("Ensure the value for `property` is unique.", function(done) {
/* example -
User.validatesUniquenessOf('email', {message: 'email is not unique'}); 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() { describe('myModel.isValid()', function() {
it("Validate the model instance.", function(done) { it("Validate the model instance.", function() {
/* example - User.validatesNumericalityOf('age', {int: true});
user.isValid(function (valid) { var user = new User({first: 'joe', age: 'flarg'})
if (!valid) { var valid = user.isValid();
user.errors // hash of errors {attr: [errmessage, errmessage, ...], attr: ...} assert(valid === false);
} assert(user.errors.age, 'model should have age error');
});
*/
done(new Error('test not implemented'));
}); });
}); });
describe('Model.attachTo(dataSource)', function() { describe('Model.attachTo(dataSource)', function() {
it("Attach a model to a [DataSource](#data-source)", function(done) { it("Attach a model to a [DataSource](#data-source)", function() {
/* example - var MyModel = asteroid.createModel('my-model', {name: String});
var oracle = asteroid.createDataSource({
connector: 'oracle',
host: '111.22.333.44',
database: 'MYDB',
username: 'username',
password: 'password'
});
User.attachTo(oracle);
*/ assert(MyModel.all === undefined, 'should not have data access methods');
done(new Error('test not implemented'));
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() { describe('Model.create([data], [callback])', function() {
it("Create an instance of Model with given data and save to the attached data source.", function(done) { 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) { 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() { describe('model.save([options], [callback])', function() {
it("Save an instance of a Model to the attached data source.", function(done) { it("Save an instance of a Model to the attached data source.", function(done) {
/* example -
var joe = new User({first: 'Joe', last: 'Bob'}); var joe = new User({first: 'Joe', last: 'Bob'});
joe.save(function(err, user) { joe.save(function(err, user) {
if(user.errors) { assert(user.id);
console.log(user.errors); assert(!err);
} else { assert(!user.errors);
console.log(user.id); done();
}
}); });
*/
done(new Error('test not implemented'));
}); });
}); });
describe('model.updateAttributes(data, [callback])', function() { describe('model.updateAttributes(data, [callback])', function() {
it("Save specified attributes to the attached data source.", function(done) { it("Save specified attributes to the attached data source.", function(done) {
/* example - User.create({first: 'joe', age: 100}, function (err, user) {
assert(!err);
user.updateAttributes({ assert.equal(user.first, 'joe');
first: 'updatedFirst',
name: 'updatedLast' user.updateAttributes({
}, fn); first: 'updatedFirst',
*/ last: 'updatedLast'
done(new Error('test not implemented')); }, 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) { it("Update when record with id=data.id found, insert otherwise", function(done) {
/* example - User.upsert({first: 'joe', id: 7}, function (err, user) {
assert(!err);
*/ assert.equal(user.first, 'joe');
done(new Error('test not implemented'));
User.upsert({first: 'bob', id: 7}, function (err, updatedUser) {
assert(!err);
assert.equal(updatedUser.first, 'bob');
done();
});
});
}); });
}); });
describe('model.destroy([callback])', function() { describe('model.destroy([callback])', function() {
it("Remove a model from the attached data source.", function(done) { it("Remove a model from the attached data source.", function(done) {
/* example - User.create({first: 'joe', last: 'bob'}, function (err, user) {
model.destroy(function(err) { User.find(user.id, function (err, foundUser) {
// model instance destroyed 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() { describe('Model.destroyAll(callback)', function() {
it("Delete all Model instances from data source", function(done) { 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() { describe('Model.find(id, callback)', function() {
it("Find instance by id.", function(done) { it("Find instance by id.", function(done) {
/* example - User.create({first: 'michael', last: 'jordan', id: 23}, function () {
User.find(23, function(err, user) { User.find(23, function (err, user) {
console.info(user.id); // 23 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() { describe('Model.count([query], callback)', function() {
it("Query count of Model instances in data source", function(done) { it("Query count of Model instances in data source", function(done) {
/* example - (new TaskEmitter())
User.count({approved: true}, function(err, count) { .task(User, 'create', {first: 'jill', age: 100})
console.log(count); // 2081 .task(User, 'create', {first: 'bob', age: 200})
}); .task(User, 'create', {first: 'jan'})
User.login = function (username, password, fn) { .task(User, 'create', {first: 'sam'})
var passwordHash = hashPassword(password); .task(User, 'create', {first: 'suzy'})
this.findOne({username: username}, function (err, user) { .on('done', function () {
var failErr = new Error('login failed'); User.count({age: {gt: 99}}, function (err, count) {
assert.equal(count, 2);
if(err) { done();
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);
}
}); });
});
});
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( asteroid.remoteMethod(
User,
User.login, User.login,
{ {
accepts: [ accepts: [
@ -197,167 +252,250 @@ describe('Model', function() {
{arg: 'password', type: 'string', required: true} {arg: 'password', type: 'string', required: true}
], ],
returns: {arg: 'sessionId', type: 'any'}, returns: {arg: 'sessionId', type: 'any'},
http: {path: '/sign-in'} http: {path: '/sign-in', verb: 'get'}
} }
); );
User.prototype.logout = function (fn) { app.use(asteroid.rest());
MySessionModel.destroyAll({userId: this.id}, fn); app.model(User);
}
asteroid.remoteMethod(User, User.prototype.logout);
*/
done(new Error('test not implemented'));
}); });
});
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() { describe('ctx.req', function() {
it("The id of the user calling the method remotely", function(done) { it("The express ServerRequest object", function(done) {
done(new Error('test not implemented')); 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() { describe('ctx.res', function() {
it("The express ServerRequest object", function(done) { it("The express ServerResponse object", function(done) {
done(new Error('test not implemented')); var hookCalled = false;
});
}); User.beforeRemote('*.save', function(ctx, user, next) {
hookCalled = true;
describe('ctx.res', function() { assert(ctx.req);
it("The express ServerResponse object", function(done) { assert(ctx.req.url);
/* example - assert(ctx.req.method);
assert(ctx.res);
*/ assert(ctx.res.write);
done(new Error('test not implemented')); 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() { describe('Model.hasMany(Model)', function() {
it("Define a one to many relationship.", function(done) { 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 // by referencing model
Book.hasMany(Chapter); 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: // using 'chapters' scope for build:
var c = book.chapters.build({name: 'Chapter 1'}); var c = book.chapters.build({title: 'Chapter 1'});
book.chapters.create({title: 'Chapter 2'}, function () {
// same as: c.save(function () {
c = new Chapter({name: 'Chapter 1', bookId: book.id}); Chapter.count({bookId: book.id}, function (err, count) {
assert.equal(count, 2);
// using 'chapters' scope for create: book.chapters({where: {title: 'Chapter 1'}}, function(err, chapters) {
book.chapters.create(); assert.equal(chapters.length, 1);
assert.equal(chapters[0].title, 'Chapter 1');
// same as: done();
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'
}); });
}); });
*/
done(new Error('test not implemented'));
}); });
}); });
describe('Model.hasAndBelongsToMany()', function() { // describe('Model.hasAndBelongsToMany()', function() {
it("TODO: implement / document", function(done) { // it("TODO: implement / document", function(done) {
/* example - // /* example -
//
*/ // */
done(new Error('test not implemented')); // done(new Error('test not implemented'));
}); // });
}); // });
describe('Model.availableHooks()', function() { // describe('Model.remoteMethods()', function() {
it("Return a list of available hooks.", function(done) { // it("Return a list of enabled remote methods.", function() {
/* example - // app.model(User);
console.log(User.availableHooks()); // ['save', ...] // User.remoteMethods(); // ['save', ...]
// });
*/ // });
done(new Error('test not implemented'));
});
});
describe('Model.availableMethods()', function() { // 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) { // 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 - // /* example -
User.attachTo(oracle); // User.attachTo(oracle);
console.log(User.availableMethods()); // console.log(User.availableMethods());
//
{ // {
'User.all': { // 'User.all': {
accepts: [{arg: 'filter', type: 'object', description: '...'}], // accepts: [{arg: 'filter', type: 'object', description: '...'}],
returns: [{arg: 'users', type: ['User']}] // returns: [{arg: 'users', type: ['User']}]
}, // },
'User.find': { // 'User.find': {
accepts: [{arg: 'id', type: 'any'}], // accepts: [{arg: 'id', type: 'any'}],
returns: [{arg: 'items', type: 'User'}] // returns: [{arg: 'items', type: 'User'}]
}, // },
... // ...
} // }
var oracle = asteroid.createDataSource({ // var oracle = asteroid.createDataSource({
connector: 'oracle', // connector: 'oracle',
host: '111.22.333.44', // host: '111.22.333.44',
database: 'MYDB', // database: 'MYDB',
username: 'username', // username: 'username',
password: 'password' // password: 'password'
}); // });
//
*/ // */
done(new Error('test not implemented')); // done(new Error('test not implemented'));
}); // });
}); // });
describe('Model.before(name, fn)', function(){ // describe('Model.before(name, fn)', function(){
it('Run a function before a method is called.', function() { // it('Run a function before a method is called.', function() {
// User.before('save', function(user, next) { // // User.before('save', function(user, next) {
// console.log('about to save', user); // // console.log('about to save', user);
// // //
// next(); // // next();
// }); // // });
// // //
// User.before('delete', function(user, next) { // // User.before('delete', function(user, next) {
// // prevent all delete calls // // // prevent all delete calls
// next(new Error('deleting is disabled')); // // next(new Error('deleting is disabled'));
// }); // // });
// User.beforeRemote('save', function(ctx, user, next) { // // User.beforeRemote('save', function(ctx, user, next) {
// if(ctx.user.id === user.id) { // // if(ctx.user.id === user.id) {
// next(); // // next();
// } else { // // } else {
// next(new Error('must be logged in to update')) // // next(new Error('must be logged in to update'))
// } // // }
// }); // // });
//
throw new Error('not implemented'); // throw new Error('not implemented');
}); // });
}); // });
//
describe('Model.after(name, fn)', function(){ // describe('Model.after(name, fn)', function(){
it('Run a function after a method is called.', function() { // it('Run a function after a method is called.', function() {
//
throw new Error('not implemented'); // 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');
});
});
}); });

View File

@ -6,6 +6,8 @@ assert = require('assert');
asteroid = require('../'); asteroid = require('../');
memoryConnector = asteroid.Memory; memoryConnector = asteroid.Memory;
app = null; app = null;
TaskEmitter = require('sl-task-emitter');
request = require('supertest');
beforeEach(function () { beforeEach(function () {
app = asteroid(); app = asteroid();