From 6d1e47ceafaee4f514f69716339da17ee34d95fe Mon Sep 17 00:00:00 2001 From: Anatoliy Chakkaev Date: Sun, 31 Mar 2013 16:35:26 +0400 Subject: [PATCH 1/2] Schemas switching --- Makefile | 8 +++++--- docs/roadmap.md | 2 ++ lib/model.js | 2 +- lib/schema.js | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index fcf7fda2..c10bb085 100644 --- a/Makefile +++ b/Makefile @@ -56,20 +56,22 @@ about-docs: GITBRANCH = $(shell git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/') REPO = marcusgreenwood/hatchjs +TARGET = origin FROM = $(GITBRANCH) TO = $(GITBRANCH) pull: - git pull origin $(FROM) + git pull $(TARGET) $(FROM) safe-pull: - git pull origin $(FROM) --no-commit + git pull $(TARGET) $(FROM) --no-commit push: test - git push origin $(TO) + git push $(TARGET) $(TO) feature: git checkout -b feature-$(filter-out $@,$(MAKECMDGOALS)) + git push -u $(TARGET) feature-$(filter-out $@,$(MAKECMDGOALS)) %: @: diff --git a/docs/roadmap.md b/docs/roadmap.md index 43617852..2adb94f0 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -19,6 +19,8 @@ jugglingdb-roadmap - The Future of JugglingDB ## MODEL CORE +* schema switching +* common transaction support * virtual attributes * object presentation modes * mass-assignment protection diff --git a/lib/model.js b/lib/model.js index 9c24e12e..493a1979 100644 --- a/lib/model.js +++ b/lib/model.js @@ -694,7 +694,7 @@ AbstractClass.prototype.isNewRecord = function () { * @private */ AbstractClass.prototype._adapter = function () { - return this.constructor.schema.adapter; + return this.schema.adapter; }; /** diff --git a/lib/schema.js b/lib/schema.js index cf15d8cc..5401c250 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -163,11 +163,12 @@ Schema.prototype.define = function defineClass(className, properties, settings) standartize(properties, settings); // every class can receive hash of data as optional param - var NewClass = function ModelConstructor(data) { + var NewClass = function ModelConstructor(data, schema) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data); } AbstractClass.call(this, data); + this.schema = schema || this.constructor.schema; }; hiddenProperty(NewClass, 'schema', schema); From cf5ed5b8775637ae2454e74c2163197388dad4ef Mon Sep 17 00:00:00 2001 From: Anatoliy Chakkaev Date: Mon, 1 Apr 2013 17:49:12 +0400 Subject: [PATCH 2/2] Transactions --- lib/adapters/memory.js | 37 +++++++++++++--- lib/model.js | 31 +++++++++----- lib/schema.js | 96 +++++++++++++++++++++++++++++++++++++++--- test/schema.test.js | 55 ++++++++++++++++++++++++ 4 files changed, 195 insertions(+), 24 deletions(-) create mode 100644 test/schema.test.js diff --git a/lib/adapters/memory.js b/lib/adapters/memory.js index 2efabac2..52c0c3b1 100644 --- a/lib/adapters/memory.js +++ b/lib/adapters/memory.js @@ -1,14 +1,30 @@ exports.initialize = function initializeSchema(schema, callback) { schema.adapter = new Memory(); - process.nextTick(callback); + schema.adapter.connect(callback); }; -function Memory() { - this._models = {}; - this.cache = {}; - this.ids = {}; +function Memory(m) { + if (m) { + this.isTransaction = true; + this.cache = m.cache; + this.ids = m.ids; + this._models = m._models; + } else { + this.isTransaction = false; + this.cache = {}; + this.ids = {}; + this._models = {}; + } } +Memory.prototype.connect = function(callback) { + if (this.isTransaction) { + this.onTransactionExec = callback; + } else { + process.nextTick(callback); + } +}; + Memory.prototype.define = function defineModel(descr) { var m = descr.model.modelName; this._models[m] = descr; @@ -20,7 +36,7 @@ Memory.prototype.create = function create(model, data, callback) { var id = data.id || this.ids[model]++; data.id = id; this.cache[model][id] = JSON.stringify(data); - process.nextTick(function () { + process.nextTick(function() { callback(null, id); }); }; @@ -185,6 +201,15 @@ Memory.prototype.updateAttributes = function updateAttributes(model, id, data, c this.save(model, merge(base, data), cb); }; +Memory.prototype.transaction = function () { + return new Memory(this); +}; + +Memory.prototype.exec = function(callback) { + this.onTransactionExec(); + setTimeout(callback, 50); +}; + function merge(base, update) { if (!base) return update; Object.keys(update).forEach(function (key) { diff --git a/lib/model.js b/lib/model.js index 493a1979..1018e596 100644 --- a/lib/model.js +++ b/lib/model.js @@ -172,23 +172,29 @@ AbstractClass.create = function (data, callback) { callback = function () {}; } + if (!data) { + data = {}; + } + if (data instanceof Array) { var instances = []; - var errors = new Array(data.length); + var errors = Array(data.length); var gotError = false; var wait = data.length; if (wait === 0) callback(null, []); - var instances = data.map(function(d, i) { - return Model.create(d, function(err, inst) { - // console.log('got', i, err, inst, inst.errors); - if (err) { - errors[i] = err; - gotError = true; - } - modelCreated(); - }); - }); + var instances = []; + for (var i = 0; i < data.length; i += 1) { + (function(d, i) { + instances.push(Model.create(d, function(err, inst) { + if (err) { + errors[i] = err; + gotError = true; + } + modelCreated(); + })); + })(data[i], i); + } return instances; @@ -249,6 +255,9 @@ AbstractClass.create = function (data, callback) { function stillConnecting(schema, obj, args) { if (schema.connected) return false; + if (!schema.connecting) { + schema.connect(); + } var method = args.callee; schema.on('connected', function () { method.apply(obj, [].slice.call(args)); diff --git a/lib/schema.js b/lib/schema.js index 5401c250..d2e8143c 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -2,6 +2,7 @@ * Module dependencies */ var AbstractClass = require('./model.js').AbstractClass; +var EventEmitter = require('events').EventEmitter; var util = require('util'); var path = require('path'); var fs = require('fs'); @@ -65,6 +66,7 @@ function Schema(name, settings) { // Disconnected by default this.connected = false; + this.connecting = true; // create blank models pool this.models = {}; @@ -115,9 +117,24 @@ function Schema(name, settings) { this.emit('connected'); }.bind(this)); + + schema.connect = function(cb) { + var schema = this; + schema.connecting = true; + schema.adapter.connect(function(err) { + if (!err) { + schema.connected = true; + schema.connecting = false; + schema.emit('connected'); + } + if (cb) { + cb(err); + } + }); + }; }; -util.inherits(Schema, require('events').EventEmitter); +util.inherits(Schema, EventEmitter); /** * Define class @@ -160,8 +177,6 @@ Schema.prototype.define = function defineClass(className, properties, settings) settings = settings || {}; - standartize(properties, settings); - // every class can receive hash of data as optional param var NewClass = function ModelConstructor(data, schema) { if (!(this instanceof ModelConstructor)) { @@ -173,8 +188,6 @@ Schema.prototype.define = function defineClass(className, properties, settings) hiddenProperty(NewClass, 'schema', schema); hiddenProperty(NewClass, 'modelName', className); - hiddenProperty(NewClass, 'cache', {}); - hiddenProperty(NewClass, 'mru', []); hiddenProperty(NewClass, 'relations', {}); // inherit AbstractClass methods @@ -188,6 +201,8 @@ Schema.prototype.define = function defineClass(className, properties, settings) NewClass.getter = {}; NewClass.setter = {}; + standartize(properties, settings); + // store class in model pool this.models[className] = NewClass; this.definitions[className] = { @@ -195,7 +210,7 @@ Schema.prototype.define = function defineClass(className, properties, settings) settings: settings }; - // pass controll to adapter + // pass control to adapter this.adapter.define({ model: NewClass, properties: properties, @@ -252,7 +267,6 @@ Schema.prototype.define = function defineClass(className, properties, settings) return NewClass; - }; function standartize(properties, settings) { @@ -270,6 +284,7 @@ Schema.prototype.define = function defineClass(className, properties, settings) // or {timestamps: {created: 'created_at', updated: false}} // by default property names: createdAt, updatedAt } + /** * Define single property named `prop` on `model` * @@ -417,6 +432,73 @@ Schema.prototype.disconnect = function disconnect(cb) { } }; +Schema.prototype.copyModel = function copyModel(Master) { + var schema = this; + var className = Master.modelName; + var md = Master.schema.definitions[className]; + var Slave = function SlaveModel() { + Master.apply(this, [].slice.call(arguments)); + this.schema = schema; + }; + + util.inherits(Slave, Master); + + Slave.__proto__ = Master; + + hiddenProperty(Slave, 'schema', schema); + hiddenProperty(Slave, 'modelName', className); + hiddenProperty(Slave, 'relations', Master.relations); + + if (!(className in schema.models)) { + + // store class in model pool + schema.models[className] = Slave; + schema.definitions[className] = { + properties: md.properties, + settings: md.settings + }; + + if (!schema.isTransaction) { + schema.adapter.define({ + model: Slave, + properties: md.properties, + settings: md.settings + }); + } + + } + + return Slave; +}; + +Schema.prototype.transaction = function() { + var schema = this; + var transaction = new EventEmitter; + transaction.isTransaction = true; + transaction.origin = schema; + transaction.name = schema.name; + transaction.settings = schema.settings; + transaction.connected = false; + transaction.connecting = false; + transaction.adapter = schema.adapter.transaction(); + + // create blank models pool + transaction.models = {}; + transaction.definitions = {}; + + for (var i in schema.models) { + schema.copyModel.call(transaction, schema.models[i]); + } + + transaction.connect = schema.connect; + + transaction.exec = function(cb) { + transaction.adapter.exec(cb); + }; + + return transaction; +}; + /** * Define hidden property */ diff --git a/test/schema.test.js b/test/schema.test.js new file mode 100644 index 00000000..12f738f1 --- /dev/null +++ b/test/schema.test.js @@ -0,0 +1,55 @@ +var db = getSchema(), slave = getSchema(), Model, SlaveModel; +var should = require('should'); + +describe.only('schema', function() { + + it('should define Model', function() { + Model = db.define('Model'); + Model.schema.should.eql(db); + var m = new Model; + m.schema.should.eql(db); + }); + + it('should clone existing model', function() { + SlaveModel = slave.copyModel(Model); + SlaveModel.schema.should.eql(slave); + slave.should.not.eql(db); + var sm = new SlaveModel; + sm.should.be.instanceOf(Model); + sm.schema.should.not.eql(db); + sm.schema.should.eql(slave); + }); + + it('should automigrate', function(done) { + db.automigrate(done); + }); + + it('should create transaction', function(done) { + var tr = db.transaction(); + tr.connected.should.be.false; + tr.connecting.should.be.false; + var called = false; + tr.models.Model.create(Array(3), function () { + called = true; + }); + tr.connected.should.be.false; + tr.connecting.should.be.true; + + db.models.Model.count(function(err, c) { + should.not.exist(err); + should.exist(c); + c.should.equal(0); + called.should.be.false; + tr.exec(function () { + setTimeout(function() { + called.should.be.true; + db.models.Model.count(function(err, c) { + c.should.equal(3); + done(); + }); + }, 100); + }); + }); + }); + +});