So I am mapping database fields onto response fields so I do not expose a data layer to consumers using the field
property on the SequelizeJS models like:
module.exports = function (sequelize, DataTypes)
{
return sequelize.define('Product',
{
id:
{
type: DataTypes.INTEGER,
primaryKey: true,
field: 'sku_id'
},
name:
{
type: DataTypes.STRING,
field: 'sku_name'
},
code:
{
type: DataTypes.STRING,
field: 'sku_code'
}
},
{
timestamps: false,
freezeTableName: true
});
};
The route controller then looks like:
module.exports = server =>
{
const Joi = require('joi');
const db = require('rfr')('models/index');
const _ = require('lodash');
const schema = Joi.array()
.items(Joi.object(
{
id: Joi.number().integer().positive().required().description('The id of the product'),
name: Joi.string().required().description('The name of the product'),
code: Joi.string().required().description('The code of the product')
})
.label('Product'))
.label('ProductCollection');
return server.route(
{
method: 'GET',
path: '/products/group/{id}',
config:
{
handler,
validate:
{
params:
{
id: Joi.number().integer().positive().required().description('the product group id')
}
},
response:
{
schema,
modify: true,
options: { stripUnknown: true }
},
tags: ['api', 'products'],
description: 'Get a list of products by group'
}
});
////////////////////////
function handler(request, reply)
{
var sql = db.sequelize;
var options =
{
replacements:
{
id: request.params.id
},
model: db.Product,
mapToModel: true,
type: sql.QueryTypes.SELECT
};
sql.query('sp_GetProducts @RowId = :id', options)
.then(results =>
{
let sorted = _.orderBy(results, 'code');
return reply(sorted);
})
.catch(err => reply(require('boom').wrap(err)));
}
};
The challenge is that:
- Not having
modify: true
andstripUnknown: true
on the response causes validation errors due to all the private properties on the SequelizeJS model, like; _changed, _options, _previousDataValues, etc - To address #1 and still leaving off the
modify: true
andstripUnknown: true
values, we could addunknown(true)
to the Joi validation ... but then all public properties (not the private ones listed above in #1) are included in the response as the validation is allowing them and we are not stripping them - If we remove
unknown(true)
from the Joi validation and add themodify: true
andstripUnknown: true
properties (like the code above shows) then an error is thrown as the private properties (and the public) ones are being stripped from the Sequelize model ... so the model is bugging out as it expects these to exist
Therefore, rather than manually mapping database objects to response objects, the only way past this I can see is to:
- Set
unknown(true)
on the validation - Implement some sort of global handler (or per route handler) that will do the stripping after the validation has been done and the response is about to be sent
I am not sure what the correct extension point in #2 above would be, or if there is a neater approach to achieving the desired result.
or using something like map-obj and have the handler interrogate the Joi schema to determine if the source object (model) key => value
should be copied to the new (response) object, I feel that this is just more overhead (cpu cycles and expensive for large arrays of objects) as opposed to letting Sequelize do it as it builds the model.
EDIT
This is the util function I created to acheive the mapping @Shivan suggested. Which utilises the object-mapper package.
function mapFromModel(data, model)
{
if (!data) { return []; }
if (_.isArray(data) === false) { data = [data]; }
if (!model.attributes && !model.sequelize) { throw new Error('Expecting `model` argument to be a sequelize model.'); }
let transform = {};
Object
.keys(model.attributes)
.forEach(key =>
{
let obj = model.attributes[key];
transform[obj.field] = `${obj.fieldName}?`;
});
return data.map(value => objectMapper(value, {}, transform));
}
My use case here is that I use the model to define the definition of what I want the object to look like and then this function does the mapping, which also modifies property names based on the field property in the model for each data field. This waY I can also easily build a model from the object this function returns if I need to re-use this object for updates/deletes, which I don't see being needed in the near future.
You seem to be using
raw
property on the query Which flattens the resultCan you try without it.