Sequelize custom validation - unable to access all fields in entity - javascript

I attempted to create a model in sequelize (say has 3 attributes, attrA, B, and C) with some custom validation logic. This tutorial helped me get most of it set up:
const Model = Sequelize.define('model', {
attrA: { type: Sequelize.STRING },
attrB: { type: Sequelize.STRING },
attrC: { type: Sequelize.STRING },
}, {
validate: {
someValidationLogic() {
// Do something with attrA,B,C
// if (this.attrA ... this.attrB ... this.attrC) throw new Error..
}
}
})
In the application logic however, only say, 2 out of the 3 attributes (A and B) need to be updated:
Model.update(
{
attrA: 'foo',
attrB: 'bar'
}, {
where: {
id: 1,
},
returning: true,
})
This results in that when the custom validation logic being called, in the this object accessed in the function, only attrA and attrB are defined in this, and attrC remained undefined. This causes the validation logic to fail because attrC cannot be read. Is there any way I can get the object visible from someValidationLogic() to have all attributes populated? Or should this "validation" shouldn't have been validation logic at all and should've been done on the application level?

Your validation logic could take in account the possibility of attrC not being defined :
validate: {
someValidationLogic() {
if (this.attrA !== 'undefined' && this.attrA === 'forbidden value' ) {
// throw new Error
}
}
}
But if your validation includes checking the provided values against current database values, then you would better handle this in the application layer : first recover the current database record, manipulate it as needed, then save it to database.

Related

Ajv library: Accessing keys in the nested schema validation javascript

I'm working on the use case where I need to validate the schema using ajv lib of the provided data which can be nested.
Now the problem is, Schema could change based on the value of a particular variable, which is not in the scope where this check is to be done.
how do I achieve it through ajv.
I tried using if-else & data & const but no luck.
I've recently encountered similar use case with ajv validator.
By looking at your issue I believe you need to access some other scope of the actual object passed for validation, In my case it was lying in the root of the object.
So I used the validate function in ajv's User Defined Keyword section, which in turn is giving me the whole object from top level in the first index of arguments itself, that way I accessed my dependent key in validate function and used it in my ajv IF: condition, eg
ajv.addKeyword({
keyword: "isRegular",
validate: (...test) => {
const test1 = test[1]
return test1.dependentKeyFromRootOfObject === "REGULAR"
},
})
and used the then created keyword isRegular in the nested object's IF: condition like
if: { isRegular: true },
then: {
properties: {
rcType: { type: "string" },
date: { type: "string" },
},
required: ["rcType", "date"],
additionalProperties: false,
},
else: {
properties: {
rcType: { type: "string" },
},
required: ["rcType"],
additionalProperties: false,
},
Hope this helps.

How can I limit the objects from a group in a query in Gatsby?

I have this query in my code which allows me to build a tag cloud for this blog front page
tagCloud:allContentfulBlogPost {
group(field: tags, limit: 8) {
fieldValue
}
}
It's passing data that I map in my component using {data.tagCloud.group.map(tag => (...))};. The code works nicely, but it won't be limited by the filter I'm passing above in the group(fields: tags, limit: 8) in my query. It renders all the tags and not only the first eight.
I've unsuccessfully tried the skip filter as well for the sake of seeing if it works.
Is this the proper way to limit the count to my mapping component in Gatsby?
The Contentful source plugin doesn't define arguments on any of the nodes it creates, unfortunately. Instead you would need to create these yourself. The easiest way to do that is through the createResolvers API.
Here's a similar example from a project of mine:
// in gatsby-node.js
exports.createResolvers = ({ createResolvers }) => {
createResolvers({
SourceArticleCollection: {
// Add articles from the selected section(s)
articles: {
type: ["SourceArticle"],
args: {
// here's where the `limit` argument is added
limit: {
type: "Int",
},
},
resolve: async (source, args, context, info) => {
// this function just needs to return the data for the field;
// in this case, I'm able to fetch a list of the top-level
// entries that match a particular condition, but in your case
// you might want to instead use the existing data in your
// `source` and just slice it in JS.
const articles = await context.nodeModel.runQuery({
query: {
filter: {
category: {
section: {
id: {
in: source.sections.map((s) => s._ref),
},
},
},
},
},
type: "SourceArticle",
})
return (articles || []).slice(0, args.limit || source.limit || 20)
},
},
},
})
}
Because resolvers run as part of the data-fetching routines that support the GraphQL API, this will run server-side at build-time and only the truncated/prepared data will be sent down to the client at request time.

What is the best way to keep track of changes of a document's property in MongoDB?

I would like to know how to keep track of the values of a document in MongoDB.
It's a MongoDB Database with a Node and Express backend.
Say I have a document, which is part of the Patients collection.
{
"_id": "4k2lK49938d82kL",
"firstName": "John",
"objective": "Burn fat"
}
Then I edit the "objective" property, so the document results like this:
{
"_id": "4k2lK49938d82kL",
"firstName": "John",
"objective": "Gain muscle"
}
What's the best/most efficient way to keep track of that change? In other words, I would like to know that the "objective" property had the value "Burn fat" in the past, and access it in the future.
Thanks a lot!
Maintaining/tracking history in the same document is not all recommended. As the document size will keep on increasing leading to
probably if there are too many updates, 16mb document size limit
Performance degrades
Instead, you should maintain a separate collection for history. You might have use hibernates' Javers or envers for auditing for your relational databases. if not you can check how they work. A separate table (xyz_AUD) is maintained for each table (xyz). For each row (with primary key abc) in xyz table, there exist multiple rows in xyz_AUD table, where each row is version of that row.
Moreover, Javers also support MongoDB auditing. If you are using java you can directly use it. No need to write your own logic.
Refer - https://nullbeans.com/auditing-using-spring-boot-mongodb-and-javers/
One more thing, Javers Envers Hibernate are java libraries. But I'm sure for other programming languages also, similar libraries will be present.
There is a mongoose plugin as well -
https://www.npmjs.com/package/mongoose-audit (quite oudated 4 years)
https://github.com/nassor/mongoose-history#readme (better)
Maybe you can change the type of "objective" to array and track the changes in it. the last one of the array is the latest value.
Maintain it as a sub-document like below
{
"_id": "4k2lK49938d82kL",
"firstName": "John",
"objective": {
obj1: "Gain muscle",
obj2: "Burn fat"
}
}
You can also maintain it as an array field but remember, mongodb doesn't allow you to maintain uniqueness in an array field and if you plan to index the "objective" field, you'll have to create a multi key index
I think the simplest solution would be to use and update an array:
const patientSchema = new Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
objective: { type: String, required: true }
notes: [{
date: { type: Date, default: Date.now() },
note: { type: String, required: true }
}],
});
Then when you want to update the objective...
const updatePatientObjective = async (req, res) => {
try {
// check if _id and new objective exist in req.body
const { _id, objective, date } = req.body;
if (!_id || !objective) throw "Unable to update patient's objective.";
// make sure provided _id is valid
const existingPatient = await Patient.findOne({ _id });
if (!existingPatient) throw "Unable to locate that patient.";
// pull out objective as previousObjective
const { objective: previousObjective } = existingPatient;
// update patient's objective while pushing
// the previous objective into the notes sub document
await existingPatient.updateOne({
// update current objective
$set { objective },
// push an object with a date and note (previouseObjective)
// into a notes array
$push: {
notes: {
date,
note: previousObjective
},
},
}),
);
// send back response
res
.status(201)
.json({ message: "Successfully updated your objective!" });
} catch (err) {
return res.status(400).json({ err: err.toString() });
}
};
Document will look like:
firstName: "John",
lastName: "Smith",
objective: "Lose body fat.",
notes: [
{
date: 2019-07-19T17:45:43-07:00,
note: "Gain muscle".
},
{
date: 2019-08-09T12:00:38-07:00,
note: "Work on cardio."
}
{
date: 2019-08-29T19:00:38-07:00,
note: "Become a fullstack web developer."
}
...etc
]
Alternatively, if you're worried about document size, then create a separate schema for patient history and reference the user's id (or just store the patient's _id as a string instead of referencing an ObjectId, whichever you prefer):
const patientHistorySchema = new Schema({
_id: { type: Schema.Types.ObjectId, ref: "Patient", required: true },
objective: { type: String, required: true }
});
Then create a new patient history document when the objective is updated...
PatientHistory.create({ _id, objective: previousObjective });
And if you need to access to the patient history documents...
PatientHistory.find({ _id });

How to .update() value to NULL in sequelize

I'm writing my service to update a row using sequelize for PostGres. When I try out my query using a PSequel it works fine:
UPDATE "test_table" SET "test_col"=NULL WHERE "id"= '2'
But using sequelize it throws a 500 error:
db.TestTable.update({ testCol: NULL }, { where: { id: id } })
.then((count) => {
if (count) {
return count;
}
});
My model does allowNull which I believe is what allows null values to be the default as well as set:
testCol: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
field: 'test_col'
},
Any other value but NULL works as expected. Is there a different method for setting null values?
From the looks of it, I think your issue is that you are using SQL's syntax for a null value ('NULL') where you should be using JS syntax ('null').
db.TestTable.update({ testCol: null }, { where: { id: id } })
.then((count) => {
if (count) {
return count;
}
});
should work.
Have you checked a more detailed error message in logs? I'd suggest you to add a promise catching error and then update your question.
For now, my guess is that you created your connection with omitNull: true. Call an update function with just one null property probably is the reason of error 500 because it'll generate a incomplete UPDATE command (without SET).
Try to set omitNull: false or, if you cannot do this test, try to update this way:
db.TestTable.testCol = null;
db.TestTable.save(['testCol']);
More info here.

how to write validation in sencha touch / ext js model

I have a model like this
Ext.define('app.model.TeamEmpAssignment', {
extend : 'Ext.data.Model',
config : {
idProperty : 'teamEmpId',
fields : [{
name : 'teamEmpId',
type : 'int'
}, {
name : 'actName'
}, {
name : 'beginDateTime'
}, {
name : 'endDateTime'
},
],
validations : [{
field : 'beginDateTime',
type : 'presence',
message : 'Effective Begin Date Time required'
}, {
field : 'endDateTime',
type : 'presence',
message : 'Effective End Date Time required'
},
],
}
});
I have to write a validation to compare that endDateTime > startDateTime
I am trying sencha touch 2.3.1
I know this is an old question, but I ran into it while dealing with the same problem and wanted to share my approach.
I created a method in the model, which does the custom validation, something like:
Ext.define('app.model.TeamEmpAssignment', {
extend : 'Ext.data.Model',
config : {
// config here
},
checkDates: function (errors) {
// assuming the dates are timestamps
if (this.get('beginDateTime') > this.get('endDateTime')) {
errors.add(Ext.create('Ext.data.Error', {
field : 'beginDateTime',
message: 'Begin date can\'t be after end date'
}));
}
}
});
You will notice that checkDates has one argument - errors. This argument is actually the Ext.data.Errors object return by the validate method of the model. So we can do something like:
var record = Ext.create('Ext.data.Errors');
// set some values here
var errors = record.validate();
record.checkDates(errors);
console.log(errors.isValid());
We can use isValid, because all it does is check whether the Ext.data.Errors collection has items.
I am currently using it like this, however it will be quite easy to override the validate method, so you don't have to call the custom validation externaly.
Ext.define('app.model.TeamEmpAssignment', {
extend : 'Ext.data.Model',
config : {
// config here
},
checkDates: function (errors) {
// assuming the dates are timestamps
if (this.get('beginDateTime') > this.get('endDateTime')) {
errors.add(Ext.create('Ext.data.Error', {
field : 'beginDateTime',
message: 'Begin date can\'t be after end date'
}));
}
},
validate: function () {
var errors = this.callParent(arguments);
this.checkDates(errors);
return errors;
}
});
The validation types come from the Ext.data.validations singleton. You can add your own validators here, however the method does not supply the all the fields in the model (which it sounds like you will need). We would advise moving your validators to a different spot (outside the model), maybe on the related form component (after a load or before a save).

Categories