-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
fix: better mergeDeep implementation and tests #5097
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
I guess the tests pass, but I'm a bit worried about edge cases, maybe something like passing a class instance to an |
|
Tests can easily pass because we don't have a large test coverage for monogdb driver. I think your changes won't work properly because in the cases when embedded objects (nested objects) are used, we actually need class instances, we can't skip them. Solution to this issue is to rewrite current way of "copying object" - traverse over defined columns in the class and all its embeddeds and copy only object properties that are defined in these columns. Columns definition tells us what schema should be used for saving and this schema can't be recursive. |
|
@pleerock Ok, thanks! I will work some more on this then. I will add more tests regarding having nested embedded objects and objects with more information than the schema allows (should we throw an error or something if we detect that, or just ignore?). I will work on making a new function to transform nested entities into plain objects using schema (probably using |
@pleerock I was looking into this again, and it looks like there is a test for this, and it passed: typeorm/test/functional/mongodb/basic/embedded-columns/mongodb-embedded-columns.ts Lines 134 to 158 in 4307a59
So I don't know exactly how I could break this, since I saw that it passed that, it looks to me to be pretty solid (even though it really doesn't look like it). |
|
@pleerock is this ever going to get merged any time soon? |
imnotjames
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you rebase & fix the conflict?
| // for mongodb we have a bit different updation logic | ||
| if (this.queryRunner instanceof MongoQueryRunner) { | ||
| const partialEntity = OrmUtils.mergeDeep({}, subject.entity!); | ||
| const partialEntity = OrmUtils.mergeDeep({}, { ...subject.entity! }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why shallow cloning the object here? Doesn't this code that already?
| static isObject(item: any) { | ||
| return (item && typeof item === "object" && !Array.isArray(item)); | ||
| // Checks if it's an object made by Object.create(null), {} or new Object() | ||
| static isPlainObject(item: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're dropping isObject can you make this private? To reduce the public API footprint.
| @Column({ type: String, transformer: new ComplexTransformer(), nullable: true }) | ||
| complex: Complex | null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to add this to a different entity in a different entity file?
That way we're not muddying up the original tests
| describe("OrmUtils.isPlainObject", () => { | ||
| const isPlainObject = OrmUtils.isPlainObject.bind(OrmUtils); | ||
|
|
||
| it("should return `true` if the Object is made by Object.create(null), {} or new Object().", () => { | ||
| class Foo {} | ||
|
|
||
| expect(isPlainObject(new Object())).to.be.true; | ||
| expect(isPlainObject(Object.create(null))).to.be.true; | ||
| expect(isPlainObject(Object.create(Object.prototype))).to.be.true; | ||
| expect(isPlainObject({ foo: "bar" })).to.be.true; | ||
| expect(isPlainObject({ constructor: "foo" })).to.be.true; | ||
| expect(isPlainObject({ constructor: Foo })).to.be.true; | ||
| expect(isPlainObject({})); | ||
| }); | ||
|
|
||
| it("should return `false` if the Object is not made by Object.create(null), {} or new Object().", () => { | ||
| class Foo {} | ||
|
|
||
| expect(isPlainObject(Object.create({}))).to.be.false; | ||
| expect(isPlainObject(Object.create(Object))).to.be.false; | ||
| expect(isPlainObject(/foo/)).to.be.false; | ||
| expect(isPlainObject(function() {})).to.be.false; | ||
| expect(isPlainObject(() => {})).to.be.false; | ||
| expect(isPlainObject(["foo", "bar"])).to.be.false; | ||
| expect(isPlainObject([])).to.be.false; | ||
| expect(isPlainObject(new Foo())).to.be.false; | ||
| expect(isPlainObject(null)).to.be.false; | ||
| expect(isPlainObject(Math)).to.be.false; | ||
| expect(isPlainObject(Error)).to.be.false; | ||
| expect(isPlainObject(0)).to.be.false; | ||
| expect(isPlainObject(false)).to.be.false; | ||
| expect(isPlainObject(NaN)).to.be.false; | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we're doing all the tests below - do we need to test isPlainObject if it's just an internal helper? Do the below tests not go through all the possible permutations?
The current mergeDeep implementation tries to recursively copy complex objects like class instances. So, a new implementation of
mergeDeepandisPlainObject(previouslyisObject) is given. This fixes #5096, which demonstrates the problem.As a bonus, this creates tests for these methods which weren't tested before.