Skip to content

Commit

Permalink
fix: better support of array embedded columns
Browse files Browse the repository at this point in the history
add basic support of array embedded in ColumnMetadata so we can
use getEntityValueMap and getEntityValue with mongo
  • Loading branch information
imnotjames committed Jul 5, 2021
1 parent 8a60d70 commit b9a5071
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 14 deletions.
6 changes: 6 additions & 0 deletions src/decorator/options/ColumnEmbeddedOptions.ts
Expand Up @@ -9,4 +9,10 @@ export interface ColumnEmbeddedOptions {
*/
prefix?: string | boolean;

/**
* Indicates if this embedded is in array mode.
*
* This option works only in mongodb.
*/
array?: boolean;
}
36 changes: 24 additions & 12 deletions src/metadata/ColumnMetadata.ts
Expand Up @@ -526,31 +526,40 @@ export class ColumnMetadata {

// first step - we extract all parent properties of the entity relative to this column, e.g. [data, information, counters]
const propertyNames = [...this.embeddedMetadata.parentPropertyNames];
const isEmbeddedArray = this.embeddedMetadata.isArray;

// now need to access post[data][information][counters] to get column value from the counters
// and on each step we need to create complex literal object, e.g. first { data },
// then { data: { information } }, then { data: { information: { counters } } },
// then { data: { information: { counters: [this.propertyName]: entity[data][information][counters][this.propertyName] } } }
// this recursive function helps doing that
const extractEmbeddedColumnValue = (propertyNames: string[], value: ObjectLiteral, map: ObjectLiteral): any => {
const extractEmbeddedColumnValue = (propertyNames: string[], value: ObjectLiteral): ObjectLiteral => {
if (value === undefined) {
return {};
}

const propertyName = propertyNames.shift();
if (value === undefined)
return map;

if (propertyName) {
const submap: ObjectLiteral = {};
extractEmbeddedColumnValue(propertyNames, value[propertyName], submap);
const submap = extractEmbeddedColumnValue(propertyNames, value[propertyName]);
if (Object.keys(submap).length > 0) {
map[propertyName] = submap;
return { [propertyName]: submap };
}
return map;
return {};
}
if (value[this.propertyName] !== undefined && (returnNulls === false || value[this.propertyName] !== null))
map[this.propertyName] = value[this.propertyName];
return map;

if (isEmbeddedArray && Array.isArray(value)) {
return value.map(v => ({ [this.propertyName]: v[this.propertyName] }));
}

if (value[this.propertyName] !== undefined && (returnNulls === false || value[this.propertyName] !== null)) {
return { [this.propertyName]: value[this.propertyName] };
}

return {};
};
const map: ObjectLiteral = {};
extractEmbeddedColumnValue(propertyNames, entity, map);
const map = extractEmbeddedColumnValue(propertyNames, entity);

return Object.keys(map).length > 0 ? map : undefined;

} else { // no embeds - no problems. Simply return column property name and its value of the entity
Expand Down Expand Up @@ -589,6 +598,7 @@ export class ColumnMetadata {

// first step - we extract all parent properties of the entity relative to this column, e.g. [data, information, counters]
const propertyNames = [...this.embeddedMetadata.parentPropertyNames];
const isEmbeddedArray = this.embeddedMetadata.isArray;

// next we need to access post[data][information][counters][this.propertyName] to get column value from the counters
// this recursive function takes array of generated property names and gets the post[data][information][counters] embed
Expand Down Expand Up @@ -616,6 +626,8 @@ export class ColumnMetadata {
} else if (this.referencedColumn) {
value = this.referencedColumn.getEntityValue(embeddedObject[this.propertyName]);

} else if (isEmbeddedArray && Array.isArray(embeddedObject)) {
value = embeddedObject.map(o => o[this.propertyName]);
} else {
value = embeddedObject[this.propertyName];
}
Expand Down
4 changes: 2 additions & 2 deletions test/functional/mongodb/basic/array-columns/entity/Post.ts
Expand Up @@ -25,10 +25,10 @@ export class Post {
@Column()
booleans: boolean[];

@Column(type => Counters)
@Column(type => Counters, { array: true })
other1: Counters[];

@Column(type => Counters)
@Column(type => Counters, { array: true })
other2: Counters[];

}
Expand Up @@ -118,4 +118,25 @@ describe("mongodb > array columns", () => {

})));

it("should retrieve arrays from the column metadata", () => Promise.all(connections.map(async connection => {
const post = new Post();
post.title = "Post";
post.names = ["umed", "dima", "bakhrom"];
post.numbers = [1, 0, 1];
post.booleans = [true, false, false];
post.counters = [
new Counters(1, "number #1"),
new Counters(2, "number #2"),
new Counters(3, "number #3"),
];
post.other1 = [];

const column = connection.getMetadata(Post)
.columns
.find(c => c.propertyPath === 'counters.text')!;

const value = column.getEntityValue(post);

expect(value).to.eql([ "number #1", "number #2", "number #3" ]);
})));
});
17 changes: 17 additions & 0 deletions test/functional/mongodb/basic/mongo-repository/mongo-repository.ts
@@ -1,4 +1,5 @@
import "reflect-metadata";
import {expect} from "chai";
import {Connection} from "../../../../../src/connection/Connection";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils";
import {Post} from "./entity/Post";
Expand Down Expand Up @@ -112,4 +113,20 @@ describe("mongodb > MongoRepository", () => {
loadedPosts[1].text.should.be.equal("Everything about post #2");

})));

it("should ignore non-column properties", () => Promise.all(connections.map(async connection => {
// Github issue #5321
const postRepository = connection.getMongoRepository(Post);

await postRepository.save({
title: "Hello",
text: "World",
unreal: "Not a Column"
});

const loadedPosts = await postRepository.find();

expect(loadedPosts).to.have.length(1);
expect(loadedPosts[0]).to.not.have.property("unreal");
})));
});

0 comments on commit b9a5071

Please sign in to comment.