Skip to content
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

Save and find nested entities #37

Merged
merged 27 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d62cb04
feat(datamapper): enabling nested entity saving with improved null an…
vitorgamer58 Jul 10, 2023
5871b7b
test(datamapper.test): adds tests to enforce rules regarding nested e…
vitorgamer58 Jul 10, 2023
68dfcf0
feat(datamapper): enable find of nested entities
vitorgamer58 Jul 10, 2023
8d4b6b2
test(datamapper.test): validate toEntity with nested entities
vitorgamer58 Jul 10, 2023
2453cb7
fix(datamapper): improved empty object checking in arrayDataParse
vitorgamer58 Jul 10, 2023
e34049e
feat(herbs2mongo): enable export of DataMapper class
vitorgamer58 Jul 10, 2023
e9c006e
test: adjust array type return rule when value is empty
vitorgamer58 Jul 10, 2023
4f460ae
feat(datamapper): enable find of nested entities
vitorgamer58 Jul 12, 2023
9818b6b
test(findbyid): add test to validate nested entity
vitorgamer58 Jul 12, 2023
16d2e54
refactor(datamapper): field transformation and filter of null and und…
vitorgamer58 Aug 19, 2023
8a4e05d
refactor(datamapper): refine parsing for entity processing
vitorgamer58 Aug 19, 2023
6190f0b
feat(datamapper): adds recursion and childrenm key in field mapping
vitorgamer58 Aug 20, 2023
6418b2b
feat(datamapper): adds recursion to work with nested entities
vitorgamer58 Aug 20, 2023
00f598e
test(datamapper.test): adds tests with multi-level nested entities
vitorgamer58 Aug 20, 2023
636c9ab
feat(datamapper): recursive parse of entities from collection to entity
vitorgamer58 Aug 20, 2023
b56dd30
feat(datamapper): add recursive entity array parser
vitorgamer58 Aug 20, 2023
9830cdc
feat: adjust nested entity array compatibility
vitorgamer58 Aug 20, 2023
2acfe31
test(findbyid): coverage of more nested entity scenarios
vitorgamer58 Aug 20, 2023
135587d
refactor(datamapper): refactor ifs to use ternary if
vitorgamer58 Aug 20, 2023
b87c6b1
test(datamapper.test): add more test cases
vitorgamer58 Aug 20, 2023
f94144e
refactor(datamapper): remove unused code
vitorgamer58 Aug 20, 2023
66b91af
test(datamapper.test): add null nested entity test case
vitorgamer58 Aug 20, 2023
ac90924
refactor(datamapper): remove if useless
vitorgamer58 Aug 20, 2023
75d91d0
style(datamapper): code formatting and styling
vitorgamer58 Aug 20, 2023
09a3740
test(datamapper.test): use new to create GreatGreatGrandChildEntity
vitorgamer58 Aug 20, 2023
3896da7
test(datamapper): fix unit test
vitorgamer58 Aug 20, 2023
1122dd1
feat: adjust entity checking when entityField.type is an array
vitorgamer58 Aug 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 133 additions & 11 deletions src/dataMapper.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const Convention = require('./convention')
const { entity } = require('@herbsjs/herbs')
const { entity, checker } = require('@herbsjs/herbs')
const dependency = { convention: Convention }

class DataMapper {
Expand Down Expand Up @@ -34,14 +34,27 @@ class DataMapper {

const fields = Object.keys(schema)
.map((field) => {
if (typeof schema[field] === 'function') return { type: Function }
if (typeof schema[field] === 'function') return null

const isArray = Array.isArray(schema[field].type)
const type = fieldType(schema[field].type)
const isEntity = entity.isEntity(type)
const nameDb = convention.toCollectionFieldName(field)

const isID = entityIDs.includes(field)
return { name: field, type, isEntity, nameDb, isArray, isID }

const object = { name: field, type, isEntity, nameDb, isArray, isID }

if (isEntity) {
const entitySchema = isArray
? schema[field].type[0].prototype.meta.schema
: schema[field].type.prototype.meta.schema
object.children = this.buildAllFields(entitySchema, [], convention)
}

return object
})
.filter(Boolean)

const allFields = fields.filter((f) => f.type !== Function)

Expand All @@ -58,16 +71,56 @@ class DataMapper {

collectionFields() {
return this.allFields
.filter((i) => !i.isEntity)
.map((i) => i.nameDb)
}

isNotNullOrUndefined(field, instance) {
if (instance[field.name] === null || instance[field.name] === undefined) return false
return true
}

transformField(field, instance) {
if (field.isEntity) {
return { [field.nameDb]: this.parseEntity(field, instance[field.name]) }
}

return { [field.nameDb]: instance[field.name] }
}

parseEntity(field, value) {
if (field.isArray && checker.isArray(value)) {
const parsedArray = value.map(item => this.parseEntity(field, item))
return parsedArray.reduce((acc, curr, index) => {
acc[index] = curr
return acc
}, {})
}

const parsedEntity = Object.keys(value).reduce((acc, key) => {
if (value[key] === null || value[key] === undefined) return acc

const childField = field.children.find((i) => i.name === key)

if (childField?.isEntity) {
acc[childField.nameDb] = this.parseEntity(childField, value[key])

return acc
}

acc[childField.nameDb] = value[key]

return acc
}, {})

return parsedEntity
}

collectionFieldsWithValue(instance) {

let collectionFields = this.allFields
.filter((i) => !i.isEntity)
.map(i => ({ [i.nameDb]: instance[i.name] }))
.reduce((x, y) => ({ ...x, ...y }))
.filter((field) => this.isNotNullOrUndefined(field, instance))
.map((field) => this.transformField(field, instance))
.reduce((acc, current) => ({ ...acc, ...current }), {})

if (instance.id === undefined) {
delete instance.id
Expand All @@ -83,9 +136,9 @@ class DataMapper {

buildProxy() {

function getDataParser(type, isArray) {
function getDataParser(type, isArray, isArrayOfEntities, field) {
function arrayDataParser(value, parser) {
if (value === null) return null
if (checker.isEmpty(value)) return null
return value.map((i) => parser(i))
}

Expand All @@ -94,11 +147,34 @@ class DataMapper {
return parser(value)
}

if (isArray) {
if (isArray && !isArrayOfEntities) {
const parser = getDataParser(type, false)
return (value) => arrayDataParser(value, parser)
}

if (isArrayOfEntities) {
return (value) => {
if (checker.isEmpty(value)) return null
return value?.map((item) => {
const object = Object.keys(item).reduce((obj, key) => {
const childField = field?.children.find((i) => i.nameDb === key)

if (childField.isEntity) {
obj[childField.name] = processEntity(childField, item)

return obj
}

const parser = getDataParser(field.type, false)
obj[childField.name] = parser(item[childField.nameDb])

return obj
}, {})
return object
})
}
}

if ((type === Date) || (!convention.isScalarType(type)))
return (x) => x

Expand All @@ -108,6 +184,44 @@ class DataMapper {
const convention = this.convention
const proxy = {}

function processEntity(field, payload) {
const entityValue = payload[field.nameDb]

if (checker.isEmpty(entityValue)) return undefined

const object = field.type.schema.fields.reduce((obj, entityField) => {
const fieldNameDb = convention.toCollectionFieldName(entityField.name)

const typeIsArray = checker.isArray(entityField.type)

const isEntity = typeIsArray
? entity.isEntity(entityField.type[0])
: entity.isEntity(entityField.type)

if (isEntity) {
const childField = field?.children.find((i) => i.name === entityField.name)

if (childField.isArray) {
const arrayOfEntityParser = getDataParser(childField.type, childField.isArray, childField.isEntity, childField)
obj[entityField.name] = arrayOfEntityParser(payload[field.nameDb][fieldNameDb])

return obj
}

obj[entityField.name] = processEntity(childField, payload[field.nameDb])

return obj
}

const fieldParser = getDataParser(entityField.type, Array.isArray(entityField.type))

obj[entityField.name] = fieldParser(payload[field.nameDb][fieldNameDb])
return obj
}, {})

return object
}

Object.defineProperty(proxy, '_payload', {
enumerable: false,
wricollection: true,
Expand All @@ -126,7 +240,15 @@ class DataMapper {
Object.defineProperty(proxy, field.name, {
enumerable: true,
get: function () {
if (field.isEntity) return undefined
if (field.isEntity && !field.isArray) {
return processEntity(field, this._payload)
}

if (field.isEntity && field.isArray) {
const arrayOfEntityParser = getDataParser(field.type, field.isArray, field.isEntity, field)
return arrayOfEntityParser(this._payload[nameDb])
}

return parser(this._payload[nameDb])
}
})
Expand Down
3 changes: 2 additions & 1 deletion src/herbs2mongo.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const Repository = require('./repository')
const DataMapper = require('./dataMapper')

module.exports = { Repository }
module.exports = { Repository, DataMapper }
Loading
Loading