2012-01-19 18:11:44 +00:00
|
|
|
## About [<img src="https://secure.travis-ci.org/1602/jugglingdb.png" />](http://travis-ci.org/#!/1602/jugglingdb)
|
2011-10-05 21:18:38 +00:00
|
|
|
|
2012-10-18 14:13:33 +00:00
|
|
|
JugglingDB is cross-db ORM for nodejs, providing **common interface** to access most popular database formats.
|
2012-12-14 15:25:32 +00:00
|
|
|
Currently supported are: mysql, sqlite3, postgres, couchdb, mongodb, redis, neo4j
|
|
|
|
and js-memory-storage (yep,
|
2011-10-06 20:21:27 +00:00
|
|
|
self-written engine for test-usage only). You can add your favorite database adapter, checkout one of the
|
|
|
|
existing adapters to learn how, it's super-easy, I guarantee.
|
2011-10-05 21:18:38 +00:00
|
|
|
|
|
|
|
## Installation
|
|
|
|
|
2012-01-18 15:53:25 +00:00
|
|
|
npm install jugglingdb
|
2011-10-05 21:18:38 +00:00
|
|
|
|
2012-01-10 16:44:56 +00:00
|
|
|
## Participation
|
|
|
|
|
2012-10-18 14:13:33 +00:00
|
|
|
- Check status of project on trello board: https://trello.com/board/jugglingdb/4f0a0b1e27d3103c64288388
|
|
|
|
- Make sure all tests pass (`npm test` command)
|
|
|
|
- Feel free to vote and comment on cards (tickets/issues), if you want to join team -- send me a message with your email.
|
2012-01-10 16:44:56 +00:00
|
|
|
|
2012-12-14 15:25:32 +00:00
|
|
|
If you want to create your own jugglingdb adapter, you should publish your
|
|
|
|
adapter package with name `jugglingdb-ADAPTERNAME`. Creating adapter is simple,
|
|
|
|
check [jugglingdb-redis](/1602/jugglingdb-redis) for example. JugglingDB core
|
|
|
|
exports common tests each adapter should pass, you could create your adapter in
|
|
|
|
TDD style, check that adapter pass all tests defined in `test/common_test.js`.
|
|
|
|
|
2011-10-05 21:18:38 +00:00
|
|
|
## Usage
|
|
|
|
|
|
|
|
```javascript
|
2012-10-18 14:13:33 +00:00
|
|
|
var Schema = require('jugglingdb').Schema;
|
2012-12-14 15:25:32 +00:00
|
|
|
var schema = new Schema('redis', {port: 6379}); //port number depends on your configuration
|
2011-10-05 21:18:38 +00:00
|
|
|
// define models
|
|
|
|
var Post = schema.define('Post', {
|
|
|
|
title: { type: String, length: 255 },
|
|
|
|
content: { type: Schema.Text },
|
2011-10-11 19:28:27 +00:00
|
|
|
date: { type: Date, default: Date.now },
|
2012-11-10 05:28:07 +00:00
|
|
|
published: { type: Boolean, default: false, index: true }
|
2011-10-05 21:18:38 +00:00
|
|
|
});
|
2012-11-10 05:28:07 +00:00
|
|
|
|
2011-10-06 07:26:26 +00:00
|
|
|
// simplier way to describe model
|
2011-10-05 21:18:38 +00:00
|
|
|
var User = schema.define('User', {
|
|
|
|
name: String,
|
2011-10-06 07:26:26 +00:00
|
|
|
bio: Schema.Text,
|
2011-10-05 21:18:38 +00:00
|
|
|
approved: Boolean,
|
|
|
|
joinedAt: Date,
|
|
|
|
age: Number
|
|
|
|
});
|
|
|
|
|
2012-11-10 05:28:07 +00:00
|
|
|
// define any custom method
|
|
|
|
User.prototype.getNameAndAge = function () {
|
|
|
|
return this.name + ', ' + this.age;
|
|
|
|
};
|
|
|
|
|
|
|
|
// models also accessible in schema:
|
|
|
|
schema.models.User;
|
|
|
|
schema.models.Post;
|
|
|
|
|
2011-10-05 21:18:38 +00:00
|
|
|
// setup relationships
|
|
|
|
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'});
|
|
|
|
// creates instance methods:
|
|
|
|
// user.posts(conds)
|
2011-10-15 15:57:35 +00:00
|
|
|
// user.posts.build(data) // like new Post({userId: user.id});
|
|
|
|
// user.posts.create(data) // build and save
|
2011-10-05 21:18:38 +00:00
|
|
|
|
|
|
|
Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
|
|
|
|
// creates instance methods:
|
|
|
|
// post.author(callback) -- getter when called with function
|
|
|
|
// post.author() -- sync getter when called without params
|
|
|
|
// post.author(user) -- setter when called with object
|
|
|
|
|
2012-02-04 13:25:39 +00:00
|
|
|
schema.automigrate(); // required only for mysql NOTE: it will drop User and Post tables
|
2011-10-05 21:18:38 +00:00
|
|
|
|
|
|
|
// work with models:
|
|
|
|
var user = new User;
|
|
|
|
user.save(function (err) {
|
2011-10-15 15:57:35 +00:00
|
|
|
var post = user.posts.build({title: 'Hello world'});
|
2011-10-05 21:18:38 +00:00
|
|
|
post.save(console.log);
|
|
|
|
});
|
|
|
|
|
2012-01-09 13:02:11 +00:00
|
|
|
// or just call it as function (with the same result):
|
|
|
|
var user = User();
|
|
|
|
user.save(...);
|
|
|
|
|
2011-10-05 21:18:38 +00:00
|
|
|
// Common API methods
|
|
|
|
|
|
|
|
// just instantiate model
|
|
|
|
new Post
|
|
|
|
// save model (of course async)
|
|
|
|
Post.create(cb);
|
|
|
|
// all posts
|
|
|
|
Post.all(cb)
|
|
|
|
// all posts by user
|
2012-01-31 07:51:55 +00:00
|
|
|
Post.all({where: {userId: user.id}, order: 'id', limit: 10, skip: 20});
|
2011-10-05 21:18:38 +00:00
|
|
|
// the same as prev
|
|
|
|
user.posts(cb)
|
2012-11-10 05:28:07 +00:00
|
|
|
// get one latest post
|
|
|
|
Post.findOne({where: {published: true}, order: 'date DESC'}, cb);
|
2011-10-05 21:18:38 +00:00
|
|
|
// same as new Post({userId: user.id});
|
2011-10-15 15:57:35 +00:00
|
|
|
user.posts.build
|
2011-10-05 21:18:38 +00:00
|
|
|
// save as Post.create({userId: user.id}, cb);
|
2011-10-15 15:57:35 +00:00
|
|
|
user.posts.create(cb)
|
2011-10-05 21:18:38 +00:00
|
|
|
// find instance by id
|
|
|
|
User.find(1, cb)
|
|
|
|
// count instances
|
2012-01-31 07:51:55 +00:00
|
|
|
User.count([conditions, ]cb)
|
2011-10-05 21:18:38 +00:00
|
|
|
// destroy instance
|
|
|
|
user.destroy(cb);
|
|
|
|
// destroy all instances
|
|
|
|
User.destroyAll(cb);
|
2011-10-10 13:39:10 +00:00
|
|
|
|
|
|
|
// Setup validations
|
|
|
|
User.validatesPresenceOf('name', 'email')
|
|
|
|
User.validatesLengthOf('password', {min: 5, message: {min: 'Password is too short'}});
|
|
|
|
User.validatesInclusionOf('gender', {in: ['male', 'female']});
|
|
|
|
User.validatesExclusionOf('domain', {in: ['www', 'billing', 'admin']});
|
|
|
|
User.validatesNumericalityOf('age', {int: true});
|
2011-11-20 07:55:23 +00:00
|
|
|
User.validatesUniquenessOf('email', {message: 'email is not unique'});
|
2011-10-10 13:39:10 +00:00
|
|
|
|
2011-11-20 07:55:23 +00:00
|
|
|
user.isValid(function (valid) {
|
|
|
|
if (!valid) {
|
|
|
|
user.errors // hash of errors {attr: [errmessage, errmessage, ...], attr: ...}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
## Callbacks
|
|
|
|
|
|
|
|
The following callbacks supported:
|
|
|
|
|
|
|
|
- afterInitialize
|
|
|
|
- beforeCreate
|
|
|
|
- afterCreate
|
|
|
|
- beforeSave
|
|
|
|
- afterSave
|
|
|
|
- beforeUpdate
|
|
|
|
- afterUpdate
|
|
|
|
- beforeDestroy
|
|
|
|
- afterDestroy
|
|
|
|
- beforeValidation
|
|
|
|
- afterValidation
|
|
|
|
|
|
|
|
Each callback is class method of the model, it should accept single argument: `next`, this is callback which
|
|
|
|
should be called after end of the hook. Except `afterInitialize` because this method is syncronous (called after `new Model`).
|
|
|
|
|
|
|
|
## Object lifecycle:
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
var user = new User;
|
|
|
|
// afterInitialize
|
|
|
|
user.save(callback);
|
|
|
|
// beforeValidation
|
|
|
|
// afterValidation
|
|
|
|
// beforeSave
|
|
|
|
// beforeCreate
|
|
|
|
// afterCreate
|
|
|
|
// afterSave
|
|
|
|
// callback
|
|
|
|
user.updateAttribute('email', 'email@example.com', callback);
|
|
|
|
// beforeValidation
|
|
|
|
// afterValidation
|
|
|
|
// beforeUpdate
|
|
|
|
// afterUpdate
|
|
|
|
// callback
|
|
|
|
user.destroy(callback);
|
|
|
|
// beforeDestroy
|
|
|
|
// afterDestroy
|
|
|
|
// callback
|
|
|
|
User.create(data, callback);
|
|
|
|
// beforeValidate
|
|
|
|
// afterValidate
|
|
|
|
// beforeCreate
|
|
|
|
// afterCreate
|
|
|
|
// callback
|
2011-10-05 21:18:38 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
Read the tests for usage examples: ./test/common_test.js
|
2011-10-10 13:39:10 +00:00
|
|
|
Validations: ./test/validations_test.js
|
2011-10-05 21:18:38 +00:00
|
|
|
|
|
|
|
## Your own database adapter
|
|
|
|
|
2011-10-06 20:21:27 +00:00
|
|
|
To use custom adapter, pass it's package name as first argument to `Schema` constructor:
|
2011-10-05 21:18:38 +00:00
|
|
|
|
|
|
|
mySchema = new Schema('couch-db-adapter', {host:.., port:...});
|
|
|
|
|
|
|
|
Make sure, your adapter can be required (just put it into ./node_modules):
|
|
|
|
|
|
|
|
require('couch-db-adapter');
|
2012-12-10 13:34:44 +00:00
|
|
|
|
|
|
|
## Jugglingdb Adapters
|
|
|
|
|
|
|
|
- Firebird: https://github.com/hgourvest/jugglingdb-firebird
|
|
|
|
- Couchdb: TODO: add link for external couchdb adapter
|
2011-10-05 21:18:38 +00:00
|
|
|
|
|
|
|
## Running tests
|
|
|
|
|
2012-10-18 14:13:33 +00:00
|
|
|
To run all tests (requires all databases):
|
2011-10-05 21:18:38 +00:00
|
|
|
|
2012-10-18 14:13:33 +00:00
|
|
|
npm test
|
2011-10-05 21:18:38 +00:00
|
|
|
|
2011-10-06 20:21:27 +00:00
|
|
|
If you run this line, of course it will fall, because it requres different databases to be up and running,
|
|
|
|
but you can use js-memory-engine out of box! Specify ONLY env var:
|
2011-10-05 21:18:38 +00:00
|
|
|
|
|
|
|
ONLY=memory nodeunit test/common_test.js
|
|
|
|
|
|
|
|
of course, if you have redis running, you can run
|
|
|
|
|
|
|
|
ONLY=redis nodeunit test/common_test.js
|
|
|
|
|
|
|
|
## Package structure
|
|
|
|
|
2011-10-10 13:39:10 +00:00
|
|
|
Now all common logic described in `./lib/*.js`, and database-specific stuff in `./lib/adapters/*.js`. It's super-tiny, right?
|
2011-10-05 21:18:38 +00:00
|
|
|
|
|
|
|
## Contributing
|
|
|
|
|
|
|
|
If you have found a bug please write unit test, and make sure all other tests still pass before pushing code to repo.
|
|
|
|
|
|
|
|
## License
|
|
|
|
|
|
|
|
MIT
|