Skip to content

Commit

Permalink
Merge branch 'master' into saveReturnObject
Browse files Browse the repository at this point in the history
  • Loading branch information
fishcharlie committed Mar 10, 2021
2 parents a1857fe + a084cf0 commit 83fc0ed
Show file tree
Hide file tree
Showing 7 changed files with 601 additions and 8 deletions.
24 changes: 18 additions & 6 deletions lib/Populate.ts
Expand Up @@ -29,7 +29,8 @@ export function PopulateDocument (this: Document, settings?: PopulateSettings |
const {model} = this;
const localSettings = settings;
const promise = model.schemaForObject(this).then((schema) => {
const modelAttributes: any[] = utils.array_flatten(schema.attributes().map((prop) => ({prop, "details": schema.getAttributeTypeDetails(prop)}))).filter((obj) => Array.isArray(obj.details) ? obj.details.some((detail) => detail.name === "Model") : obj.details.name === "Model").map((obj) => obj.prop);
// TODO: uncomment out `/* || detail.name === "Model Set"*/` part and add relevant tests
const modelAttributes: any[] = utils.array_flatten(schema.attributes().map((prop) => ({prop, "details": schema.getAttributeTypeDetails(prop)}))).filter((obj) => Array.isArray(obj.details) ? obj.details.some((detail) => detail.name === "Model"/* || detail.name === "Model Set"*/) : obj.details.name === "Model" || obj.details.name === "Model Set").map((obj) => obj.prop);

return {schema, modelAttributes};
}).then((obj: {schema: Schema; modelAttributes: any[]}) => {
Expand All @@ -42,8 +43,11 @@ export function PopulateDocument (this: Document, settings?: PopulateSettings |
// TODO: `subModel` is currently any, we should fix that
const subModel = typeof typeSettings.model === "object" ? model.Document as any : typeSettings.model;

const doesPopulatePropertyExist = !(typeof this[prop] === "undefined" || this[prop] === null);
if (!doesPopulatePropertyExist || this[prop] instanceof subModel) {
prop = prop.endsWith(".0") ? prop.substring(0, prop.length - 2) : prop;

const documentPropValue = utils.object.get(this as any, prop);
const doesPopulatePropertyExist = !(typeof documentPropValue === "undefined" || documentPropValue === null);
if (!doesPopulatePropertyExist || documentPropValue instanceof subModel) {
return;
}
const key: string = [internalSettings.parentKey, prop].filter((a) => Boolean(a)).join(".");
Expand All @@ -54,9 +58,17 @@ export function PopulateDocument (this: Document, settings?: PopulateSettings |
return;
}

const subDocument = await subModel.get(this[prop]);
const saveDocument: Document = await PopulateDocument.bind(subDocument)(localSettings, null, {"parentKey": key});
this[prop] = saveDocument;
const isArray = Array.isArray(documentPropValue);
const isSet = documentPropValue instanceof Set;
if (isArray || isSet) {
const subDocuments = await Promise.all([...documentPropValue as any].map((val) => subModel.get(val)));
const saveDocuments = await Promise.all(subDocuments.map((doc) => PopulateDocument.bind(doc)(localSettings, null, {"parentKey": key})));
utils.object.set(this as any, prop, saveDocuments);
} else {
const subDocument = await subModel.get(documentPropValue);
const saveDocument: Document = await PopulateDocument.bind(subDocument)(localSettings, null, {"parentKey": key});
utils.object.set(this as any, prop, saveDocument);
}
}));
});

Expand Down
21 changes: 19 additions & 2 deletions lib/Schema.ts
Expand Up @@ -242,7 +242,7 @@ const attributeTypes: (DynamoDBTypeResult | DynamoDBSetTypeResult)[] = utils.arr
type SetValueType = {wrapperName: "Set"; values: ValueType[]; type: string /* TODO: should probably make this an enum */};
type GeneralValueType = string | boolean | number | Buffer | Date;
export type ValueType = GeneralValueType | {[key: string]: ValueType} | ValueType[] | SetValueType;
type AttributeType = string | StringConstructor | BooleanConstructor | NumberConstructor | typeof Buffer | DateConstructor | ObjectConstructor | ArrayConstructor | SetConstructor | symbol | Schema;
type AttributeType = string | StringConstructor | BooleanConstructor | NumberConstructor | typeof Buffer | DateConstructor | ObjectConstructor | ArrayConstructor | SetConstructor | symbol | Schema | ModelType<Document>;

export interface TimestampObject {
createdAt?: string | string[];
Expand Down Expand Up @@ -540,6 +540,22 @@ export class Schema {

utils.object.set(parsedObject, key, newValue);
});
utils.object.entries(parsedObject).forEach((entry) => {
const key = entry[0];
const value = entry[1] as any;

if (!key.endsWith(".type") && !key.endsWith(".0")) {
if (value && value.Model && value.Model instanceof Model) {
utils.object.set(parsedObject, key, {"type": value});
} else if (value && Array.isArray(value)) {
value.forEach((item, index) => {
if (item && item.Model && item.Model instanceof Model) {
utils.object.set(parsedObject, `${key}.${index}`, {"type": item});
}
});
}
}
});

// Anytime `this.schemaObject` is modified, `this[internalCache].attributes` must be set to undefined or null
this.schemaObject = parsedObject;
Expand Down Expand Up @@ -837,7 +853,8 @@ Schema.prototype.getAttributeTypeDetails = function (this: Schema, key: string,
if (Array.isArray(schemaValue[index])) {
schemaValue = schemaValue[index];
}
type = getType(schemaValue[0]);
const subValue = schemaValue[0];
type = getType(typeof subValue === "object" && subValue.type ? subValue.type : subValue);
}

const returnObject = retrieveTypeInfo(type, isSet, key, typeSettings);
Expand Down
5 changes: 5 additions & 0 deletions test/types/Schema.ts
Expand Up @@ -62,6 +62,11 @@ const shouldSucceedWithConstantType = new dynamoose.Schema({
}
});

const shouldSucceedWithModelAsType = new dynamoose.Schema({
"id": String,
"item": dynamoose.model("User", {"id": String, "data": String})
});

// Nested Schema
const shouldSucceedWithNestedSchema = new dynamoose.Schema({
"id": String,
Expand Down
224 changes: 224 additions & 0 deletions test/unit/Model.js
Expand Up @@ -1492,6 +1492,230 @@ describe("Model", () => {
});

describe("Populate", () => {
it("Should not populate document automatically", async () => {
let getItemTimesCalled = 0;

User = dynamoose.model("User", {"id": Number, "name": String, "parent": dynamoose.model("Parent", {"id": Number, "data": String})});
dynamoose.aws.ddb.set({
"getItem": (params) => {
getItemTimesCalled++;
return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"N": "2"}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}};
}
});
const user = await callType.func(User).bind(User)(1);
expect(user.toJSON()).to.eql({
"id": 1,
"name": "Charlie",
"parent": 2
});
expect(getItemTimesCalled).to.eql(1);
});

it("Should not populate document automatically if schema property is object", async () => {
let getItemTimesCalled = 0;

User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": dynamoose.model("Parent", {"id": Number, "data": String})}});
dynamoose.aws.ddb.set({
"getItem": (params) => {
getItemTimesCalled++;
return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"N": "2"}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}};
}
});
const user = await callType.func(User).bind(User)(1);
expect(user.toJSON()).to.eql({
"id": 1,
"name": "Charlie",
"parent": 2
});
expect(getItemTimesCalled).to.eql(1);
});

it("Should not populate document automatically when schema property is dynamoose.THIS", async () => {
let getItemTimesCalled = 0;

User = dynamoose.model("User", {"id": Number, "name": String, "parent": dynamoose.THIS});
dynamoose.aws.ddb.set({
"getItem": (params) => {
getItemTimesCalled++;
return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"N": "2"}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}};
}
});
const user = await callType.func(User).bind(User)(1);
expect(user.toJSON()).to.eql({
"id": 1,
"name": "Charlie",
"parent": 2
});
expect(getItemTimesCalled).to.eql(1);
});

it("Should not populate document automatically when schema property is dynamoose.THIS if schema property is object", async () => {
let getItemTimesCalled = 0;

User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": dynamoose.THIS}});
dynamoose.aws.ddb.set({
"getItem": (params) => {
getItemTimesCalled++;
return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"N": "2"}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}};
}
});
const user = await callType.func(User).bind(User)(1);
expect(user.toJSON()).to.eql({
"id": 1,
"name": "Charlie",
"parent": 2
});
expect(getItemTimesCalled).to.eql(1);
});

it("Should not populate document automatically when using set", async () => {
let getItemTimesCalled = 0;

User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Set, "schema": [dynamoose.model("Parent", {"id": Number, "data": String})]}});
dynamoose.aws.ddb.set({
"getItem": (params) => {
getItemTimesCalled++;
return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"NS": ["2"]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}};
}
});
const user = await callType.func(User).bind(User)(1);
expect(user.id).to.eql(1);
expect(user.name).to.eql("Charlie");
expect(user.parent).to.eql(new Set([2]));
expect(Object.keys(user.toJSON())).to.eql(["id", "name", "parent"]);
expect(getItemTimesCalled).to.eql(1);
});

it("Should not populate document automatically when using set if schema property is object", async () => {
let getItemTimesCalled = 0;

User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Set, "schema": [{"type": dynamoose.model("Parent", {"id": Number, "data": String})}]}});
dynamoose.aws.ddb.set({
"getItem": (params) => {
getItemTimesCalled++;
return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"NS": ["2"]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}};
}
});
const user = await callType.func(User).bind(User)(1);
expect(user.id).to.eql(1);
expect(user.name).to.eql("Charlie");
expect(user.parent).to.eql(new Set([2]));
expect(Object.keys(user.toJSON())).to.eql(["id", "name", "parent"]);
expect(getItemTimesCalled).to.eql(1);
});

it("Should not populate document automatically when using set when schema property is dynamoose.THIS", async () => {
let getItemTimesCalled = 0;

User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Set, "schema": [dynamoose.THIS]}});
dynamoose.aws.ddb.set({
"getItem": (params) => {
getItemTimesCalled++;
return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"NS": ["2"]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}};
}
});
const user = await callType.func(User).bind(User)(1);
expect(user.id).to.eql(1);
expect(user.name).to.eql("Charlie");
expect(user.parent).to.eql(new Set([2]));
expect(Object.keys(user.toJSON())).to.eql(["id", "name", "parent"]);
expect(getItemTimesCalled).to.eql(1);
});

it("Should not populate document automatically when using set when schema property is dynamoose.THIS if schema property is object", async () => {
let getItemTimesCalled = 0;

User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Set, "schema": [{"type": dynamoose.THIS}]}});
dynamoose.aws.ddb.set({
"getItem": (params) => {
getItemTimesCalled++;
return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"NS": ["2"]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}};
}
});
const user = await callType.func(User).bind(User)(1);
expect(user.id).to.eql(1);
expect(user.name).to.eql("Charlie");
expect(user.parent).to.eql(new Set([2]));
expect(Object.keys(user.toJSON())).to.eql(["id", "name", "parent"]);
expect(getItemTimesCalled).to.eql(1);
});

it("Should not populate document automatically when using array", async () => {
let getItemTimesCalled = 0;

User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Array, "schema": [dynamoose.model("Parent", {"id": Number, "data": String})]}});
dynamoose.aws.ddb.set({
"getItem": (params) => {
getItemTimesCalled++;
return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"L": [{"N": "2"}]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}};
}
});
const user = await callType.func(User).bind(User)(1);
expect(user.toJSON()).to.eql({
"id": 1,
"name": "Charlie",
"parent": [2]
});
expect(getItemTimesCalled).to.eql(1);
});

it("Should not populate document automatically when using array if schema property is object", async () => {
let getItemTimesCalled = 0;

User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Array, "schema": [{"type": dynamoose.model("Parent", {"id": Number, "data": String})}]}});
dynamoose.aws.ddb.set({
"getItem": (params) => {
getItemTimesCalled++;
return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"L": [{"N": "2"}]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}};
}
});
const user = await callType.func(User).bind(User)(1);
expect(user.toJSON()).to.eql({
"id": 1,
"name": "Charlie",
"parent": [2]
});
expect(getItemTimesCalled).to.eql(1);
});

it("Should not populate document automatically when using array when schema property is dynamoose.THIS", async () => {
let getItemTimesCalled = 0;

User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Array, "schema": [dynamoose.THIS]}});
dynamoose.aws.ddb.set({
"getItem": (params) => {
getItemTimesCalled++;
return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"L": [{"N": "2"}]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}};
}
});
const user = await callType.func(User).bind(User)(1);
expect(user.toJSON()).to.eql({
"id": 1,
"name": "Charlie",
"parent": [2]
});
expect(getItemTimesCalled).to.eql(1);
});

it("Should not populate document automatically when using array when schema property is dynamoose.THIS if schema property is object", async () => {
let getItemTimesCalled = 0;

User = dynamoose.model("User", {"id": Number, "name": String, "parent": {"type": Array, "schema": [{"type": dynamoose.THIS}]}});
dynamoose.aws.ddb.set({
"getItem": (params) => {
getItemTimesCalled++;
return {"promise": () => params.Key.id.N === "1" ? {"Item": {"id": {"N": "1"}, "name": {"S": "Charlie"}, "parent": {"L": [{"N": "2"}]}}} : {"Item": {"id": {"N": "2"}, "name": {"S": "Bob"}}}};
}
});
const user = await callType.func(User).bind(User)(1);
expect(user.toJSON()).to.eql({
"id": 1,
"name": "Charlie",
"parent": [2]
});
expect(getItemTimesCalled).to.eql(1);
});

it("Should autopopulate if model settings have populate set", async () => {
User = dynamoose.model("User", {"id": Number, "name": String, "parent": dynamoose.THIS}, {"populate": "*"});
dynamoose.aws.ddb.set({
Expand Down

0 comments on commit 83fc0ed

Please sign in to comment.