Skip to content

Commit 373e038

Browse files
authored
Merge pull request #1084 from strongloop/strict-mode-cleanup
[SEMVER-MAJOR] Strict mode cleanup
2 parents 7365e32 + 805db78 commit 373e038

File tree

7 files changed

+127
-72
lines changed

7 files changed

+127
-72
lines changed

3.0-RELEASE-NOTES.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,55 @@ bulk `updateOrCreate`, you could use `async.each` or `Promise.all` to
102102
repeatedly call `updateOfCreate` for each instance.
103103

104104
See [related code change](https://github.com/strongloop/loopback-datasource-juggler/pull/889) here.
105+
106+
## Removing strict: validate and strict: throw
107+
Given a user model definition as follow:
108+
```js
109+
ds.define('User', {
110+
name: String, required: false,
111+
age: Number, required: false
112+
})
113+
114+
var johndoe = new User({ name: 'John doe', age: 15, gender: 'm'});
115+
```
116+
- #### strict: false
117+
behavior will remain the same and unknown properties will be saved along with other properties
118+
```js
119+
//johndoe would be created as
120+
{
121+
id: 1,
122+
name: 'John doe',
123+
age: 15,
124+
geneder: 'm'
125+
}
126+
```
127+
128+
- #### strict: `'validate'` and strict: `'throw'`
129+
are removed in favor of `strict: true`, and because non-empty string
130+
resolves as true, they will share the same behavior as `strict: true`
131+
132+
- #### strict: `true`'s behavior change:
133+
134+
**Previously:** (2.x)
135+
136+
`strict:true` would silently removing unknown properties, and User instance will be created with unknown properties discarded
137+
138+
```js
139+
johndoe.isValid(); //true
140+
//johndoe would be created as
141+
{
142+
id: 1,
143+
name: 'John doe',
144+
age: 15
145+
}
146+
```
147+
148+
**New Behavior:** (3.x)
149+
150+
```js
151+
johndoe.isValid(); //false
152+
//johndoe would NOT be created and will result in the following error
153+
//ValidationError: The User instance is not valid. Details: gender is not defined in the model (value: 'm').
154+
```
155+
156+
See [related code change](https://github.com/strongloop/loopback-datasource-juggler/pull/1084) here.

lib/dao.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,7 @@ function applyStrictCheck(model, strict, data, inst, cb) {
9393
key = keys[i];
9494
if (props[key]) {
9595
result[key] = data[key];
96-
} else if (strict === 'throw') {
97-
cb(new Error(g.f('Unknown property: %s', key)));
98-
return;
99-
} else if (strict === 'validate') {
96+
} else if (strict) {
10097
inst.__unknownProperties.push(key);
10198
}
10299
}

lib/model.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ ModelBaseClass.prototype._initProperties = function(data, options) {
9494

9595
if (strict === undefined) {
9696
strict = ctor.definition.settings.strict;
97+
} else if (strict === 'throw') {
98+
g.warn('Warning: Model %s, {{strict mode: `throw`}} has been removed, ' +
99+
'please use {{`strict: true`}} instead, which returns' +
100+
'{{`Validation Error`}} for unknown properties,', ctor.modelName);
97101
}
98102

99103
var persistUndefinedAsNull = ctor.definition.settings.persistUndefinedAsNull;
@@ -141,7 +145,7 @@ ModelBaseClass.prototype._initProperties = function(data, options) {
141145
},
142146
});
143147

144-
if (strict === 'validate') {
148+
if (strict) {
145149
Object.defineProperty(this, '__unknownProperties', {
146150
writable: true,
147151
enumerable: false,
@@ -155,7 +159,7 @@ ModelBaseClass.prototype._initProperties = function(data, options) {
155159
this.__dataSource = options.dataSource;
156160
this.__strict = strict;
157161
this.__persisted = false;
158-
if (strict === 'validate') {
162+
if (strict) {
159163
this.__unknownProperties = [];
160164
}
161165
}
@@ -243,9 +247,7 @@ ModelBaseClass.prototype._initProperties = function(data, options) {
243247
this.constructor.modelName, p
244248
));
245249
}
246-
} else if (strict === 'throw') {
247-
throw new Error(g.f('Unknown property: %s', p));
248-
} else if (strict === 'validate') {
250+
} else {
249251
this.__unknownProperties.push(p);
250252
}
251253
}

lib/validations.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ Validatable.prototype.isValid = function(callback, data, options) {
433433
var valid = true, inst = this, wait = 0, async = false;
434434
var validations = this.constructor.validations;
435435

436-
var reportDiscardedProperties = this.__strict === 'validate' &&
436+
var reportDiscardedProperties = this.__strict &&
437437
this.__unknownProperties && this.__unknownProperties.length;
438438

439439
// exit with success when no errors

test/datatype.test.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ describe('datatypes', function() {
192192
TestModel = db.define(
193193
'TestModel',
194194
{
195+
name: {type: String, required: false},
195196
desc: {type: String, required: false},
196197
stars: {type: Number, required: false},
197198
},
@@ -220,12 +221,12 @@ describe('datatypes', function() {
220221

221222
it('should convert property value undefined to null', function(done) {
222223
var EXPECTED = {desc: null, extra: null};
224+
var data = {desc: undefined, extra: undefined};
223225
if (isStrict) {
224226
// SQL-based connectors don't support dynamic properties
225227
delete EXPECTED.extra;
228+
delete data.extra;
226229
}
227-
228-
var data = {desc: undefined, extra: undefined};
229230
TestModel.create(data, function(err, created) {
230231
if (err) return done(err);
231232

test/loopback-dl.test.js

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,47 +1917,8 @@ describe('ModelBuilder options.models', function() {
19171917
},
19181918
});
19191919
assert.equal(m1.address.number, undefined, 'm1 should not contain number property in address');
1920-
});
1921-
1922-
it('should use strictEmbeddedModels setting (validate) when applied on modelBuilder', function() {
1923-
var builder = new ModelBuilder();
1924-
builder.settings.strictEmbeddedModels = 'validate';
1925-
var M1 = builder.define('testEmbedded', {
1926-
name: 'string',
1927-
address: {
1928-
street: 'string',
1929-
},
1930-
});
1931-
var m1 = new M1({
1932-
name: 'Jim',
1933-
address: {
1934-
street: 'washington st',
1935-
number: 5512,
1936-
},
1937-
});
1938-
assert.equal(m1.address.number, undefined, 'm1 should not contain number property in address');
19391920
assert.equal(m1.address.isValid(), false, 'm1 address should not validate with extra property');
19401921
var codes = m1.address.errors && m1.address.errors.codes || {};
19411922
assert.deepEqual(codes.number, ['unknown-property']);
19421923
});
1943-
1944-
it('should use the strictEmbeddedModels setting (throw) when applied on modelBuilder', function() {
1945-
var builder = new ModelBuilder();
1946-
builder.settings.strictEmbeddedModels = 'throw';
1947-
var M1 = builder.define('testEmbedded', {
1948-
name: 'string',
1949-
address: {
1950-
street: 'string',
1951-
},
1952-
});
1953-
assert.throws(function() {
1954-
var m1 = new M1({
1955-
name: 'Jim',
1956-
address: {
1957-
street: 'washington st',
1958-
number: 5512,
1959-
},
1960-
});
1961-
});
1962-
});
19631924
});

test/manipulation.test.js

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -544,41 +544,83 @@ describe('manipulation', function() {
544544
});
545545
});
546546

547-
it('should ignore unknown attributes when strict: true', function(done) {
548-
// Using {foo: 'bar'} only causes dependent test failures due to the
549-
// stripping of object properties when in strict mode (ie. {foo: 'bar'}
550-
// changes to '{}' and breaks other tests
551-
person.updateAttributes({name: 'John', foo: 'bar'},
552-
function(err, p) {
553-
if (err) return done(err);
554-
should.not.exist(p.foo);
555-
Person.findById(p.id, function(e, p) {
556-
if (e) return done(e);
557-
should.not.exist(p.foo);
558-
done();
559-
});
547+
it('should discard undefined values before strict validation',
548+
function(done) {
549+
Person.definition.settings.strict = true;
550+
Person.findById(person.id, function(err, p) {
551+
p.updateAttributes({name: 'John', unknownVar: undefined},
552+
function(err, p) {
553+
// if uknownVar was defined, it would return validationError
554+
if (err) return done(err);
555+
Person.findById(p.id, function(e, p) {
556+
if (e) return done(e);
557+
p.name.should.equal('John');
558+
p.should.not.have.property('unknownVar');
559+
done();
560+
});
561+
});
560562
});
561-
});
563+
});
564+
565+
it('should allow unknown attributes when strict: false',
566+
function(done) {
567+
Person.definition.settings.strict = false;
568+
Person.findById(person.id, function(err, p) {
569+
p.updateAttributes({name: 'John', foo: 'bar'},
570+
function(err, p) {
571+
if (err) return done(err);
572+
p.should.have.property('foo');
573+
done();
574+
});
575+
});
576+
});
562577

563-
it('should throw error on unknown attributes when strict: throw', function(done) {
578+
// Prior to version 3.0 `strict: true` used to silently remove unknown properties,
579+
// now return validationError upon unknown properties
580+
it('should return error on unknown attributes when strict: true',
581+
function(done) {
582+
// Using {foo: 'bar'} only causes dependent test failures due to the
583+
// stripping of object properties when in strict mode (ie. {foo: 'bar'}
584+
// changes to '{}' and breaks other tests
585+
Person.definition.settings.strict = true;
586+
Person.findById(person.id, function(err, p) {
587+
p.updateAttributes({name: 'John', foo: 'bar'},
588+
function(err, p) {
589+
should.exist(err);
590+
err.name.should.equal('ValidationError');
591+
err.message.should.containEql('`foo` is not defined in the model');
592+
p.should.not.have.property('foo');
593+
Person.findById(p.id, function(e, p) {
594+
if (e) return done(e);
595+
p.should.not.have.property('foo');
596+
done();
597+
});
598+
});
599+
});
600+
});
601+
602+
// strict: throw is deprecated, use strict: true instead
603+
// which returns Validation Error for unknown properties
604+
it('should fallback to strict:true when using strict: throw', function(done) {
564605
Person.definition.settings.strict = 'throw';
565606
Person.findById(person.id, function(err, p) {
566607
p.updateAttributes({foo: 'bar'},
567608
function(err, p) {
568609
should.exist(err);
569-
err.name.should.equal('Error');
570-
err.message.should.equal('Unknown property: foo');
571-
should.not.exist(p);
610+
err.name.should.equal('ValidationError');
611+
err.message.should.containEql('`foo` is not defined in the model');
572612
Person.findById(person.id, function(e, p) {
573613
if (e) return done(e);
574-
should.not.exist(p.foo);
614+
p.should.not.have.property('foo');
575615
done();
576616
});
577617
});
578618
});
579619
});
580620

581-
it('should throw error on unknown attributes when strict: throw', function(done) {
621+
// strict: validate is deprecated, use strict: true instead
622+
// behavior remains the same as before, because validate is now default behavior
623+
it('should fallback to strict:true when using strict:validate', function(done) {
582624
Person.definition.settings.strict = 'validate';
583625
Person.findById(person.id, function(err, p) {
584626
p.updateAttributes({foo: 'bar'},
@@ -588,7 +630,7 @@ describe('manipulation', function() {
588630
err.message.should.containEql('`foo` is not defined in the model');
589631
Person.findById(person.id, function(e, p) {
590632
if (e) return done(e);
591-
should.not.exist(p.foo);
633+
p.should.not.have.property('foo');
592634
done();
593635
});
594636
});

0 commit comments

Comments
 (0)