Repository: dynamoosejs/dynamoose
Pull request #818 (2020-04-18 02:03:44)

Fixing modifiers
Fixing an issue where modifiers wouldn't be called if value was falsey.


Patch:
diff --git a/lib/Document.js b/lib/Document.js
index 4c4de3ee..b7900849 100644
--- a/lib/Document.js
+++ b/lib/Document.js
@@ -10,9 +10,6 @@ const staticMethods = {
 	"isDynamoObject": function (object, recurrsive = false) {
 		// This function will check to see if a nested object is valid by calling Document.isDynamoObject recursively
 		const isValid = (value) => {
-			if (typeof value === "undefined" || value === null) {
-				return false;
-			}
 			const keys = Object.keys(value);
 			const key = keys[0];
 			const nestedResult = (typeof value[key] === "object" && !(value[key] instanceof Buffer) ? (Array.isArray(value[key]) ? value[key].every((value) => this.isDynamoObject(value, true)) : this.isDynamoObject(value[key])) : true);
@@ -227,6 +224,18 @@ function DocumentCarrier(model) {
 				utils.object.set(returnObject, key, typeDetails[settings.type](value));
 			}
 		});
+		if (settings.modifiers) {
+			await Promise.all(settings.modifiers.map((modifier) => {
+				return Promise.all(Document.attributesWithSchema(returnObject).map(async (key) => {
+					const value = utils.object.get(returnObject, key);
+					const modifierFunction = await model.schema.getAttributeSettingValue(modifier, key, {"returnFunction": true});
+					const isValueUndefined = typeof value === "undefined" || value === null;
+					if (modifierFunction && !isValueUndefined) {
+						utils.object.set(returnObject, key, await modifierFunction(value));
+					}
+				}));
+			}));
+		}
 		if (settings.validate) {
 			await Promise.all(Document.attributesWithSchema(returnObject).map(async (key) => {
 				const value = utils.object.get(returnObject, key);
@@ -284,17 +293,6 @@ function DocumentCarrier(model) {
 				}
 			}));
 		}
-		if (settings.modifiers) {
-			await Promise.all(settings.modifiers.map((modifier) => {
-				return Promise.all(Document.attributesWithSchema(returnObject).map(async (key) => {
-					const value = utils.object.get(returnObject, key);
-					const modifierFunction = await model.schema.getAttributeSettingValue(modifier, key, {"returnFunction": true});
-					if (modifierFunction && value) {
-						utils.object.set(returnObject, key, await modifierFunction(value));
-					}
-				}));
-			}));
-		}
 
 		return returnObject;
 	};


Test patch:
diff --git a/test/Document.js b/test/Document.js
index e6d97d02..8b026461 100644
--- a/test/Document.js
+++ b/test/Document.js
@@ -182,28 +182,6 @@ describe("Document", () => {
 					});
 				});
 
-				it("Should save with correct object with undefined as value without required or default as first property", async () => {
-					putItemFunction = () => Promise.resolve();
-					User = new Model("User", new Schema({"id": Number, "name": String}));
-					user = new User({"name": undefined, "id": 1});
-					await callType.func(user).bind(user)();
-					expect(putParams).to.eql([{
-						"Item": {"id": {"N": "1"}},
-						"TableName": "User"
-					}]);
-				});
-
-				it("Should save with correct object with undefined as value without required or default as second property", async () => {
-					putItemFunction = () => Promise.resolve();
-					User = new Model("User", new Schema({"id": Number, "name": String}));
-					user = new User({"id": 1, "name": undefined});
-					await callType.func(user).bind(user)();
-					expect(putParams).to.eql([{
-						"Item": {"id": {"N": "1"}},
-						"TableName": "User"
-					}]);
-				});
-
 				it("Should save with correct object with string set", async () => {
 					putItemFunction = () => Promise.resolve();
 					User = new Model("User", {"id": Number, "friends": [String]});
@@ -1305,14 +1283,6 @@ describe("Document", () => {
 			{
 				"input": {"data": {"L": []}},
 				"output": true
-			},
-			{
-				"input": {"prop": undefined},
-				"output": false
-			},
-			{
-				"input": {"prop": null},
-				"output": false
 			}
 		];
 
@@ -1714,6 +1684,16 @@ describe("Document", () => {
 				"input": [{"id": 1, "items": [{"birthday": 1}, {"birthday": 10000}, {"birthday": "test"}]}, {"type": "fromDynamo", "customTypesDynamo": true}],
 				"schema": new Schema({"id": Number, "items": {"type": Array, "schema": [{"type": Object, "schema": {"birthday": Date}}]}}),
 				"error": new Error.ValidationError("Expected items.2.birthday to be of type date, instead found type string.")
+			},
+			{
+				"schema": {"id": Number, "name": {"type": String, "set": (val) => val === "" ? undefined : val}},
+				"input": [{"id": 1, "name": ""}, {"type": "toDynamo", "modifiers": ["set"]}],
+				"output": {"id": 1, "name": undefined}
+			},
+			{
+				"schema": {"id": Number, "name": {"type": String, "required": true, "set": (val) => val === "" ? undefined : val}},
+				"input": [{"id": 1, "name": ""}, {"type": "toDynamo", "modifiers": ["set"], "required": true}],
+				"error": new Error.ValidationError("name is a required property but has no value when trying to save document")
 			}
 		];
 
diff --git a/test/Model.js b/test/Model.js
index 27e59f60..22b5938a 100644
--- a/test/Model.js
+++ b/test/Model.js
@@ -1349,42 +1349,6 @@ describe("Model", () => {
 					});
 				});
 
-				it("Should send correct params to putItem with value as undefined as first property", async () => {
-					createItemFunction = () => Promise.resolve();
-					await callType.func(User).bind(User)({"name": undefined, "id": 1});
-					expect(createItemParams).to.be.an("object");
-					expect(createItemParams).to.eql({
-						"ConditionExpression": "attribute_not_exists(#__hash_key)",
-						"ExpressionAttributeNames": {
-							"#__hash_key": "id"
-						},
-						"Item": {
-							"id": {
-								"N": "1"
-							}
-						},
-						"TableName": "User"
-					});
-				});
-
-				it("Should send correct params to putItem with value as undefined as second property", async () => {
-					createItemFunction = () => Promise.resolve();
-					await callType.func(User).bind(User)({"id": 1, "name": undefined});
-					expect(createItemParams).to.be.an("object");
-					expect(createItemParams).to.eql({
-						"ConditionExpression": "attribute_not_exists(#__hash_key)",
-						"ExpressionAttributeNames": {
-							"#__hash_key": "id"
-						},
-						"Item": {
-							"id": {
-								"N": "1"
-							}
-						},
-						"TableName": "User"
-					});
-				});
-
 				it("Should not include attributes that do not exist in schema", async () => {
 					createItemFunction = () => Promise.resolve();
 					await callType.func(User).bind(User)({"id": 1, "name": "Charlie", "hello": "world"});