From 13e046ed09b7d64ab54ba31e1d7a42ab9ddb0e2c Mon Sep 17 00:00:00 2001 From: Anatoliy Chakkaev Date: Mon, 25 Mar 2013 01:22:23 +0400 Subject: [PATCH 1/7] Makefile for mocha testing --- Makefile | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 856806b9..db3bd36f 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,17 @@ -# doc: -# makedoc lib/abstract-class.js lib/schema.js lib/validatable.js -t "JugglingDB API docs" +## TESTS + +TESTER = ./node_modules/.bin/mocha +OPTS = --require ./test/init.js +TESTS = test/*.test.js test: - @./node_modules/.bin/mocha --require should test/*.test.js - + $(TESTER) $(OPTS) $(TESTS) test-verbose: - @./node_modules/.bin/mocha --require should --reporter spec test/*.test.js + $(TESTER) $(OPTS) --reporter spec $(TESTS) +testing: + $(TESTER) $(OPTS) --watch $(TESTS) + +## DOCS MAN_DOCS = $(shell find docs -name '*.md' \ |sed 's|.md|.3|g' \ From cee7029445c4578cec1241ca12d5063231d869da Mon Sep 17 00:00:00 2001 From: Anatoliy Chakkaev Date: Mon, 25 Mar 2013 01:22:50 +0400 Subject: [PATCH 2/7] Started docs for model, changelog --- docs/changelog.md | 2 +- docs/model.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index e7596c85..b8d4c29a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -5,7 +5,7 @@ jugglingdb-changelog(3) - The History of JugglingDB ### upcoming release -* Documentation in web and man +* Documentation in [web][http://jugglingdb.co] and man ### 0.2.1 diff --git a/docs/model.md b/docs/model.md index b0d12c87..4d462a03 100644 --- a/docs/model.md +++ b/docs/model.md @@ -7,6 +7,39 @@ This section describes common methods of models managed by jugglingdb and explains some model internals, such as data representation, setters, getters and virtual attributes. +## ESSENTIALS + +### Default values + +## DB WRITE METHODS + +### Model.create([data[, callback]]) + +Create instance of Model with given data and save to database, invoke callback +when ready. Callback accepts two arguments: error and model instance. + +### Model.updateAttributes(data[, callback]); +### Model.updateAttributes(data[, callback]); + +## DB READ METHODS + +### Model.all([params[, callback]]) + +Find all instances of Model, matched by query. Fields used for filter and sort +should be declared with `{index: true}` in model definition. + +* `param`: + * where: Object `{ key: val, key2: {gt: 'val2'}}` + * include: String, Object or Array. See AbstractClass.include documentation. + * order: String + * limit: Number + * skip: Number + +* `callback`: + Accepts two arguments: + * err (null or Error) + * Array of instances + ## SEE ALSO jugglingdb-schema(3) From 5ea5da0153fb94fe6069670b71137a685b2ca4bf Mon Sep 17 00:00:00 2001 From: Anatoliy Chakkaev Date: Mon, 25 Mar 2013 01:25:10 +0400 Subject: [PATCH 3/7] Rewrite hooks API This commit breaks compatibility, but adds consistent API and allow modify data of update --- lib/abstract-class.js | 105 ++++++++++++---------- test/hooks.test.js | 198 ++++++++++++++++++++++++++++++++++++++++++ test/init.js | 11 +++ 3 files changed, 268 insertions(+), 46 deletions(-) create mode 100644 test/hooks.test.js create mode 100644 test/init.js diff --git a/lib/abstract-class.js b/lib/abstract-class.js index d885ed83..138f0ae1 100644 --- a/lib/abstract-class.js +++ b/lib/abstract-class.js @@ -74,7 +74,7 @@ AbstractClass.prototype._initProperties = function (data, applySetters) { ctor.forEachProperty(function (attr) { - if (!self.__data.hasOwnProperty(attr)) { + if ('undefined' === typeof self.__data[attr]) { self.__data[attr] = self.__dataWas[attr] = getDefault(attr); } else { self.__dataWas[attr] = self.__data[attr]; @@ -110,11 +110,11 @@ AbstractClass.prototype._initProperties = function (data, applySetters) { return def; } } else { - return null; + return undefined; } } - this.trigger("initialize"); + this.trigger('initialize'); } /** @@ -181,17 +181,21 @@ AbstractClass.create = function (data, callback) { obj = new this(data); data = obj.toObject(true); - // validation required - obj.isValid(function (valid) { - if (!valid) { - callback(new Error('Validation error'), obj); - } else { - create(); - } - }); + obj.trigger('save', function(saveDone) { + + // validation required + obj.isValid(function (valid) { + if (!valid) { + callback(new Error('Validation error'), obj); + } else { + create(saveDone); + } + }); + + }, obj); } - function create() { + function create(saveDone) { obj.trigger('create', function (done) { var data = this.toObject(true); // Added this to fix the beforeCreate trigger not fire. @@ -207,12 +211,18 @@ AbstractClass.create = function (data, callback) { obj._rev = rev } done.call(this, function () { - if (callback) { + if (saveDone) { + saveDone.call(obj, function () { + if (callback) { + callback(err, obj); + } + }); + } else if (callback) { callback(err, obj); } }); }.bind(this)); - }); + }, obj); } }; @@ -646,7 +656,7 @@ AbstractClass.prototype.save = function (options, callback) { }); } - }); + }, this); } }; @@ -753,47 +763,50 @@ AbstractClass.prototype.updateAttributes = function updateAttributes(data, cb) { var inst = this; var model = this.constructor.modelName; - if(!data) data = {}; + if (typeof data === 'function') { + cb = data; + data = null; + } + + if (!data) { + data = {}; + } // update instance's properties Object.keys(data).forEach(function (key) { inst[key] = data[key]; }); - inst.isValid(function (valid) { - if (!valid) { - if (cb) { - cb(new Error('Validation error'), inst); - } - } else { - update(); - } - }); + inst.trigger('save', function (saveDone) { + inst.trigger('update', function (done) { - function update() { - inst.trigger('save', function (saveDone) { - inst.trigger('update', function (done) { - - Object.keys(data).forEach(function (key) { - data[key] = inst[key]; - }); - - inst._adapter().updateAttributes(model, inst.id, inst.constructor._forDB(data), function (err) { - if (!err) { - // update _was attrs - Object.keys(data).forEach(function (key) { - inst.__dataWas[key] = inst.__data[key]; - }); + inst.isValid(function (valid) { + if (!valid) { + if (cb) { + cb(new Error('Validation error'), inst); } - done.call(inst, function () { - saveDone.call(inst, function () { - cb(err, inst); + } else { + Object.keys(data).forEach(function (key) { + inst[key] = data[key]; + }); + + inst._adapter().updateAttributes(model, inst.id, inst.constructor._forDB(data), function (err) { + if (!err) { + // update _was attrs + Object.keys(data).forEach(function (key) { + inst.__dataWas[key] = inst.__data[key]; + }); + } + done.call(inst, function () { + saveDone.call(inst, function () { + cb(err, inst); + }); }); }); - }); - }, data); - }); - } + } + }); + }, data); + }, data); }; AbstractClass.prototype.fromObject = function (obj) { diff --git a/test/hooks.test.js b/test/hooks.test.js new file mode 100644 index 00000000..9e334e28 --- /dev/null +++ b/test/hooks.test.js @@ -0,0 +1,198 @@ +var j = require('../'), + should = require('should'), + Schema = j.Schema, + AbstractClass = j.AbstractClass, + Hookable = j.Hookable, + + db, User; + +describe('hooks', function() { + + before(function() { + db = new Schema('memory'); + + User = db.define('User', { + email: String, + name: String, + password: String, + state: String + }); + }); + + describe('initialize', function() { + + afterEach(function() { + User.afterInitialize = null; + }); + + it('should be triggered on new', function(done) { + User.afterInitialize = function() { + done(); + }; + new User; + }); + + it('should be triggered on create', function(done) { + var user; + User.afterInitialize = function() { + if (this.name === 'Nickolay') { + this.name += ' Rozental'; + } + }; + User.create({name: 'Nickolay'}, function(err, u) { + u.id.should.be.a('number'); + u.name.should.equal('Nickolay Rozental'); + done(); + }); + }); + + }); + + describe('create', function() { + + afterEach(removeHooks('Create')); + + it('should be triggered on create', function(done) { + addHooks('Create', done); + User.create(); + }); + + it('should not be triggered on new', function() { + User.beforeCreate = function(next) { + should.fail('This should not be called'); + next(); + }; + var u = new User; + }); + + it('should be triggered on new+save', function(done) { + addHooks('Create', done); + (new User).save(); + }); + + }); + + describe('save', function() { + afterEach(removeHooks('Save')); + + it('should be triggered on create', function(done) { + addHooks('Save', done); + User.create(); + }); + + it('should be triggered on new+save', function(done) { + addHooks('Save', done); + (new User).save(); + }); + + it('should be triggered on updateAttributes', function(done) { + User.create(function(err, user) { + addHooks('Save', done); + user.updateAttributes({name: 'Anatoliy'}); + }); + }); + + it('should be triggered on save', function(done) { + User.create(function(err, user) { + addHooks('Save', done); + user.name = 'Hamburger'; + user.save(); + }); + }); + + it('should save full object', function(done) { + User.create(function(err, user) { + User.beforeSave = function(next, data) { + data.toObject().should.have.keys('id', 'name', 'email', + 'password', 'state') + done(); + }; + user.save(); + }); + }); + }); + + describe('update', function() { + afterEach(removeHooks('Update')); + + it('should not be triggered on create', function() { + User.beforeUpdate = function(next) { + should.fail('This should not be called'); + next(); + }; + User.create(); + }); + + it('should not be triggered on new+save', function() { + User.beforeUpdate = function(next) { + should.fail('This should not be called'); + next(); + }; + (new User).save(); + }); + + it('should be triggered on updateAttributes', function(done) { + User.create(function (err, user) { + addHooks('Update', done); + user.updateAttributes({name: 'Anatoliy'}); + }); + }); + + it('should be triggered on save', function(done) { + User.create(function (err, user) { + addHooks('Update', done); + user.name = 'Hamburger'; + user.save(); + }); + }); + + it('should update limited set of fields', function(done) { + User.create(function (err, user) { + User.beforeUpdate = function(next, data) { + data.should.have.keys('name', 'email'); + done(); + }; + user.updateAttributes({name: 1, email: 2}); + }); + }); + }); + + describe('destroy', function() { + afterEach(removeHooks('Destroy')); + + it('should be triggered on destroy', function() { + var hook = 'not called'; + User.beforeDestroy = function() { + hook = 'called'; + }; + User.afterDestroy = function() { + hook.should.eql('called'); + done(); + }; + User.create(function (err, user) { + user.destroy(); + }); + }); + }); +}); + +function addHooks(name, done) { + var called = false, random = Math.floor(Math.random() * 1000); + User['before' + name] = function(next, data) { + called = true; + data.email = random; + next(); + }; + User['after' + name] = function(next) { + (new Boolean(called)).should.equal(true); + this.email.should.equal(random); + done(); + }; +} + +function removeHooks(name) { + return function() { + User['after' + name] = null; + User['before' + name] = null; + }; +} diff --git a/test/init.js b/test/init.js new file mode 100644 index 00000000..6d13dbec --- /dev/null +++ b/test/init.js @@ -0,0 +1,11 @@ +require('should'); + +if (!process.env.TRAVIS) { + if (typeof __cov === 'undefined') { + process.on('exit', function () { + require('semicov').report(); + }); + } + + require('semicov').init('lib'); +} From 2fa07e8e1d29a4d6476eca5f0cd6aec989612901 Mon Sep 17 00:00:00 2001 From: Anatoliy Chakkaev Date: Mon, 25 Mar 2013 01:27:36 +0400 Subject: [PATCH 4/7] Rename essentials --- lib/abstract-class.js | 8 ++++---- lib/{hookable.js => hooks.js} | 0 lib/{validatable.js => validations.js} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename lib/{hookable.js => hooks.js} (100%) rename lib/{validatable.js => validations.js} (100%) diff --git a/lib/abstract-class.js b/lib/abstract-class.js index 138f0ae1..cacd6673 100644 --- a/lib/abstract-class.js +++ b/lib/abstract-class.js @@ -2,10 +2,10 @@ * Module dependencies */ var util = require('util'); -var jutil = require('./jutil'); -var Validatable = require('./validatable').Validatable; -var List = require('./list'); -var Hookable = require('./hookable').Hookable; +var jutil = require('./jutil.js'); +var Validatable = require('./validations.js').Validatable; +var List = require('./list.js'); +var Hookable = require('./hooks.js').Hookable; var DEFAULT_CACHE_LIMIT = 1000; var BASE_TYPES = ['String', 'Boolean', 'Number', 'Date', 'Text']; diff --git a/lib/hookable.js b/lib/hooks.js similarity index 100% rename from lib/hookable.js rename to lib/hooks.js diff --git a/lib/validatable.js b/lib/validations.js similarity index 100% rename from lib/validatable.js rename to lib/validations.js From b59fb01b4ac466cdef8bca93576194726567a838 Mon Sep 17 00:00:00 2001 From: Anatoliy Chakkaev Date: Mon, 25 Mar 2013 01:28:08 +0400 Subject: [PATCH 5/7] Upd memory adapter to work with undefined in dataset --- lib/adapters/memory.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/adapters/memory.js b/lib/adapters/memory.js index 314b1555..fbd748cc 100644 --- a/lib/adapters/memory.js +++ b/lib/adapters/memory.js @@ -130,6 +130,8 @@ function applyFilter(filter) { if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { return value.match(example); } + if (typeof example === 'undefined') return undefined; + if (typeof value === 'undefined') return undefined; // not strict equality return (example !== null ? example.toString() : example) == (value !== null ? value.toString() : value); } From d4a526f5b32329d265ae66224eb91002ac50baed Mon Sep 17 00:00:00 2001 From: Anatoliy Chakkaev Date: Mon, 25 Mar 2013 01:28:45 +0400 Subject: [PATCH 6/7] Remove old hooks tests --- test/hookable_test.coffee | 95 --------------------------------------- 1 file changed, 95 deletions(-) delete mode 100644 test/hookable_test.coffee diff --git a/test/hookable_test.coffee b/test/hookable_test.coffee deleted file mode 100644 index f54204aa..00000000 --- a/test/hookable_test.coffee +++ /dev/null @@ -1,95 +0,0 @@ -juggling = require('../index') -Schema = juggling.Schema -AbstractClass = juggling.AbstractClass -Hookable = juggling.Hookable - -require('./spec_helper').init module.exports - -schema = new Schema 'memory' -User = schema.define 'User', - email: String - name: String - password: String - state: String - age: Number - gender: String - domain: String - pendingPeriod: Number - createdByAdmin: Boolean - -it "should trigger after initialize", (test) -> - User.afterInitialize = -> - User.afterInitialize = null - test.done() - user = new User - - -it "should trigger before create", (test) -> - User.beforeCreate = () -> - User.beforeCreate = null - test.done() - User.create -> test.ok "saved" - -it "should trigger after create", (test) -> - User.afterCreate = (next) -> - User.afterCreate = null - next() - - User.create -> - test.ok "saved" - test.done() - -it 'should trigger before save', (test) -> - test.expect(3) - User.beforeSave = (next) -> - User.beforeSave = null - @name = 'mr. ' + @name - next() - user = new User name: 'Jonathan' - - user.save -> - test.equals User.schema.adapter.cache.User[user.id].name, user.name - test.equals user.name, 'mr. Jonathan' - test.ok 'saved' - test.done() - -it 'should trigger after save', (test) -> - User.afterSave = (next) -> - User.afterSave = null - next() - - user = new User - user.save -> - test.ok "saved" - test.done() - -it "should trigger before update", (test) -> - User.beforeUpdate = () -> - User.beforeUpdate = null - test.done() - User.create {}, (err, user) -> - user.updateAttributes email:"1@1.com", -> test.ok "updated" - -it "should trigger after update", (test) -> - User.afterUpdate = () -> - User.afterUpdate = null - test.done() - User.create (err, user) -> - user.updateAttributes email: "1@1.com", -> test.ok "updated" - -it "should trigger before destroy", (test)-> - User.beforeDestroy = () -> - User.beforeDestroy = null - test.done() - User.create {}, (err, user) -> - user.destroy() - -it "should trigger after destroy", (test) -> - User.afterDestroy = () -> - User.afterDestroy = null - test.done() - User.create (err, user) -> - user.destroy() - -it 'allows me to modify attributes before saving', (test) -> - test.done() From 50850d80bef28ac90e10c53e8b0d10aab81926ef Mon Sep 17 00:00:00 2001 From: Anatoliy Chakkaev Date: Mon, 25 Mar 2013 01:29:07 +0400 Subject: [PATCH 7/7] Minor test amends --- test/common_test.js | 2 +- test/defaults.test.js | 1 - test/json.test.js | 2 +- test/spec_helper.js | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test/common_test.js b/test/common_test.js index 70250f06..dbb76965 100644 --- a/test/common_test.js +++ b/test/common_test.js @@ -232,7 +232,7 @@ function testOrm(schema) { }); 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}' + var outString = '{"title":"hello, json","date":1,"published":false,"likes":[],"related":[],"id":1}' if (schema.name === 'nano') outString = '{"title":"hello, json","subject":null,"content":null,"date":1,"published":false,"likes":[],"related":[],"_rev":null,"id":1,"userId":null}' diff --git a/test/defaults.test.js b/test/defaults.test.js index b96f5fb8..51775dee 100644 --- a/test/defaults.test.js +++ b/test/defaults.test.js @@ -19,7 +19,6 @@ describe('defaults', function() { it('should apply defaults on create', function(done) { Server.create(function(err, s) { s.port.should.equal(80); - console.log(s.__data); done(); }); }); diff --git a/test/json.test.js b/test/json.test.js index 775359e2..a720a23d 100644 --- a/test/json.test.js +++ b/test/json.test.js @@ -4,7 +4,7 @@ var should = require('should'); describe('JSON property', function() { var schema, Model; - it('could be defined', function() { + it('should be defined', function() { schema = new Schema('memory'); Model = schema.define('Model', {propertyName: Schema.JSON}); var m = new Model; diff --git a/test/spec_helper.js b/test/spec_helper.js index cff21113..d679e333 100644 --- a/test/spec_helper.js +++ b/test/spec_helper.js @@ -1,6 +1,6 @@ if (!process.env.TRAVIS) { var semicov = require('semicov'); - semicov.init('lib'); + semicov.init('lib', 'JugglingDB'); process.on('exit', semicov.report); }