Doc updates, tests

This commit is contained in:
Ritchie Martori 2013-06-11 09:01:44 -07:00
parent 1e1a227f86
commit 935cd5cc77
11 changed files with 271 additions and 181 deletions

121
README.md
View File

@ -42,8 +42,8 @@ Create an asteroid application.
Expose a `Model` to remote clients.
var memory = asteroid.createDataSource({connector: 'memory'});
var Color = memory.defineModel({name: String});
var memory = asteroid.createDataSource({connector: asteroid.Memory});
var Color = memory.createModel('color', {name: String});
app.model(Color);
app.use(asteroid.rest());
@ -57,23 +57,29 @@ Get the app's exposed models.
var models = app.models();
models.forEach(function (Model) {
console.log(Model.name); // color
console.log(Model.modelName); // color
});
### Model
An Asteroid `Model` is a vanilla JavaScript class constructor with an attached set of properties and settings. A `Model` instance is created by passing a data object containing properties to the `Model` constructor.
An Asteroid `Model` is a vanilla JavaScript class constructor with an attached set of properties and options. A `Model` instance is created by passing a data object containing properties to the `Model` constructor. A `Model` constructor will clean the object passed to it and only set the values matching the properties you define.
var Color = asteroid.createModel({name: 'string'});
// valid color
var Color = asteroid.createModel('color', {name: String});
var red = new Color({name: 'red'});
console.log(red.name); // red
// invalid color
var foo = new Color({bar: 'bat baz'});
console.log(foo.bar); // undefined
**Properties**
A model defines a list of property names, types and other validation metadata. A [DataSource](#data-source) uses this definition to validate a `Model` during operations such as `save()`.
**Settings**
**Options**
Some [DataSources](#data-source) may support additional `Model` settings.
Some [DataSources](#data-source) may support additional `Model` options.
Define an asteroid model.
@ -254,10 +260,9 @@ Define a static model method.
});
}
Expose the static model method to clients as a [remote method](#remote-method).
Setup the static model method to be exposed to clients as a [remote method](#remote-method).
asteroid.remoteMethod(
User,
User.login,
{
accepts: [
@ -279,13 +284,13 @@ Define an instance method.
Define a remote model instance method.
asteroid.remoteMethod(User, User.prototype.logout);
asteroid.remoteMethod(User.prototype.logout);
#### Remote Methods
Both instance and static methods can be exposed to clients. A remote method must accept a callback with the conventional `fn(err, result, ...)` signature.
##### asteroid.remoteMethod(Model, fn, [options]);
##### asteroid.remoteMethod(fn, [options]);
Expose a remote method.
@ -294,7 +299,6 @@ Expose a remote method.
}
asteroid.remoteMethod(
Product,
Product.stats,
{
returns: {arg: 'stats', type: 'array'},
@ -341,12 +345,23 @@ Run a function before or after a model method is called.
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
@ -360,6 +375,11 @@ Run a function before or after a remote method is called by a client.
}
});
User.afterRemote('save', function(ctx, user, next) {
console.log('user has been saved', user);
next();
});
#### 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.
@ -467,56 +487,70 @@ Define a data source for persisting models.
password: 'password'
});
#### dataSource.createModel(name, properties, settings)
#### dataSource.createModel(name, properties, options)
Define a model and attach it to a `DataSource`.
var Color = oracle.createModel('color', {name: String});
#### dataSource.discover(options, fn)
Discover an object containing properties and settings for an existing data source.
oracle.discover({owner: 'MYORG'}, function(err, tables) {
var productSchema = tables.PRODUCTS;
var ProductModel = oracle.createModel('product', productSchema.properties, productSchema.settings);
});
#### dataSource.discoverSync(options)
Synchronously discover an object containing properties and settings for an existing data source tables or collections.
var tables = oracle.discover({owner: 'MYORG'});
var productSchema = tables.PRODUCTS;
var ProductModel = oracle.createModel('product', productSchema.properties, productSchema.settings);
#### dataSource.discoverModels(options, fn)
#### dataSource.discoverAndBuildModels(owner, tableOrView, options, fn)
Discover a set of models based on tables or collections in a data source.
oracle.discoverModels({owner: 'MYORG'}, function(err, models) {
oracle.discoverAndBuildModels('MYORG', function(err, models) {
var ProductModel = models.Product;
});
**Note:** The `models` will contain all properties and settings discovered from the data source. It will also automatically discover and create relationships.
**Note:** The `models` will contain all properties and options discovered from the data source. It will also automatically discover and create relationships.
#### dataSource.discoverModelsSync(options)
#### dataSource.discoverAndBuildModelsSync(owner, tableOrView, options)
Synchronously Discover a set of models based on tables or collections in a data source.
var models = oracle.discoverModels({owner: 'MYORG'});
var models = oracle.discoverAndBuildModelsSync('MYORG');
var ProductModel = models.Product;
#### dataSource.defineOperation(name, options, fn)
Define and enable a new operation available to all model's attached to the data source.
var maps = asteroid.createDataSource({
connector: require('asteroid-rest'),
url: 'http://api.googleapis.com/maps/api'
});
rest.defineOperation('geocode', {
url: '/geocode/json',
verb: 'get',
accepts: [
{arg: 'address', type: 'string'},
{arg: 'sensor', default: 'true'}
],
returns: {arg: 'location', type: asteroid.GeoPoint, transform: transform},
json: true,
enableRemote: true
});
function transform(res) {
var geo = res.body.results[0].geometry;
return new asteroid.GeoPoint({lat: geo.lat, long: geo.lng});
}
var GeoCoder = rest.createModel('geocoder');
GeoCoder.geocode('123 fake street', function(err, point) {
console.log(point.lat, point.long); // 24.224424 44.444445
});
#### dataSource.enable(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 rest = asteroid.createDataSource({
var twitter = asteroid.createDataSource({
connector: require('asteroid-rest'),
url: 'http://maps.googleapis.com/maps/api'
enableAll: true
url: 'http://api.twitter.com'
});
// enable an operation
@ -564,11 +598,18 @@ Output:
{
find: {
allowRemote: true,
remoteEnabled: true,
accepts: [...],
returns: [...]
enabled: true
},
save: {
remoteEnabled: true,
prototype: true,
accepts: [...],
returns: [...],
enabled: true
},
...
}
@ -577,7 +618,7 @@ Output:
Create a data source with a specific connector. See **available connectors** for specific connector documentation.
var memory = asteroid.createDataSource({
connector: require('asteroid-memory')
connector: asteroid.Memory
});
**Available Connectors**

56
TODO.md
View File

@ -1,56 +0,0 @@
- app.model(Model)
- app.models()
-
- Model.validatesPresenceOf(properties...)
- Model.validatesLengthOf(property, options)
- Model.validatesInclusionOf(property, options)
- Model.validatesExclusionOf(property, options)
- Model.validatesNumericalityOf(property, options)
- Model.validatesUniquenessOf(property, options)
- myModel.isValid()
- Model.attachTo(dataSource)
##### Model.create([data], [callback])
##### model.save([options], [callback])
##### model.updateAttributes(data, [callback])
##### model.upsert(data, callback)
##### model.destroy([callback])
##### Model.destroyAll(callback)
##### Model.find(id, callback)
##### Model.count([query], callback)
#### Static Methods
#### Instance Methods
#### Remote Methods
##### asteroid.remoteMethod(Model, fn, [options]);
#### Hooks
#### Remote Hooks
#### Context
##### ctx.me
##### Rest
###### ctx.req
###### ctx.res
#### Relationships
##### Model.hasMany(Model)
##### Model.hasAndBelongsToMany()
#### Model.availableHooks()
#### Shared Methods
#### Model.availableMethods()
### Data Source
#### dataSource.createModel(name, options, settings)
#### dataSource.discover(options, fn)
#### dataSource.discoverSync(options)
#### dataSource.discoverModels(options, fn)
#### dataSource.discoverModelsSync(options)
#### dataSource.enable(operation)
#### dataSource.disable(operation)
#### dataSource.operations()
#### Connectors
### GeoPoint
#### geoPoint.distanceTo(geoPoint, options)
#### GeoPoint.distanceBetween(a, b, options)
#### Distance Types
#### geoPoint.lat
#### geoPoint.long
### Asteroid Types
### REST Router
### SocketIO Middleware **Not Available**

View File

@ -5,13 +5,8 @@
var asteroid = module.exports = require('./lib/asteroid');
/**
* Connector
* Connectors
*/
asteroid.Connector = require('./lib/connector');
/**
* JugglingDB Connector
*/
asteroid.JdbConnector = require('./lib/jdb-connector');
asteroid.Connector = require('./lib/connectors/base-connector');
asteroid.Memory = require('./lib/connectors/memory');

View File

@ -72,18 +72,41 @@ asteroid.errorHandler.title = 'Asteroid';
/**
* Create a data source with passing the provided options to the connector.
*
* @param {String} name (optional)
* @param {Object} options
*
* - connector - an asteroid connector
* - other values - see the specified `connector` docs
*/
asteroid.createDataSource = function (options) {
var connector = options.connector;
var jdbAdapter = connector.jdbAdapter;
if(jdbAdapter) {
// TODO remove jdb dependency
delete options.connector;
return new DataSource(jdbAdapter, options);
} else {
// TODO implement asteroid data source
throw Error('unsupported adapter')
}
}
asteroid.createDataSource = function (name, options) {
return new DataSource(name, options);
}
/**
* Create a named vanilla JavaScript class constructor with an attached set of properties and options.
*
* @param {String} name - must be unique
* @param {Object} properties
* @param {Object} options (optional)
*/
asteroid.createModel = function (name, properties, options) {
var mb = new ModelBuilder();
return mb.define(name, properties, arguments);
}
/**
* Add a remote method to a model.
* @param {Function} fn
* @param {Object} options (optional)
*/
asteroid.remoteMethod = function (fn, options) {
fn.shared = true;
Object.keys(options).forEach(function (key) {
fn[key] = options[key];
});
}

39
lib/connectors/memory.js Normal file
View File

@ -0,0 +1,39 @@
/**
* Expose `Memory`.
*/
module.exports = Memory;
/**
* Module dependencies.
*/
var Connector = require('./base-connector')
, debug = require('debug')('memory')
, util = require('util')
, inherits = util.inherits
, assert = require('assert')
, JdbMemory = require('jugglingdb/lib/adapters/memory');
/**
* Create a new `Memory` connector with the given `options`.
*
* @param {Object} options
* @return {Memory}
*/
function Memory() {
// TODO implement entire memory adapter
}
/**
* Inherit from `DBConnector`.
*/
inherits(Memory, Connector);
/**
* JugglingDB Compatibility
*/
Memory.initialize = JdbMemory.initialize;

View File

@ -1,37 +1,21 @@
describe('app', function() {
describe('app.model(Model)', function() {
it("Expose a `Model` to remote clients.", function(done) {
/* example -
var memory = asteroid.createDataSource({connector: 'memory'});
var Color = memory.defineModel({name: String});
it("Expose a `Model` to remote clients.", function() {
var memory = asteroid.createDataSource({connector: asteroid.Memory});
var Color = memory.createModel('color', {name: String});
app.model(Color);
app.use(asteroid.rest());
*/
done(new Error('test not implemented'));
assert.equal(app.models().length, 1);
});
});
describe('app.models()', function() {
it("Get the app's exposed models.", function(done) {
/* example -
it("Get the app's exposed models.", function() {
var Color = asteroid.createModel('color', {name: String});
var models = app.models();
models.forEach(function (Model) {
console.log(Model.name); // color
});
var Color = asteroid.createModel({name: 'string'});
var red = new Color({name: 'red'});
var User = asteroid.createModel('user', {
first: String,
last: String,
age: Number
});
*/
done(new Error('test not implemented'));
assert.equal(models.length, 1);
assert.equal(models[0].modelName, 'color');
});
});
});

View File

@ -1,51 +1,34 @@
describe('asteroid', function() {
describe('asteroid.createDataSource(options)', function(){
it('Create a data sources with a connector.', function(done) {
done(new Error('not implemented'));
it('Create a data source with a connector.', function() {
var dataSource = asteroid.createDataSource({
connector: asteroid.Memory
});
assert(dataSource.connector());
});
});
describe('asteroid.remoteMethod(Model, fn, [options]);', function() {
it("Expose a remote method.", function(done) {
/* example -
it("Setup a remote method.", function() {
var Product = asteroid.createModel('product', {price: Number});
Product.stats = function(fn) {
myApi.getStats('products', fn);
// ...
}
asteroid.remoteMethod(
Product,
Product.stats,
{
returns: {arg: 'stats', type: 'array'},
http: {path: '/info', verb: 'get'}
}
);
// examples
{arg: 'myArg', type: 'number'}
[
{arg: 'arg1', type: 'number', required: true},
{arg: 'arg2', type: 'array'}
]
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'))
}
});
*/
done(new Error('test not implemented'));
assert.equal(Product.stats.returns.arg, 'stats');
assert.equal(Product.stats.returns.type, 'array');
assert.equal(Product.stats.http.path, '/info');
assert.equal(Product.stats.http.verb, 'get');
assert.equal(Product.stats.shared, true);
});
});
});

View File

@ -1,11 +1,30 @@
describe('DataSource', function() {
describe('dataSource.createModel(name, properties, settings)', function() {
it("Define a model and attach it to a `DataSource`.", function(done) {
/* example -
var Color = oracle.createModel('color', {name: String});
*/
done(new Error('test not implemented'));
it("Define a model and attach it to a `DataSource`.", function() {
var memory = asteroid.createDataSource({connector: asteroid.Memory});
var Color = memory.createModel('color', {name: String});
assert.isFunc(Color, 'all');
assert.isFunc(Color, 'create');
assert.isFunc(Color, 'updateOrCreate');
assert.isFunc(Color, 'upsert');
assert.isFunc(Color, 'findOrCreate');
assert.isFunc(Color, 'exists');
assert.isFunc(Color, 'find');
assert.isFunc(Color, 'findOne');
assert.isFunc(Color, 'destroyAll');
assert.isFunc(Color, 'count');
assert.isFunc(Color, 'include');
assert.isFunc(Color, 'relationNameFor');
assert.isFunc(Color, 'hasMany');
assert.isFunc(Color, 'belongsTo');
assert.isFunc(Color, 'hasAndBelongsToMany');
assert.isFunc(Color.prototype, 'save');
assert.isFunc(Color.prototype, 'isNewRecord');
assert.isFunc(Color.prototype, 'destroy');
assert.isFunc(Color.prototype, 'updateAttribute');
assert.isFunc(Color.prototype, 'updateAttributes');
assert.isFunc(Color.prototype, 'reload');
});
});
@ -114,7 +133,7 @@ describe('DataSource', function() {
...
}
var memory = asteroid.createDataSource({
connector: require('asteroid-memory')
connector: asteroid.Memory
});
{

View File

@ -315,4 +315,49 @@ describe('Model', function() {
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');
});
});
});

View File

@ -4,10 +4,27 @@
assert = require('assert');
asteroid = require('../');
memoryConnector = require('asteroid-memory');
memoryConnector = asteroid.Memory;
beforeEach(function () {
app = asteroid();
EmptyModel = asteroid.createModel();
memoryDataSource = asteroid.createDataSource({connector: memoryConnector});
});
});
assertValidDataSource = function (dataSource) {
// has methods
assert.isFunc(dataSource, 'createModel');
// assert.isFunc(dataSource, 'discover');
// assert.isFunc(dataSource, 'discoverSync');
assert.isFunc(dataSource, 'discoverAndBuildModels');
assert.isFunc(dataSource, 'discoverAndBuildModelsSync');
assert.isFunc(dataSource, 'enable');
assert.isFunc(dataSource, 'disable');
assert.isFunc(dataSource, 'defineOperation');
assert.isFunc(dataSource, 'operations');
}
assert.isFunc = function (obj, name) {
assert(obj, 'cannot assert function ' + name + ' on object that doesnt exist');
assert(typeof obj[name] === 'function', name + ' is not a function');
}