I've got a problem with the sequelize hooks. The following code does its job very well when i create a new instance. But when it comes to updating an existent database entry the values of rec.working_time and rec.break_duration are not wirtten back to the database
I can see that the hook is called by adding some console logs and the rec values change as well. But somhow there is no update on theese fields.
If i do the same with an 'beforeUpdate' everything works fine with the same code.
TimeRecording.hook('afterValidate', rec => {
var moment = require('moment');
var models = require('../models');
let workingTime = moment(rec.time_to).diff(rec.time_from, 'minutes', null);
let workingHours = workingTime / 60;
return models.BreakControl.max('break_duration',
{
where: { working_time: { lt: workingHours } }
})
.then(breakControl => {
breakControl = breakControl ? breakControl : 0;
rec.working_time = workingTime - breakControl;
rec.break_duration = breakControl;
})
.catch(err => console.error(err));
});
Here also a quck look on the important attributes in the model:
time_from: {
type: DataTypes.DATE,
allowNull: false,
},
time_to: {
type: DataTypes.DATE,
validate: {
isAfter: function (value, next) {
let self = this;
if (self.time_from > value) {
return next('time_to must be after time_from!');
}
return next();
}
}
},
required_time: {
type: DataTypes.DECIMAL(4, 0)
},
working_time: {
type: DataTypes.DECIMAL(4, 0),
validate: {
min: 0
}
},
break_duration: {
type: DataTypes.DECIMAL(4, 0)
}
So am i missing something, or did i fail to understand some critical concept here?
Thanks for any information on this.
Related
I try to update a record, following the documentation here . But After I check the record it is not updated. I filter the object correctly but when I try to set hasSentEntry to a boolean it seems to not affect it.
If I try to update with the second method( Upsert an Object in docs ). The program crashes and closes before I could print the error. How can I solve this? I can't understand the reason.
Here is the schema:
const AttendanceSchema = {
name: ATTENDANCE_SCHEMA,
primaryKey: "_id",
properties: {
_id: "objectId",
KartId: "string?",
entryTime: "string?",
exitTime: "string?",
photo: "string?",
hasSentEntry: "bool?",
hasSentExit: "bool?",
},
};
Here is the database options with migration:
const databaseOptions = {
path: "/var/lib/realm/local.realm",
schema: [
AttendanceSchema,
],
schemaVersion: 2,
migration: (oldRealm, newRealm) => {
if (oldRealm.schemaVersion < 3) {
const oldSchema = oldRealm.objects(ATTENDANCE_SCHEMA);
const newSchema = newRealm.objects(ATTENDANCE_SCHEMA);
newSchema.hasSentEntry = oldSchema?.hasSentEntry ? true : false;
newSchema.hasSentExit = oldSchema?.hasSentExit ? true : false;
}
},
};
How I try to update it:
realm.write(() => {
const attendanceData = realm
.objects("Attendance")
.filtered("photo=='" + filePath + "'");
attendanceData.hasSentEntry = true;
});
This is the upset version:
delete attendance.hasSentEntry;
try {
const modifiedAttendance = realm.create(
"Attendance",
{ ...attendance, hasSentEntry: true },
"modified"
);
} catch (e) {
console.log("Error??", e);
}
I'm unable to perform any kind of upsert or create within Sequelize (v: 6.9.0, PostGres dialect).
Using out-of-the-box id as PK, with a unique constraint on the name field. I've disabled timestamps because I don't need them, and upsert was complaining about them. I've tried manually defining the PK id, and allowing Sequelize to magically create it. Here's the current definition:
const schema = {
name: {
unique: true,
allowNull: false,
type: DataTypes.STRING,
}
};
class Pet extends Model { }
Pet.define = () => Pet.init(schema, { sequelize }, { timestamps: false });
Pet.buildCreate = (params) => new Promise((resolve, reject) => {
let options = {
defaults: params
, where: {
name: params.name
}
, returning: true
}
Pet.upsert(options)
.then((instance) => {
resolve(instance);
})
.catch(e => {
// message:'Cannot read property 'createdAt' of undefined'
console.log(`ERROR: ${e.message || e}`);
reject(e);
});
});
module.exports = Pet;
Upsert code:
// handled in separate async method, including here for clarity
sequelize.sync();
// later in code, after db sync
Pet.buildCreate({ name: 'Fido' });
In debugging, the options appear correct:
{
defaults: {
name: 'Fido'
},
returning:true,
where: {
name: 'Fido'
}
}
I've also tried findOrCreate and findCreateFind, they all return errors with variations of Cannot convert undefined or null to object.
I've tried including id: null with the params, exact same results.
The only way I've succeeded is by providing PK in the params, but that is clearly not scalable.
How can I upsert a Model instance without providing a PK id in params?
class Pet extends Model { }
//...you might have the id for the pet from other sources..call it petId
const aPet = Pet.findCreateFind({where: {id: petId}});
aPet.attribute1 = 'xyz';
aPet.attribute2 = 42;
aPet.save();
I am having trouble figuring out if I designed the schema correctly because I am receiving a 500 error when attempting to PATCH changes of the roles property from a profile. (Note: The 500 error just responds with an empty {}, so it isn't really informative)
Below is the profile schema:
var ProfileSchema = new Schema({
name: {
type: String,
required: true
},
roles: [{
application: {
type: Schema.Types.ObjectId,
required: true,
ref: 'Application'
},
role: {
type: String,
required: true,
enum: [ 'admin', 'author', 'publisher' ]
}
}]
});
Each profile has a role for an application, and when I send the request to the controller action 'update', it fails:
profile update controller:
// Updates an existing Profile in the DB
export function update(req, res) {
try {
if (req.body._id) {
delete req.body._id;
}
console.log('ENDPOINT HIT...');
console.log(`REQUEST PARAM ID: ${req.params.id}`);
console.log('REQUEST BODY:');
console.log(req.body);
console.log('ENTIRE REQUEST: ');
return Profile.findByIdAsync(req.params.id)
.then(handleEntityNotFound(res))
.then(saveUpdates(req.body))
.then(respondWithResult(res))
.catch(handleError(res));
} catch(ex) {
console.error('FAILED TO UPDATE PROFILE');
return handleError(res);
}
}
I made sure that the id and body was being sent properly, and I am hitting the end point.
This is an example of the request body JSON:
{
_id: 57e58ad2781fd340563e29ff,
__updated: Thu Oct 27 2016 15:41:12 GMT-0400 (EDT),
__created: Fri Sep 23 2016 16:04:34 GMT-0400 (EDT),
name: 'test',
__v: 11,
roles:[
{ application: 57b70937c4b9fe460a235375,
role: 'admin',
_id: 58125858a36bd76d8111ba16 },
{ application: 581b299f0145b48adf8f57bd,
role: 'publisher',
_id: 581b481898eefb19ed8a73ee }
]
}
When I try to find the Profile by Id, the promise chain goes straight to the catch(handleError(res)); part of the code and shows an empty object in my console.
My handle error function:
function handleError(res, statusCode) {
console.error('HANDLE PROFILE ERROR: ', statusCode);
statusCode = statusCode || 500;
return function(err) {
console.error('PROFILE ERROR:');
console.error(JSON.stringify(err, null, 2));
res.status(statusCode).send(err);
};
}
UPDATE
I am realizing the code is breaking when it hits my saveUpdates function (Note: I am using lodash):
function saveUpdates(updates) {
/// the code is fine here ///
return function(entity) {
/// once it enters in here, is where it begins to break ///
var updated = _.merge(entity, updates);
if(updated.roles.length != updates.roles.length) {
updated.roles = updates.roles;
}
for(var i in updates.roles) {
updated.roles.set(i, updates.roles[i]);
}
return updated.saveAsync()
.then(updated => {
return updated;
});
};
}
Lesson learned: Read Documentation properly.
Since I am using bluebird promises for this application, I forgot to use .spread() within my saveUpdates() callback function.
Solution:
function saveUpdates(updates) {
return function(entity) {
var updated = _.merge(entity, updates);
if(updated.roles.length != updates.roles.length) {
updated.roles = updates.roles;
}
for(var i in updates.roles) {
updated.roles.set(i, updates.roles[i]);
}
return updated.saveAsync()
// use `.spread()` and not `.then()` //
.spread(updated => {
return updated;
});
};
}
I want to thank the following SOA that led to this conclusion: https://stackoverflow.com/a/25799076/5994486
Also, here is the link to the bluebird documentation in case anyone was curious on .spread(): http://bluebirdjs.com/docs/api/spread.html
I am building kind of multitenancy using sequelize.js. Technically I need to filter all queries by predefined column and dynamic value of the current context. General idea was to use defaultScope to filter out other contexts, something like:
var context = () => { return "some current context id"; }
connection.define('kid', {
firstName: Sequelize.STRING,
photoUrl: Sequelize.STRING,
context: {
type: Sequelize.STRING,
defaultValue: context // this part works, it accepts function
}
}, {
defaultScope: {
where: {
context: context // this does not work, it does not accept function and values is defined only once
}
}
});
However this does not work because defaultScope is defined on the application start.
What is the right way to do this?
The problem is that Sequelize scopes are defined on the model but you need to apply the scope just before the query because that's when you have context such as the user and role.
Here's a slightly modified copy of the scope merge function from Sequelize which you can use in your hooks such as beforeFind()
// Feel free to write a more fp version; mutations stink.
const {assign, assignWith} = require('lodash')
const applyScope = ({scope, options}) => {
if (!scope) {
throw new Error('Invalid scope.')
}
if (!options) {
throw new Error('Invalid options.')
}
assignWith(options, scope, (objectValue, sourceValue, key) => {
if (key === 'where') {
if (Array.isArray(sourceValue)) {
return sourceValue
}
return assign(objectValue || {}, sourceValue)
}
else if (['attributes', 'include'].indexOf(key) >= 0
&& Array.isArray(objectValue)
&& Array.isArray(sourceValue)
) {
return objectValue.concat(sourceValue)
}
return objectValue ? objectValue : sourceValue
})
}
In your model:
{
hooks: {
beforeFind(options) {
// Mutates options...
applyScope({
scope: this.options.scopes.user(options.user)
, options
})
return options
}
}
, scopes: {
user(user) {
// Set the scope based on user/role.
return {
where: {
id: user.id
}
}
}
}
}
Finally in your query, set an option with the context that you need.
const user = {id: 12, role: 'admin'}
YourModel.findOne({
attributes: [
'id'
]
, where: {
status: 'enabled'
}
, user
})
I'm not sure it will help, but you can override a model default scope anytime.
let defaultScope = {
where: {
context: ""
}
};
defaultScope.where.context = context();
model.addScope('defaultScope',defaultScope,{override: true});
Maybe too late here but scopes can take arguments if defined as functions. From documentation Sequelize scope docs if the scope is defined as
scopes: {
accessLevel (value) {
return {
where: {
accessLevel: {
[Op.gte]: value
}
}
}
}
sequelize,
modelName: 'project'
}
you can use it like: Project.scope({ method: ['accessLevel', 19]}).findAll(); where 19 is the dynamic value the scope will use.
As per defaultScope I'm not sure it can be defined as a function
Is it possible to have an asynchronous validator using Sequelize.js? I want to check for the existence of an association before saving a model. Something like this:
User = db.define("user", {
name: Sequelize.STRING
},
{
validate:
hasDevice: ->
#getDevices().success (devices) ->
throw Exception if (devices.length < 1)
return
})
# .... Device is just another model
User.hasMany(Device)
Or is there a way to force that check to run synchronously? (not ideal)
you can use asynchronous validations in v2.0.0.
It works like this:
var Model = sequelize.define('Model', {
attr: Sequelize.STRING
}, {
validate: {
hasAssociation: function(next) {
functionThatChecksTheAssociation(function(ok) {
if (ok) {
next()
} else {
next('Ooops. Something is wrong!')
}
})
}
}
})