The AdminBro Data model
Before you dive into the details of the flat helpers, let me briefly introduce the way how the database data is stored in AdminBro.
The Simple Case
On the backend all the information from DataBase tables/collections goes to {@BaseRecord#params}
object. Usually, it is a {key: value}
store where key
is a table name, and value could be
either a string, a number, or an instance of Date() etc.
On the Frontend - these values go to the corresponding RecordJSON.params property.
This is an example params
property in the "simple case":
params: {
name: 'John',
surname: 'Doe',
age: 28,
}
The Real World Case
In the real world:
- databases have nested JSONB properties or Mixed Schemas
- developers would like to send more complicated data from the Fronted to the backend as arrays.
To achieve that we "flatten" all the data before saving them to params
property.
So in the real world params could look like:
params: {
name: 'John',
surname: 'Doe',
age: 28,
// nested objects
'auth.facebook': 'some login token',
'auth.twitter': 'twitter login token',
// arrays
'interests.0': 'running',
'interests.1': 'jogging',
}
and all the data can be even nested on the deeper levels.
Why we did that?
An alternative solution would be to store an entire raw object and don't care. But there are 2 reasons why we picked this one.
1. storing selected data in the database
If you send to the ORM unflatten save request like this:
Model.save({{ auth: { facebook: 'newFacebookToken' } }})
it will override an entire auth
property, so from the example above, we will lose auth.twitter
.
In the second (flatten) case:
Model.save({ `auth.facebook`: 'newFacebookToken' }})
ORM should keep the value of the auth.twitter
The above is true for Mongoose adapter which is the most advanced regarding handing mixed values
2. Sending data between the Frontend and the Backend in FormData format
AdminBro allows you to upload Files from the Frontend to the Backend. The most optimal way of
doing that is by using FormData.
But, this requires that values for all the fields are send in [key: string]: string
form.
And this, as you might guess, fits perfectly to our flatten params
logic.
Consequences
Flattening in AdminBro has its consequences everywhere where you use
- BaseRecord and
- RecordJSON,
because instead of raw object you have it's flatten version.
Also, (as mentioned) the payload
send to the backed is also flattened.
There you should use helpers gathered in flat
- New:
- in version 3.3
View Source admin-bro/src/utils/flat/flat-module.ts, line 11
Methods
# static filterOutParams(params, properties) → {FlattenParams}
From all keys in params
it removes this passed in an argument.
Example
import flat from '@admin-bro'
const params = {
name: 'John',
'education.school.name': 'Harvard',
'education.school.id': 123,
}
flat.filterOutParams(params, 'education.school')
// results to {
// name: 'John',
// }
flat.filterOurParams(params, 'name')
// results to {
// 'education.school.name': 'Harvard',
// 'education.school.id': 123,
// }
Parameters:
Name | Type | Description |
---|---|---|
params |
FlattenParams | |
properties |
string | Array.<string> |
# static get(params, propertyPathopt, options) → {any}
Returns sub-property from the flatten params. When the property path is not given function returns an entire unflatten object.
Example
import flat from '@admin-bro'
const params = {
name: 'John',
'education.school.name': 'Harvard',
'education.school.id': 123,
}
const data = flat.get(params, 'education.school')
// results to {
// name: 'Harvard',
// id: 321,
// }
// value is undefined
const data = flat.get(params)
// results to {
// name: 'John',
// education: {
// school: {
// name: 'Harvard',
// id: 321,
// }
// }
// }
Parameters:
Name | Type | Attributes | Description |
---|---|---|---|
params |
FlattenParams | flatten params from which property has to be taken |
|
propertyPath |
string |
<optional> |
name of the property |
options |
GetOptions | options |
when property key exists directly it returns what is inside, otherwise it tries to find any nested objects and returns them
# static merge(params) → {FlattenParams}
Merges params together and returns flatten result
Parameters:
Name | Type | Description |
---|---|---|
params |
any | |
...mergeParams |
Array.<any> |
# static removePath(params, …properties) → {FlattenParams}
This method removes the given path from the flatten object.
Most of the time removing one path is a trivial thing but when it comes to arrays it is more complicated than just simply removing its key.
Usage
import { flat } from 'admin-bro'
// do something with flat.removePath
const paramsWithoutName = flat.removePath(otherParams, 'name.0')
Why it exists?
Take a look at this example:
having the flatten object
{
'property.0': 'val1',
'property.1': 'val2',
'property.2': 'val3',
}
you want to remove property.1
path. In order to do this you will need to remove key property.1
and rename property.2
to property.1
and take a look at this example:
{
name: 'value',
'notPopulated.0': 'val1',
'notPopulated.1': 'val2',
'property.0': 'val1',
'property.1': 'val2',
'property.2': 'val3',
'property.3.nested.0': 'val1',
'property.3.nested.1': 'val2',
'property.3.nested.2': 'val3',
'property.3.nested.3.some': 'val3',
'property.3.nested.4.some-other': 'val41',
'property.4': 'val4',
'property.5.nested.0': 'val5',
}
what should happen when you want to remove property.3.nested.3.some
?
This function solves these problems for you.
Parameters:
Name | Type | Attributes | Description |
---|---|---|---|
params |
FlattenParams | ||
properties |
string |
<repeatable> |
# static selectParams(params, properties, optionsopt) → {FlattenParams}
From all keys in params
it selects only those passed in arguments.
Example
import flat from '@admin-bro'
const params = {
name: 'John',
'education.school.name': 'Harvard',
'education.school.id': 123,
}
flat.selectParams(params, 'education.school')
// results to {
// 'education.school.name': 'Harvard',
// 'education.school.id': 123,
// }
flat.selectParams(params, 'education.school.id', 'name')
// results to {
// 'name': 'John',
// 'education.school.id': 123,
// }
Parameters:
Name | Type | Attributes | Description |
---|---|---|---|
params |
FlattenParams | ||
properties |
string | Array.<string> | ||
options |
GetOptions |
<optional> |
# static set(params, propertyPath, valueopt) → {FlattenParams}
Updates the flatten param object with a given value. Value can be anything and, this anything will
be flattened and added to params
.
params
is not mutated here.
Example
import flat from '@admin-bro'
const params = {
name: 'John',
'education.school.name': 'Harvard',
'education.school.id': 123,
}
const data = flat.set(params, 'education.shool', {
name: 'Yale',
id: 321,
})
// results to data === {
// name: 'John',
// 'education.school.name': 'Yale`,
// 'education.school.id': 321,
// }
// value is undefined
const data = flat.set(params, 'education') // results to data === { name: 'John' }
Parameters:
Name | Type | Attributes | Description |
---|---|---|---|
params |
FlattenParams | ||
propertyPath |
string | ||
value |
any |
<optional> |
if not give function will only try to remove old keys |
# pathToParts(propertyPath, options) → {PathParts}
the Long story short this method:
- changes:
nested.nested2.normalInner
- to
["nested", "nested.nested2", "nested.nested2.normalInner"]
So it can be used to search for the param in a FlattenParams object.
Formally it changes path in "flatten" notation, to an Array of all possible keys, which could have searched property.
When skipArrayIndexes
is set to true it also it takes care of the arrays, which are
separated by numbers (indexes). Then it:
- changes:
nested.0.normalInner.1
- to:
nested.normalInner
Everything because when we look for a property of a given path it can be inside a mixed property. So first, we have to find top-level mixed property, and then, step by step, find inside each of them.
Parameters:
Name | Type | Description |
---|---|---|
propertyPath |
string | |
options |
PathToPartsOptions |
Type Definitions
# FlattenValue
Available types for flatten values. This is an Union of types:
string
boolean
number
Date
null
[]
(empty array){}
(empty object)File
# GetOptions
Properties:
Name | Type | Attributes | Description |
---|---|---|---|
includeAllSiblings |
boolean |
<optional> |
Indicates if all the "less related" siblings should be included. This option takes care of,
fetching elements in nested arrays. Let's say you have keys: |
# PathToPartsOptions
Properties:
Name | Type | Attributes | Description |
---|---|---|---|
skipArrayIndexes |
boolean |
<optional> |
Indicates if array indexes should be skipped from the outcome. |