Add model setting "persistUndefinedAsNull"
When the setting "persistUndefinedAsNull" is true, the model will use `null` instead of `undefined` in all property values. - Known optional model properties are set to `null` when no value was provided. - When setting model properties, `undefined` is always converted to `null`. This applies to both known (model-defined) properties and additional (custom, dynamic) properties. - The instance method `toObject()` converts `undefined` to `null` too. Because `toJSON()` calls `toObject()` under the hood, the change applies to `toJSON()` too.
This commit is contained in:
parent
abba2a88d9
commit
9fd4c00225
|
@ -1878,7 +1878,7 @@ DataAccessObject.prototype.setAttributes = function setAttributes(data) {
|
||||||
};
|
};
|
||||||
|
|
||||||
DataAccessObject.prototype.unsetAttribute = function unsetAttribute(name, nullify) {
|
DataAccessObject.prototype.unsetAttribute = function unsetAttribute(name, nullify) {
|
||||||
if (nullify) {
|
if (nullify || this.constructor.definition.settings.persistUndefinedAsNull) {
|
||||||
this[name] = this.__data[name] = null;
|
this[name] = this.__data[name] = null;
|
||||||
} else {
|
} else {
|
||||||
delete this[name];
|
delete this[name];
|
||||||
|
|
|
@ -429,6 +429,12 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
||||||
} else if (typeof DataType === 'string') {
|
} else if (typeof DataType === 'string') {
|
||||||
DataType = modelBuilder.resolveType(DataType);
|
DataType = modelBuilder.resolveType(DataType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull;
|
||||||
|
if (value === undefined && persistUndefinedAsNull) {
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (ModelClass.setter[propertyName]) {
|
if (ModelClass.setter[propertyName]) {
|
||||||
ModelClass.setter[propertyName].call(this, value); // Try setter first
|
ModelClass.setter[propertyName].call(this, value); // Try setter first
|
||||||
} else {
|
} else {
|
||||||
|
|
24
lib/model.js
24
lib/model.js
|
@ -77,6 +77,8 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
|
||||||
strict = ctor.definition.settings.strict;
|
strict = ctor.definition.settings.strict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var persistUndefinedAsNull = ctor.definition.settings.persistUndefinedAsNull;
|
||||||
|
|
||||||
if (ctor.hideInternalProperties) {
|
if (ctor.hideInternalProperties) {
|
||||||
// Object.defineProperty() is expensive. We only try to make the internal
|
// Object.defineProperty() is expensive. We only try to make the internal
|
||||||
// properties hidden (non-enumerable) if the model class has the
|
// properties hidden (non-enumerable) if the model class has the
|
||||||
|
@ -151,6 +153,11 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
|
||||||
if (typeof propVal === 'function') {
|
if (typeof propVal === 'function') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (propVal === undefined && persistUndefinedAsNull) {
|
||||||
|
propVal = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (properties[p]) {
|
if (properties[p]) {
|
||||||
// Managed property
|
// Managed property
|
||||||
if (applySetters || properties[p].id) {
|
if (applySetters || properties[p].id) {
|
||||||
|
@ -257,6 +264,10 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
|
||||||
self.__data[p] = propVal;
|
self.__data[p] = propVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (propVal === undefined && persistUndefinedAsNull) {
|
||||||
|
self.__data[p] = propVal = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle complex types (JSON/Object)
|
// Handle complex types (JSON/Object)
|
||||||
if (!BASE_TYPES[type.name]) {
|
if (!BASE_TYPES[type.name]) {
|
||||||
|
|
||||||
|
@ -345,6 +356,7 @@ ModelBaseClass.prototype.toObject = function (onlySchema, removeHidden, removePr
|
||||||
|
|
||||||
var strict = this.__strict;
|
var strict = this.__strict;
|
||||||
var schemaLess = (strict === false) || !onlySchema;
|
var schemaLess = (strict === false) || !onlySchema;
|
||||||
|
var persistUndefinedAsNull = Model.definition.settings.persistUndefinedAsNull;
|
||||||
|
|
||||||
var props = Model.definition.properties;
|
var props = Model.definition.properties;
|
||||||
var keys = Object.keys(props);
|
var keys = Object.keys(props);
|
||||||
|
@ -373,6 +385,9 @@ ModelBaseClass.prototype.toObject = function (onlySchema, removeHidden, removePr
|
||||||
if (val !== undefined && val !== null && val.toObject) {
|
if (val !== undefined && val !== null && val.toObject) {
|
||||||
data[propertyName] = val.toObject(!schemaLess, removeHidden, true);
|
data[propertyName] = val.toObject(!schemaLess, removeHidden, true);
|
||||||
} else {
|
} else {
|
||||||
|
if (val === undefined && persistUndefinedAsNull) {
|
||||||
|
val = null;
|
||||||
|
}
|
||||||
data[propertyName] = val;
|
data[propertyName] = val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -398,8 +413,11 @@ ModelBaseClass.prototype.toObject = function (onlySchema, removeHidden, removePr
|
||||||
if (removeProtected && Model.isProtectedProperty(propertyName)) {
|
if (removeProtected && Model.isProtectedProperty(propertyName)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (data[propertyName] !== undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
val = self[propertyName];
|
val = self[propertyName];
|
||||||
if (val !== undefined && data[propertyName] === undefined) {
|
if (val !== undefined) {
|
||||||
if (typeof val === 'function') {
|
if (typeof val === 'function') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -408,6 +426,8 @@ ModelBaseClass.prototype.toObject = function (onlySchema, removeHidden, removePr
|
||||||
} else {
|
} else {
|
||||||
data[propertyName] = val;
|
data[propertyName] = val;
|
||||||
}
|
}
|
||||||
|
} else if (persistUndefinedAsNull) {
|
||||||
|
data[propertyName] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Now continue to check __data
|
// Now continue to check __data
|
||||||
|
@ -434,6 +454,8 @@ ModelBaseClass.prototype.toObject = function (onlySchema, removeHidden, removePr
|
||||||
|
|
||||||
if (val !== undefined && val !== null && val.toObject) {
|
if (val !== undefined && val !== null && val.toObject) {
|
||||||
data[propertyName] = val.toObject(!schemaLess, removeHidden, true);
|
data[propertyName] = val.toObject(!schemaLess, removeHidden, true);
|
||||||
|
} else if (val === undefined && persistUndefinedAsNull) {
|
||||||
|
data[propertyName] = null;
|
||||||
} else {
|
} else {
|
||||||
data[propertyName] = val;
|
data[propertyName] = val;
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,4 +149,102 @@ describe('datatypes', function () {
|
||||||
coerced.nested.constructor.name.should.equal('Object');
|
coerced.nested.constructor.name.should.equal('Object');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('model option persistUndefinedAsNull', function() {
|
||||||
|
var TestModel;
|
||||||
|
before(function(done) {
|
||||||
|
TestModel = db.define(
|
||||||
|
'TestModel',
|
||||||
|
{
|
||||||
|
desc: { type: String, required: false },
|
||||||
|
stars: { type: Number, required: false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
persistUndefinedAsNull: true
|
||||||
|
});
|
||||||
|
|
||||||
|
db.automigrate(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set missing optional properties to null', function(done) {
|
||||||
|
var EXPECTED = { desc: null, stars: null };
|
||||||
|
TestModel.create({ name: 'a-test-name' }, function(err, created) {
|
||||||
|
if (err) return done(err);
|
||||||
|
created.should.have.properties(EXPECTED);
|
||||||
|
|
||||||
|
TestModel.findById(created.id, function(err, found) {
|
||||||
|
if (err) return done(err);
|
||||||
|
found.should.have.properties(EXPECTED);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert property value undefined to null', function(done) {
|
||||||
|
var EXPECTED = { desc: null, extra: null };
|
||||||
|
var data ={ desc: undefined, extra: undefined };
|
||||||
|
TestModel.create(data, function(err, created) {
|
||||||
|
if (err) return done(err);
|
||||||
|
created.should.have.properties(EXPECTED);
|
||||||
|
|
||||||
|
TestModel.findById(created.id, function(err, found) {
|
||||||
|
if (err) return done(err);
|
||||||
|
found.should.have.properties(EXPECTED);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert undefined to null in the setter', function() {
|
||||||
|
var inst = new TestModel();
|
||||||
|
inst.desc = undefined;
|
||||||
|
inst.should.have.property('desc', null);
|
||||||
|
inst.toObject().should.have.property('desc', null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use null in unsetAttribute()', function() {
|
||||||
|
var inst = new TestModel();
|
||||||
|
inst.unsetAttribute('stars');
|
||||||
|
inst.should.have.property('stars', null);
|
||||||
|
inst.toObject().should.have.property('stars', null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert undefined to null on save', function(done) {
|
||||||
|
var EXPECTED = { desc: null, stars: null, extra: null };
|
||||||
|
TestModel.create({}, function(err, created) {
|
||||||
|
if (err) return done(err);
|
||||||
|
created.desc = undefined; // Note: this is may be a no-op
|
||||||
|
created.unsetAttribute('stars');
|
||||||
|
created.extra = undefined;
|
||||||
|
created.__data.dx = undefined;
|
||||||
|
|
||||||
|
created.save(function(err, saved) {
|
||||||
|
if (err) return done(err);
|
||||||
|
created.should.have.properties(EXPECTED);
|
||||||
|
|
||||||
|
TestModel.dataSource.connector.all(
|
||||||
|
TestModel.modelName,
|
||||||
|
{ where: { id: created.id } },
|
||||||
|
function(err, found) {
|
||||||
|
if (err) return done(err);
|
||||||
|
should.exist(found[0]);
|
||||||
|
found[0].should.have.properties(EXPECTED);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert undefined to null in toObject()', function() {
|
||||||
|
var inst = new TestModel();
|
||||||
|
inst.desc = undefined; // Note: this may be a no-op
|
||||||
|
inst.unsetAttribute('stars');
|
||||||
|
inst.extra = undefined;
|
||||||
|
inst.__data.dx = undefined;
|
||||||
|
|
||||||
|
inst.toObject(false).should.have.properties({
|
||||||
|
desc: null, stars: null, extra: null, dx: null
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue