Fix Model.prototype.inspect

Return the raw object data when running on Node v0.11.14+
That way all `inspect` options are always preserved.

When running on older version:
 - Honour the depth argument passed to the custom `inspect` function.
 - Disable color output, becase there is now way how to detect whether
   colors were enabled or disabled by the top-level caller.
This commit is contained in:
Miroslav Bajtoš 2015-01-12 16:50:51 +01:00
parent 9b759c95ac
commit e4fc38788f
2 changed files with 54 additions and 26 deletions

View File

@ -53,14 +53,14 @@ function ModelBaseClass(data, options) {
ModelBaseClass.prototype._initProperties = function (data, options) {
var self = this;
var ctor = this.constructor;
if(data instanceof ctor) {
// Convert the data to be plain object to avoid pollutions
data = data.toObject(false);
}
var properties = _extend({}, ctor.definition.properties);
data = data || {};
if (typeof ctor.applyProperties === 'function') {
ctor.applyProperties(data);
}
@ -72,7 +72,7 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
if(strict === undefined) {
strict = ctor.definition.settings.strict;
}
if (ctor.hideInternalProperties) {
// Object.defineProperty() is expensive. We only try to make the internal
// properties hidden (non-enumerable) if the model class has the
@ -122,7 +122,7 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
this.__strict = strict;
this.__persisted = false;
}
if (options.persisted !== undefined) {
this.__persisted = options.persisted === true;
}
@ -132,13 +132,13 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
}
var keys = Object.keys(data);
if (Array.isArray(options.fields)) {
keys = keys.filter(function(k) {
return (options.fields.indexOf(k) != -1);
});
}
var size = keys.length;
var p, propVal;
for (var k = 0; k < size; k++) {
@ -156,7 +156,7 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
}
} else if (ctor.relations[p]) {
var relationType = ctor.relations[p].type;
if (!properties[p]) {
var modelTo = ctor.relations[p].modelTo || ModelBaseClass;
var multiple = ctor.relations[p].multiple;
@ -165,7 +165,7 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
properties[p] = { name: typeName, type: propType };
this.setStrict(false);
}
// Relation
if (relationType === 'belongsTo' && propVal != null) {
// If the related model is populated
@ -182,15 +182,15 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
}
}
}
keys = Object.keys(properties);
if (Array.isArray(options.fields)) {
keys = keys.filter(function(k) {
return (options.fields.indexOf(k) != -1);
});
}
size = keys.length;
for (k = 0; k < size; k++) {
@ -216,11 +216,11 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
self.__data[p] = def;
}
}
// Handle complex types (JSON/Object)
var type = properties[p].type;
if (!BASE_TYPES[type.name]) {
if (typeof self.__data[p] !== 'object' && self.__data[p]) {
try {
self.__data[p] = JSON.parse(self.__data[p] + '');
@ -228,7 +228,7 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
self.__data[p] = String(self.__data[p]);
}
}
if (type.prototype instanceof ModelBaseClass) {
if (!(self.__data[p] instanceof type)
&& typeof self.__data[p] === 'object'
@ -438,8 +438,28 @@ ModelBaseClass.prototype.reset = function () {
}
};
ModelBaseClass.prototype.inspect = function () {
return util.inspect(this.__data, false, 4, true);
// Node v0.11+ allows custom inspect functions to return an object
// instead of string. That way options like `showHidden` and `colors`
// can be preserved.
var versionParts = process.versions.node
.split(/\./g).map(function(v) { return +v; });
var INSPECT_SUPPORTS_OBJECT_RETVAL =
versionParts[0] > 0 ||
versionParts[1] > 11 ||
(versionParts[0] === 11 && versionParts[1] >= 14);
ModelBaseClass.prototype.inspect = function (depth) {
if (INSPECT_SUPPORTS_OBJECT_RETVAL)
return this.__data;
// Workaround for older versions
// See also https://github.com/joyent/node/commit/66280de133
return util.inspect(this.__data, {
showHidden: false,
depth: depth,
colors: false
});
};
ModelBaseClass.mixin = function (anotherClass, options) {

View File

@ -230,7 +230,7 @@ describe('validations', function () {
done();
});
});
it('should return validation metadata', function() {
var expected = {name:[{validation: 'presence', options: {}}]};
delete User.validations;
@ -245,11 +245,11 @@ describe('validations', function () {
it('should validate presence', function () {
User.validatesPresenceOf('name', 'email');
var validations = User.validations;
validations.name.should.eql([{validation: 'presence', options: {}}]);
validations.email.should.eql([{validation: 'presence', options: {}}]);
var u = new User;
u.isValid().should.not.be.true;
u.name = 1;
@ -273,7 +273,7 @@ describe('validations', function () {
});
});
describe('absence', function () {
it('should validate absence', function () {
@ -355,7 +355,7 @@ describe('validations', function () {
done(err);
});
});
it('should skip blank values', function (done) {
User.validatesUniquenessOf('email');
var u = new User({email: ' '});
@ -370,9 +370,9 @@ describe('validations', function () {
});
})).should.be.false;
});
it('should work with if/unless', function (done) {
User.validatesUniquenessOf('email', {
User.validatesUniquenessOf('email', {
if: function() { return true; },
unless: function() { return false; }
});
@ -414,7 +414,7 @@ describe('validations', function () {
Boolean(u.isValid()).should.be.false;
u.errors.codes.should.eql({ email: ['invalid-email'] });
});
it('should validate and return detailed error messages', function() {
User.validate('global', function (err) {
if (this.email === 'hello' || this.email === 'hey') {
@ -427,11 +427,11 @@ describe('validations', function () {
u.errors.should.eql({ email: ['Cannot be `hello`'] });
u.errors.codes.should.eql({ email: ['invalid-email'] });
});
it('should validate using custom async validation', function(done) {
User.validateAsync('email', function (err, next) {
process.nextTick(next);
}, {
}, {
if: function() { return true; },
unless: function() { return false; }
});
@ -486,6 +486,14 @@ describe('validations', function () {
.should.equal('`prop` is invalid (value: [ { a: [Object] } ]).');
});
it('should exclude colors from Model values', function() {
var obj = new User();
obj.email = 'test@example.com';
var err = givenValidationError('user', obj, 'is invalid');
getErrorDetails(err).should.equal(
'`user` is invalid (value: { email: \'test@example.com\' }).');
});
function givenValidationError(propertyName, propertyValue, errorMessage) {
var jsonVal = {};
jsonVal[propertyName] = propertyValue;