Merge branch 'master' of github.com:1602/jugglingdb
This commit is contained in:
commit
91cde064a6
|
@ -6,6 +6,7 @@ services:
|
|||
- mongodb
|
||||
- redis-server
|
||||
- neo4j
|
||||
- couchdb
|
||||
before_install:
|
||||
- git submodule init && git submodule --quiet update
|
||||
- ./support/ci/neo4j.sh
|
||||
|
@ -13,3 +14,4 @@ before_script:
|
|||
- "mysql -e 'create database myapp_test;'"
|
||||
- "psql -c 'create database myapp_test;' -U postgres"
|
||||
- mongo mydb_test --eval 'db.addUser("travis", "test");'
|
||||
- curl -X PUT localhost:5984/nano-test
|
|
@ -0,0 +1,4 @@
|
|||
{spawn} = require 'child_process'
|
||||
|
||||
task 'build', 'Build lib/ from src/', ->
|
||||
spawn 'coffee', ['-c', '-o', 'lib', 'src'], stdio: 'inherit'
|
|
@ -178,6 +178,11 @@ Make sure, your adapter can be required (just put it into ./node_modules):
|
|||
|
||||
require('couch-db-adapter');
|
||||
|
||||
## Jugglingdb Adapters
|
||||
|
||||
- Firebird: https://github.com/hgourvest/jugglingdb-firebird
|
||||
- Couchdb: TODO: add link for external couchdb adapter
|
||||
|
||||
## Running tests
|
||||
|
||||
To run all tests (requires all databases):
|
||||
|
|
|
@ -193,12 +193,15 @@ AbstractClass.create = function (data, callback) {
|
|||
var data = this.toObject(true); // Added this to fix the beforeCreate trigger not fire.
|
||||
// The fix is per issue #72 and the fix was found by by5739.
|
||||
|
||||
this._adapter().create(modelName, this.constructor._forDB(data), function (err, id) {
|
||||
this._adapter().create(modelName, this.constructor._forDB(data), function (err, id, rev) {
|
||||
if (id) {
|
||||
obj.__data.id = id;
|
||||
obj.__dataWas.id = id;
|
||||
defineReadonlyProp(obj, 'id', id);
|
||||
}
|
||||
if (rev) {
|
||||
obj._rev = rev
|
||||
}
|
||||
done.call(this, function () {
|
||||
if (callback) {
|
||||
callback(err, obj);
|
||||
|
|
|
@ -0,0 +1,318 @@
|
|||
// Generated by CoffeeScript 1.4.0
|
||||
(function() {
|
||||
var NanoAdapter, helpers, _,
|
||||
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
||||
__slice = [].slice;
|
||||
|
||||
_ = require('lodash')._;
|
||||
|
||||
exports.initialize = function(schema, callback) {
|
||||
var db, design, opts;
|
||||
if (!(opts = schema.settings)) {
|
||||
throw new Error('url is missing');
|
||||
}
|
||||
db = require('nano')(opts);
|
||||
schema.adapter = new NanoAdapter(db);
|
||||
design = {
|
||||
views: {
|
||||
by_model: {
|
||||
map: 'function (doc) { if (doc.model) return emit(doc.model, null); }'
|
||||
}
|
||||
}
|
||||
};
|
||||
return db.insert(design, '_design/nano', function(err, doc) {
|
||||
return callback();
|
||||
});
|
||||
};
|
||||
|
||||
NanoAdapter = (function() {
|
||||
|
||||
function NanoAdapter(db) {
|
||||
this.db = db;
|
||||
this.all = __bind(this.all, this);
|
||||
|
||||
this.fromDB = __bind(this.fromDB, this);
|
||||
|
||||
this.forDB = __bind(this.forDB, this);
|
||||
|
||||
this.destroyAll = __bind(this.destroyAll, this);
|
||||
|
||||
this.count = __bind(this.count, this);
|
||||
|
||||
this.updateAttributes = __bind(this.updateAttributes, this);
|
||||
|
||||
this.destroy = __bind(this.destroy, this);
|
||||
|
||||
this.find = __bind(this.find, this);
|
||||
|
||||
this.exists = __bind(this.exists, this);
|
||||
|
||||
this.updateOrCreate = __bind(this.updateOrCreate, this);
|
||||
|
||||
this.save = __bind(this.save, this);
|
||||
|
||||
this.create = __bind(this.create, this);
|
||||
|
||||
this.define = __bind(this.define, this);
|
||||
|
||||
this._models = {};
|
||||
}
|
||||
|
||||
NanoAdapter.prototype.define = function(descr) {
|
||||
var m;
|
||||
m = descr.model.modelName;
|
||||
descr.properties._rev = {
|
||||
type: String
|
||||
};
|
||||
return this._models[m] = descr;
|
||||
};
|
||||
|
||||
NanoAdapter.prototype.create = function() {
|
||||
var args;
|
||||
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
||||
return this.save.apply(this, args);
|
||||
};
|
||||
|
||||
NanoAdapter.prototype.save = function(model, data, callback) {
|
||||
var _this = this;
|
||||
data.model = model;
|
||||
helpers.savePrep(data);
|
||||
return this.db.insert(this.forDB(model, data), function(err, doc) {
|
||||
return callback(err, doc.id, doc.rev);
|
||||
});
|
||||
};
|
||||
|
||||
NanoAdapter.prototype.updateOrCreate = function(model, data, callback) {
|
||||
var _this = this;
|
||||
return this.exists(model, data.id, function(err, exists) {
|
||||
if (exists) {
|
||||
return _this.save(model, data, callback);
|
||||
} else {
|
||||
return _this.create(model, data, function(err, id) {
|
||||
data.id = id;
|
||||
return callback(err, data);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
NanoAdapter.prototype.exists = function(model, id, callback) {
|
||||
return this.db.head(id, function(err, _, headers) {
|
||||
if (err) {
|
||||
return callback(null, false);
|
||||
}
|
||||
return callback(null, headers != null);
|
||||
});
|
||||
};
|
||||
|
||||
NanoAdapter.prototype.find = function(model, id, callback) {
|
||||
var _this = this;
|
||||
return this.db.get(id, function(err, doc) {
|
||||
return callback(err, _this.fromDB(model, doc));
|
||||
});
|
||||
};
|
||||
|
||||
NanoAdapter.prototype.destroy = function(model, id, callback) {
|
||||
var _this = this;
|
||||
return this.db.get(id, function(err, doc) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
return _this.db.destroy(id, doc._rev, function(err, doc) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback.removed = true;
|
||||
return callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
NanoAdapter.prototype.updateAttributes = function(model, id, data, callback) {
|
||||
var _this = this;
|
||||
return this.db.get(id, function(err, base) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
return _this.save(model, helpers.merge(base, data), callback);
|
||||
});
|
||||
};
|
||||
|
||||
NanoAdapter.prototype.count = function(model, callback, where) {
|
||||
var _this = this;
|
||||
return this.all(model, {
|
||||
where: where
|
||||
}, function(err, docs) {
|
||||
return callback(err, docs.length);
|
||||
});
|
||||
};
|
||||
|
||||
NanoAdapter.prototype.destroyAll = function(model, callback) {
|
||||
var _this = this;
|
||||
return this.all(model, {}, function(err, docs) {
|
||||
var doc;
|
||||
docs = (function() {
|
||||
var _i, _len, _results;
|
||||
_results = [];
|
||||
for (_i = 0, _len = docs.length; _i < _len; _i++) {
|
||||
doc = docs[_i];
|
||||
_results.push({
|
||||
_id: doc.id,
|
||||
_rev: doc._rev,
|
||||
_deleted: true
|
||||
});
|
||||
}
|
||||
return _results;
|
||||
})();
|
||||
return _this.db.bulk({
|
||||
docs: docs
|
||||
}, function(err, body) {
|
||||
return callback(err, body);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
NanoAdapter.prototype.forDB = function(model, data) {
|
||||
var k, props, v;
|
||||
if (data == null) {
|
||||
data = {};
|
||||
}
|
||||
props = this._models[model].properties;
|
||||
for (k in props) {
|
||||
v = props[k];
|
||||
if (data[k] && props[k].type.name === 'Date' && (data[k].getTime != null)) {
|
||||
data[k] = data[k].getTime();
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
NanoAdapter.prototype.fromDB = function(model, data) {
|
||||
var date, k, props, v;
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
props = this._models[model].properties;
|
||||
for (k in props) {
|
||||
v = props[k];
|
||||
if ((data[k] != null) && props[k].type.name === 'Date') {
|
||||
date = new Date(data[k]);
|
||||
date.setTime(data[k]);
|
||||
data[k] = date;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
NanoAdapter.prototype.all = function(model, filter, callback) {
|
||||
var params,
|
||||
_this = this;
|
||||
params = {
|
||||
keys: [model],
|
||||
include_docs: true
|
||||
};
|
||||
return this.db.view('nano', 'by_model', params, function(err, body) {
|
||||
var doc, docs, i, k, key, orders, row, sorting, v, where, _i, _len;
|
||||
docs = (function() {
|
||||
var _i, _len, _ref, _results;
|
||||
_ref = body.rows;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
row = _ref[_i];
|
||||
row.doc.id = row.doc._id;
|
||||
delete row.doc._id;
|
||||
_results.push(row.doc);
|
||||
}
|
||||
return _results;
|
||||
})();
|
||||
if (where = filter != null ? filter.where : void 0) {
|
||||
for (k in where) {
|
||||
v = where[k];
|
||||
if (_.isDate(v)) {
|
||||
where[k] = v.getTime();
|
||||
}
|
||||
}
|
||||
docs = _.where(docs, where);
|
||||
}
|
||||
if (orders = filter != null ? filter.order : void 0) {
|
||||
if (_.isString(orders)) {
|
||||
orders = [orders];
|
||||
}
|
||||
sorting = function(a, b) {
|
||||
var ak, bk, i, item, rev, _i, _len;
|
||||
for (i = _i = 0, _len = this.length; _i < _len; i = ++_i) {
|
||||
item = this[i];
|
||||
ak = a[this[i].key];
|
||||
bk = b[this[i].key];
|
||||
rev = this[i].reverse;
|
||||
if (ak > bk) {
|
||||
return 1 * rev;
|
||||
}
|
||||
if (ak < bk) {
|
||||
return -1 * rev;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
for (i = _i = 0, _len = orders.length; _i < _len; i = ++_i) {
|
||||
key = orders[i];
|
||||
orders[i] = {
|
||||
reverse: helpers.reverse(key),
|
||||
key: helpers.stripOrder(key)
|
||||
};
|
||||
}
|
||||
docs.sort(sorting.bind(orders));
|
||||
}
|
||||
return callback(err, (function() {
|
||||
var _j, _len1, _results;
|
||||
_results = [];
|
||||
for (_j = 0, _len1 = docs.length; _j < _len1; _j++) {
|
||||
doc = docs[_j];
|
||||
_results.push(this.fromDB(model, doc));
|
||||
}
|
||||
return _results;
|
||||
}).call(_this));
|
||||
});
|
||||
};
|
||||
|
||||
return NanoAdapter;
|
||||
|
||||
})();
|
||||
|
||||
helpers = {
|
||||
merge: function(base, update) {
|
||||
var k, v;
|
||||
if (!base) {
|
||||
return update;
|
||||
}
|
||||
for (k in update) {
|
||||
v = update[k];
|
||||
base[k] = update[k];
|
||||
}
|
||||
return base;
|
||||
},
|
||||
reverse: function(key) {
|
||||
var hasOrder;
|
||||
if (hasOrder = key.match(/\s+(A|DE)SC$/i)) {
|
||||
if (hasOrder[1] === "DE") {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
stripOrder: function(key) {
|
||||
return key.replace(/\s+(A|DE)SC/i, "");
|
||||
},
|
||||
savePrep: function(data) {
|
||||
var id;
|
||||
if (id = data.id) {
|
||||
delete data.id;
|
||||
data._id = id.toString();
|
||||
}
|
||||
if (data._rev === null) {
|
||||
return delete data._rev;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
|
@ -42,6 +42,9 @@ exports.initialize = function initializeSchema(schema, callback) {
|
|||
}
|
||||
}
|
||||
});
|
||||
schema.client.on('error', function (error) {
|
||||
console.log(error);
|
||||
})
|
||||
|
||||
var clientWrapper = new Client(schema.client);
|
||||
|
||||
|
|
12
package.json
12
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "jugglingdb",
|
||||
"description": "ORM for every database: redis, mysql, neo4j, mongodb, postgres, sqlite",
|
||||
"description": "ORM for every database: redis, mysql, neo4j, mongodb, couchdb, postgres, sqlite",
|
||||
"version": "0.1.27-3",
|
||||
"author": "Anatoliy Chakkaev <rpm1602@gmail.com>",
|
||||
"contributors": [
|
||||
|
@ -39,6 +39,10 @@
|
|||
{
|
||||
"name": "Rick O'Toole",
|
||||
"email": "patrick.n.otoole@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Nicholas Westlake",
|
||||
"email": "nicholasredlin@gmail.com"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
|
@ -53,7 +57,8 @@
|
|||
"node >= 0.4.12"
|
||||
],
|
||||
"dependencies": {
|
||||
"node-uuid": ">= 1.3.3"
|
||||
"node-uuid": ">= 1.3.3",
|
||||
"lodash": "1.x.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"semicov": "*",
|
||||
|
@ -69,6 +74,7 @@
|
|||
"neo4j": ">= 0.2.5",
|
||||
"mongodb": ">= 0.9.9",
|
||||
"felix-couchdb": ">= 1.0.3",
|
||||
"cradle": ">= 0.6.3"
|
||||
"cradle": ">= 0.6.3",
|
||||
"nano": "3.3.x"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
{_} = require 'lodash'
|
||||
|
||||
# api
|
||||
exports.initialize = (schema, callback) ->
|
||||
throw new Error 'url is missing' unless opts = schema.settings
|
||||
db = require('nano')(opts)
|
||||
|
||||
schema.adapter = new NanoAdapter db
|
||||
design = views: by_model: map:
|
||||
'function (doc) { if (doc.model) return emit(doc.model, null); }'
|
||||
db.insert design, '_design/nano', (err, doc) -> callback()
|
||||
|
||||
class NanoAdapter
|
||||
constructor: (@db) ->
|
||||
@_models = {}
|
||||
|
||||
define: (descr) =>
|
||||
m = descr.model.modelName
|
||||
descr.properties._rev = type: String
|
||||
@_models[m] = descr
|
||||
|
||||
create: (args...) => @save args...
|
||||
|
||||
save: (model, data, callback) =>
|
||||
data.model = model
|
||||
helpers.savePrep data
|
||||
|
||||
@db.insert @forDB(model, data), (err, doc) =>
|
||||
callback err, doc.id, doc.rev
|
||||
|
||||
updateOrCreate: (model, data, callback) =>
|
||||
@exists model, data.id, (err, exists) =>
|
||||
if exists
|
||||
@save model, data, callback
|
||||
else
|
||||
@create model, data, (err, id) ->
|
||||
data.id = id
|
||||
callback err, data
|
||||
|
||||
exists: (model, id, callback) =>
|
||||
@db.head id, (err, _, headers) ->
|
||||
return callback null, no if err
|
||||
callback null, headers?
|
||||
|
||||
find: (model, id, callback) =>
|
||||
@db.get id, (err, doc) =>
|
||||
callback err, @fromDB(model, doc)
|
||||
|
||||
destroy: (model, id, callback) =>
|
||||
@db.get id, (err, doc) =>
|
||||
return callback err if err
|
||||
@db.destroy id, doc._rev, (err, doc) =>
|
||||
return callback err if err
|
||||
callback.removed = yes
|
||||
callback()
|
||||
|
||||
updateAttributes: (model, id, data, callback) =>
|
||||
@db.get id, (err, base) =>
|
||||
return callback err if err
|
||||
@save model, helpers.merge(base, data), callback
|
||||
|
||||
count: (model, callback, where) =>
|
||||
@all model, {where}, (err, docs) =>
|
||||
callback err, docs.length
|
||||
|
||||
destroyAll: (model, callback) =>
|
||||
@all model, {}, (err, docs) =>
|
||||
docs = for doc in docs
|
||||
{_id: doc.id, _rev: doc._rev, _deleted: yes}
|
||||
@db.bulk {docs}, (err, body) =>
|
||||
callback err, body
|
||||
|
||||
forDB: (model, data = {}) =>
|
||||
props = @_models[model].properties
|
||||
for k, v of props
|
||||
if data[k] and props[k].type.name is 'Date' and data[k].getTime?
|
||||
data[k] = data[k].getTime()
|
||||
data
|
||||
|
||||
fromDB: (model, data) =>
|
||||
return data unless data
|
||||
props = @_models[model].properties
|
||||
for k, v of props
|
||||
if data[k]? and props[k].type.name is 'Date'
|
||||
date = new Date data[k]
|
||||
date.setTime data[k]
|
||||
data[k] = date
|
||||
data
|
||||
|
||||
all: (model, filter, callback) =>
|
||||
params =
|
||||
keys: [model]
|
||||
include_docs: yes
|
||||
|
||||
@db.view 'nano', 'by_model', params, (err, body) =>
|
||||
docs = for row in body.rows
|
||||
row.doc.id = row.doc._id
|
||||
delete row.doc._id
|
||||
row.doc
|
||||
|
||||
if where = filter?.where
|
||||
for k, v of where
|
||||
where[k] = v.getTime() if _.isDate v
|
||||
docs = _.where docs, where
|
||||
|
||||
if orders = filter?.order
|
||||
orders = [orders] if _.isString orders
|
||||
|
||||
sorting = (a, b) ->
|
||||
for item, i in @
|
||||
ak = a[@[i].key]; bk = b[@[i].key]; rev = @[i].reverse
|
||||
if ak > bk then return 1 * rev
|
||||
if ak < bk then return -1 * rev
|
||||
0
|
||||
|
||||
for key, i in orders
|
||||
orders[i] =
|
||||
reverse: helpers.reverse key
|
||||
key: helpers.stripOrder key
|
||||
|
||||
docs.sort sorting.bind orders
|
||||
callback err, (@fromDB model, doc for doc in docs)
|
||||
|
||||
# helpers
|
||||
helpers =
|
||||
merge: (base, update) ->
|
||||
return update unless base
|
||||
base[k] = update[k] for k, v of update
|
||||
base
|
||||
reverse: (key) ->
|
||||
if hasOrder = key.match(/\s+(A|DE)SC$/i)
|
||||
return -1 if hasOrder[1] is "DE"
|
||||
1
|
||||
stripOrder: (key) ->
|
||||
key.replace(/\s+(A|DE)SC/i, "")
|
||||
savePrep: (data) ->
|
||||
if id = data.id
|
||||
delete data.id
|
||||
data._id = id.toString()
|
||||
if data._rev is null
|
||||
delete data._rev
|
|
@ -21,7 +21,8 @@ var schemas = {
|
|||
mongodb: { url: 'mongodb://travis:test@localhost:27017/myapp' },
|
||||
redis2: {},
|
||||
memory: {},
|
||||
cradle: {}
|
||||
cradle: {},
|
||||
nano: { url: 'http://localhost:5984/nano-test' }
|
||||
};
|
||||
|
||||
var specificTest = getSpecificTests();
|
||||
|
@ -165,9 +166,12 @@ function testOrm(schema) {
|
|||
test.done();
|
||||
});
|
||||
|
||||
it('should be expoted to JSON', function (test) {
|
||||
test.equal(JSON.stringify(new Post({id: 1, title: 'hello, json', date: 1})),
|
||||
'{"title":"hello, json","subject":null,"content":null,"date":1,"published":false,"likes":[],"related":[],"id":1,"userId":null}');
|
||||
it('should be exported to JSON', function (test) {
|
||||
var outString = '{"title":"hello, json","subject":null,"content":null,"date":1,"published":false,"likes":[],"related":[],"id":1,"userId":null}'
|
||||
if (schema.name === 'nano')
|
||||
outString = '{"title":"hello, json","subject":null,"content":null,"date":1,"published":false,"likes":[],"related":[],"_rev":null,"id":1,"userId":null}'
|
||||
|
||||
test.equal(JSON.stringify(new Post({id: 1, title: 'hello, json', date: 1})),outString);
|
||||
test.done();
|
||||
});
|
||||
|
||||
|
@ -455,6 +459,7 @@ function testOrm(schema) {
|
|||
schema.name !== 'memory' &&
|
||||
schema.name !== 'neo4j' &&
|
||||
schema.name !== 'cradle' &&
|
||||
schema.name !== 'nano' &&
|
||||
schema.name !== 'mongodb'
|
||||
)
|
||||
it('hasMany should support additional conditions', function (test) {
|
||||
|
@ -483,7 +488,8 @@ function testOrm(schema) {
|
|||
!schema.name.match(/redis/) &&
|
||||
schema.name !== 'memory' &&
|
||||
schema.name !== 'neo4j' &&
|
||||
schema.name !== 'cradle'
|
||||
schema.name !== 'cradle' &&
|
||||
schema.name !== 'nano'
|
||||
)
|
||||
it('hasMany should be cached', function (test) {
|
||||
// Finding one post with an existing author associated
|
||||
|
@ -758,7 +764,8 @@ function testOrm(schema) {
|
|||
!schema.name.match(/redis/) &&
|
||||
schema.name !== 'memory' &&
|
||||
schema.name !== 'neo4j' &&
|
||||
schema.name !== 'cradle'
|
||||
schema.name !== 'cradle' &&
|
||||
schema.name !== 'nano'
|
||||
)
|
||||
it('should allow advanced queying: lt, gt, lte, gte, between', function (test) {
|
||||
Post.destroyAll(function () {
|
||||
|
@ -991,7 +998,8 @@ function testOrm(schema) {
|
|||
!schema.name.match(/redis/) &&
|
||||
schema.name !== 'memory' &&
|
||||
schema.name !== 'neo4j' &&
|
||||
schema.name !== 'cradle'
|
||||
schema.name !== 'cradle' &&
|
||||
schema.name !== 'nano'
|
||||
)
|
||||
it('belongsTo should be cached', function (test) {
|
||||
User.findOne(function(err, user) {
|
||||
|
|
|
@ -11,6 +11,8 @@ schemas =
|
|||
redis: {}
|
||||
memory: {}
|
||||
cradle: {}
|
||||
nano:
|
||||
url: 'http://localhost:5984/nano-test'
|
||||
|
||||
testOrm = (schema) ->
|
||||
|
||||
|
|
Loading…
Reference in New Issue