diff --git a/Readme.md b/Readme.md index 3102c9ed..e239933c 100755 --- a/Readme.md +++ b/Readme.md @@ -586,7 +586,10 @@ See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Model-V ## Associations -An association is a relation between one or more tables. +An association is a relation between one or more tables. This version of node-orm2 creates foreign keys with MySQL for hasOne associations. + +### foreign keys with hasOne +`Animal.hasOne('owner', Person, { onDelete: 'cascade' })` (or **set null**, or **restrict**) ### hasOne diff --git a/lib/Drivers/DDL/mysql.js b/lib/Drivers/DDL/mysql.js index 1d521d4d..1f3d16c1 100755 --- a/lib/Drivers/DDL/mysql.js +++ b/lib/Drivers/DDL/mysql.js @@ -70,7 +70,7 @@ exports.sync = function (driver, opts, cb) { "CREATE TABLE IF NOT EXISTS " + driver.query.escapeId(opts.table) + " (" + definitions.join(", ") + ")" ); - + for (i = 0; i < opts.many_associations.length; i++) { definitions = []; @@ -116,6 +116,45 @@ exports.sync = function (driver, opts, cb) { } }; +exports.syncConstraints = function (driver, opts, cb) { + var queries = []; + var k, i, pending; + + for (i = 0; i < opts.one_associations.length; i++) { + if (opts.one_associations[i].extension) continue; + if (opts.one_associations[i].reversed) continue; + for (k in opts.one_associations[i].field) { + queries.push( + "ALTER TABLE " + driver.query.escapeId(opts.table) + " " + + "DROP FOREIGN KEY fk_" + k + "_" + opts.one_associations[i].name + ); + queries.push( + "ALTER TABLE " + driver.query.escapeId(opts.table) + " " + + "ADD CONSTRAINT fk_" + k + "_" + opts.one_associations[i].model.table + " FOREIGN KEY (" + driver.query.escapeId(k) + ") " + + "REFERENCES " + opts.one_associations[i].name + "(`id`) " + + "ON UPDATE " + (opts.one_associations[i].onUpdate ? opts.one_associations[i].onUpdate.toUpperCase() : 'CASCADE') + " " + + "ON DELETE " + (opts.one_associations[i].onDelete ? opts.one_associations[i].onDelete.toUpperCase() : 'RESTRICT') + ); + } + } + + for (i = 0; i < opts.many_associations.length; i++) { + // TODO: build this + } + + pending = queries.length; + + if (!queries.length) return cb(); + + for (i = 0; i < queries.length; i++) { + driver.execQuery(queries[i], function (err) { + if (--pending === 0) { + return cb(err && err.code === 'ER_CANT_CREATE_TABLE' ? null : err); + } + }); + } +}; + var colTypes = { integer: { 2: 'SMALLINT', 4: 'INTEGER', 8: 'BIGINT' }, floating: { 4: 'FLOAT', 8: 'DOUBLE' } diff --git a/lib/Drivers/DML/mysql.js b/lib/Drivers/DML/mysql.js index d73b5d08..971786ae 100755 --- a/lib/Drivers/DML/mysql.js +++ b/lib/Drivers/DML/mysql.js @@ -32,6 +32,10 @@ Driver.prototype.sync = function (opts, cb) { return require("../DDL/mysql").sync(this, opts, cb); }; +Driver.prototype.syncConstraints = function (opts, cb) { + return require("../DDL/mysql").syncConstraints(this, opts, cb); +}; + Driver.prototype.drop = function (opts, cb) { return require("../DDL/mysql").drop(this, opts, cb); }; diff --git a/lib/Instance.js b/lib/Instance.js index 68f624b8..248154e0 100755 --- a/lib/Instance.js +++ b/lib/Instance.js @@ -29,7 +29,7 @@ function Instance(Model, opts) { var pending = [], errors = [], required; Hook.wait(instance, opts.hooks.beforeValidation, function (err) { - var k, i; + var k, i, a; if (err) { return saveError(cb, err); } @@ -62,13 +62,15 @@ function Instance(Model, opts) { required = false; if (Model.properties[k]) { - required = Model.properties[k].required; + required = Model.properties[k].alwaysValidate || Model.properties[k].required; } else { for (i = 0; i < opts.one_associations.length; i++) { - if (opts.one_associations[i].field === k) { - required = opts.one_associations[i].required; - break; - } + for (a in opts.one_associations[i].field) { + if (a === k) { + required = opts.one_associations[i].alwaysValidate || opts.one_associations[i].required; + break; + } + } } } if (!required && instance[k] == null) { diff --git a/lib/Model.js b/lib/Model.js index 188ba3a9..948c1d41 100644 --- a/lib/Model.js +++ b/lib/Model.js @@ -83,7 +83,7 @@ function Model(opts) { autoFetchLimit : inst_opts.autoFetchLimit, cascadeRemove : inst_opts.cascadeRemove }; - var pending = 2, create_err = null; + var pending = 2; var instance = new Instance(model, { uid : inst_opts.uid, // singleton unique id id : opts.id, @@ -104,12 +104,9 @@ function Model(opts) { association_properties : association_properties }); instance.on("ready", function (err) { - if (--pending > 0) { - create_err = err; - return; - } + if (--pending > 0) return; if (typeof cb === "function") { - return cb(err || create_err, instance); + return cb(err, instance); } }); if (model_fields !== null) { @@ -123,12 +120,9 @@ function Model(opts) { ManyAssociation.autoFetch(instance, many_associations, assoc_opts, function () { ExtendAssociation.autoFetch(instance, extend_associations, assoc_opts, function () { Hook.wait(instance, opts.hooks.afterAutoFetch, function (err) { - if (--pending > 0) { - create_err = err; - return; - } + if (--pending > 0) return; if (typeof cb === "function") { - return cb(err || create_err, instance); + return cb(err, instance); } }); }); @@ -236,6 +230,33 @@ function Model(opts) { return cb(ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Driver does not support Model.sync()", { model: opts.table })); }; + model.syncConstraints = function (cb) { + if (arguments.length === 0) { + cb = function () {}; + } + if (typeof opts.driver.syncConstraints === "function") { + try { + opts.driver.syncConstraints({ + extension : opts.extension, + id : opts.id, + table : opts.table, + properties : opts.properties, + allProperties : allProperties, + indexes : opts.indexes || [], + customTypes : opts.db.customTypes, + one_associations : one_associations, + many_associations : many_associations, + extend_associations : extend_associations + }, cb); + } catch (e) { + return cb(e); + } + + return this; + } + + return cb(ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Driver does not support Model.syncConstraint()", { model: opts.table })); + }; model.get = function () { var conditions = {}; diff --git a/lib/ORM.js b/lib/ORM.js index 65df1023..7e102f40 100644 --- a/lib/ORM.js +++ b/lib/ORM.js @@ -283,11 +283,15 @@ ORM.prototype.load = function () { return loadNext(); }; -ORM.prototype.sync = function (cb) { +ORM.prototype.sync = function (constraints, cb) { var modelIds = Object.keys(this.models); var syncNext = function () { if (modelIds.length === 0) { - return cb(); + if (constraints) { + return this.syncConstraints(cb); + } else { + return cb(); + } } var modelId = modelIds.shift(); @@ -303,10 +307,42 @@ ORM.prototype.sync = function (cb) { }); }.bind(this); - if (arguments.length === 0) { - cb = function () {}; - } + if (typeof constraints === 'function') { + cb = constraints; + constraints = false; + } + if (!cb) { + cb = function () {}; + } + + syncNext(); + + return this; +}; +ORM.prototype.syncConstraints = function (cb) { + var modelIds = Object.keys(this.models); + var syncNext = function () { + if (modelIds.length === 0) { + return cb(); + } + + var modelId = modelIds.shift(); + + this.models[modelId].syncConstraints(function (err) { + if (err) { + err.model = modelId; + + return cb(err); + } + + return syncNext(); + }); + }.bind(this); + if (arguments.length === 0) { + cb = function () {}; + } + syncNext(); return this;