How can i add normalized records using sequalize? - node.js

I'm trying to create a method User.createRider() that adds normalized data to to the UserAttribute column.
var sequelize = require('../database.js').sequelize,
Sequelize = require('../database.js').Sequelize,
UserAttribute = sequelize.define('userAttributes', {
key: Sequelize.STRING,
value: Sequelize.STRING,
}),
User;
UserAttribute.belongsTo(User);
User = sequelize.define('user', {},{
classMethods: {
createRider: function(params) {
newUser = User.create();
//this should be related to the user.
UserAttribute.create({key: 'somekey', value: 'somevalue'});
return newUser;
}
}
});
module.exports = User;
My problem is that I can't create the UserAttribute-model before the user is done, because I need the User-model to associate the userAttributes to and I can't create the usermodel first, because I can't create the class method before the userAttribute is defined. This seems like a common scenario. How can this be solved.

Sounds to me like you could create the user first and then add its id to the userAttribute when you create the latter. Like so:
User = sequelize.define('user', {}, {
classMethods: {
// Returns a new user instance as a promise.
createRider: function(params) {
return User.create().then(function(user) {
return UserAttribute.create({
key: 'somekey',
value: 'somevalue',
userId: user.id
}).then(function() {
// Make sure this promise chain returns the user object, not the userAttribute object
return user;
});
});
}
}
});

Related

Why is the reference not being saved along with the rest of the data?

I am new with MongoDB "relations" and I am trying to save data to a MongoDB database. There are two models, one model is the user and the other model is the authentication data. The data is saved correctly.
import { Schema, model } from 'mongoose'
const stringRequired = {
type: String,
trim: true,
required: true
}
const stringUnique = {
...stringRequired,
unique: true
}
const UserSchema = new Schema({
name: stringRequired,
username: stringUnique,
email: stringUnique,
}, { timestamps: true });
const AuthSchema = new Schema({
email: { type: Schema.Types.ObjectId, ref: 'User' },
salt: stringRequired,
hash: stringRequired,
}, { timestamps: true })
export const userModel = model('User', UserSchema)
export const authModel = model('Auth', AuthSchema)
As you can see, one of the models is referenced by another. The email field has a reference to the user, email being the id that I want to use for authentication. But for some reason, when I save the documents, all the data is sent except the reference.
This is my controller, which as you can see, abstracts the user and the authentication to carry out the business logic and then save it in the database separately.
function add(body: any) {
return new Promise((resolve, reject) => {
if (!body) {
const error = new Error('No body on the request')
reject(error)
} else {
const user = {
username: body.username,
email: body.email,
name: body.name
}
const saltRouds = 10
const salt = bcrypt.genSaltSync(saltRouds)
const hash = bcrypt.hashSync(body.password, salt)
const auth = { salt, hash }
store.add(userModel, user)
store.add(authModel, auth)
resolve('User created')
}
})
}
This is the store.add function.
async function add (collection: any, data: any) {
return await collection.create(data)
}
Note this code is writhed with Typescript.
You're missing the reference key when creating the Auth instance. The "foreign key" in MongoDB is the id of a document that has type Schema.Types.ObjectId and can be accessed with document._id.
So your code should look like:
const auth = { salt, hash };
const user = store.add(userModel, user);
auth.email = user._id;
store.add(authModel, auth);
Be aware that your store.add() function is an async function and you should wait for it's result like #jfriend00 said in the comments.
You can achieve that by making add() also an async funtion and doing:
const auth = { salt, hash };
const user = await store.add(userModel, user);
auth.email = user._id;
await store.add(authModel, auth);
or using the Promise approach by chaining .then(). You can read more about it here.

When creating a new element my foreignKey is always null. Sequelize, Node, Postgresql

I currently have a User model and a Team model that I want to associate.
User.js Model
"use strict";
module.exports = sequelize => {
const User = sequelize.define(
"User",
{
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV1,
primaryKey: true,
allowNull: false
},
...more fields
}
{ tableName: "Users", timestamps: true }
);
User.associate = function(models) {
// 1 to many with skill
User.hasMany(models.Skill, {
foreignKey: "userId"
});
// 1 to many with team
User.hasMany(models.Team, {
foreignKey: "userId"
});
};
return User;
};
Team.js Model
"use strict";
module.exports = (sequelize, DataTypes) => {
const Team = sequelize.define(
"Team",
{
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
...more fields
},
{ tableName: "Teams", timestamps: true }
);
Team.associate = function(models) {
Team.belongsTo(models.User, {
foreignKey: "userId"
});
};
return Team;
};
The column userId is automatically added to the team's table with the correct type (uuid) but no matter what I try it's always null.
I have tried just defining without options, and again the column was added, but set to null when a team was created.
1). Define the associations with no options.
User.associate = function(models) {
User.hasMany(models.Team);
}
Also, I have another table "Skills" that uses the same code but it works.
"use strict";
module.exports = (sequelize, DataTypes) => {
const Skill = sequelize.define(
"Skill",
{
id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
},
{ tableName: "Skills", timestamps: true }
);
Skill.associate = models => {
Skill.belongsTo(models.User, {
foreignKey: "userId"
});
};
return Skill;
};
This is the method i'm using to create the team.
// Create a new team
exports.createTeam = async (req, res) => {
// Get the user id from the session
const userId = req.session.passport.user.id;
const { title } = req.body;
// Make sure the user hasn't already created a team with that title.
const existingTeam = await models.Team.findOne({
where: { title: title, userId: userId }
});
if (existingTeam !== null) {
// Response and let the user know.
...
return;
}
const defaultTeamValues = {
title: title,
...
};
// No existing team was found with that title and created by that user.
// Create it.
const team = await models.Team.create(defaultTeamValues);
// Done
res.status(200).json(team);
};
New record in Team's table showing null value in foreign key (userId) column.
I'm using pgAdmin 4 to view the tables and each table has the correct constraints fkeys too.
I feel like I'm missing something simple here, but I've read the docs and searched for similar issues but haven't found what I needed. What am I missing?
Ok, so I sort of figured out the problem. Instead of using
// Look for existing team
const existingTeam = await models.Team.findOne({
where: { title: title, userId: userId }
});
if (existingTeam !== null) {
...
return;
}
// No existing team was found with that title and created by that user.
// Create it.
const team = await models.Team.create(defaultTeamValues);
I switch to this method.
const team = await models.Team.findOrCreate({
where: {
title: title,
userId: userId
},
// title and userId will be appended to defaults on create.
defaults: defaultTeamValues
});
The difference being that findOrCreate will append, the values in the where query, to the default values you provide.
This is what I did previously when adding new skills and that's why my Skills records had the userId and Teams did not.
So at this point, I don't know if my associations are even working because I'm actually just adding the userId value into the new record.
Back to reading the docs and testing.

sequelize use instance method after selecting result

I want to understand how sequelize instance methods works and if its possible to use returned object as instance for further usage. Basically I'm just selecting user by its user name, later I want to compare if password matches and if so - update data. But the error says
Unhandled rejection TypeError: user_data.validPassword is not a function
and I'm not even close to instance update..
my User model:
const bcrypt = require('bcryptjs');
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
user_name: DataTypes.STRING,
user_password: DataTypes.STRING,
user_token: DataTypes.STRING,
user_alias_name: DataTypes.STRING,
}, {
tableName: 'oc_users',
instanceMethods: {
generateHash(password) {
return bcrypt.hash(password, bcrypt.genSaltSync(8));
},
validPassword(password) {
return bcrypt.compare(password, this.password);
}
}
});
return User;
};
my method:
...
loginAttempt(cookie) {
return models.User.findOne({
attributes: ['id', 'user_password', 'user_alias_name'],
where: {user_name: this.user}
}).then(user_data => {
if (!user_data) return 'No matching results for such a user';
return user_data.validPassword(this.password).then(result => {
if (result !== true) return 'Invalid password for selected user';
return this.updateAfterLogin(user_data, cookie);
})
})
}
updateAfterLogin(user, cookie) {
return user.update({
user_token: cookie
}).then(()=> {
return {data: 'updated'};
})
}
...
It depends on which version of sequelize you're using and probabily you're using Sequelize v4. On Sequelize v4 classMethods and instanceMethods were removed from sequelize.define.
You may check it at oficial docs for more informations:
http://docs.sequelizejs.com/manual/tutorial/upgrade-to-v4.html#config-options
Removed classMethods and instanceMethods options from sequelize.define. Sequelize models are now ES6 classes. You can set class / instance level methods like this
Old
const Model = sequelize.define('Model', {
...
}, {
classMethods: {
associate: function (model) {...}
},
instanceMethods: {
someMethod: function () { ...}
}
});
New
const Model = sequelize.define('Model', {
...
});
// Class Method
Model.associate = function (models) {
...associate the models
};
// Instance Method
Model.prototype.someMethod = function () {..}

Sequelize: is there a way to update a value from an instance method

Im am trying to add a value to a column after an account is created and I would like to do this from the instance method of the model. I am creating the account in a service in the typical manner and I want to use the users id which is generated by sequelize to concat to another generated id which is also stored in the Users table with each user. I basically would like to call the instance method once the user is created as follows:
Model:
module.exports = function(sequelize) {
let User = sequelize.define('User', {
email: ORM.STRING,
password: ORM.STRING,
accountVerified: {
type: ORM.INTEGER,
defaultValue: 0
}
},
{
classMethods: {
hashPassword: function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
}
},
instanceMethods: {
validatePassword: function(password) {
return bcrypt.compareSync(password, this.password);
},
uniqueId: function() {
let ch = this.id + sid.generate();
this.userId = this.id + sid.generate();
}
}
});
return User;
};
Service:
findOrCreate(email, password, done) {
let cachedEmail = email;
this.models.User
.findOrCreate({
where: { email: email },
defaults: {
password: this.models.User.hashPassword(password)
}
})
.spread(function(user, created) {
if (!created) {
return done(null, false, {
email: cachedEmail,
message: `Email ${cachedEmail} already exists.`
});
}
user.uniqueId();
done(null, user);
});
}
You can do it in two ways
use afterCreate hook which is run immediately after creating new instance,
use the instance method which updates specified column value.
The first option is very comfortable because it is run every time new instance of model is created so you would not have to remember about adding the code every time you would like to generate the unique id.
{
hooks: {
afterCreate: function(user, options){
let uniqueId = user.get('id') + sid.generate();
return user.set('userId', uniqueId).save().then((self) => {
return self;
});
}
}
}
Above code would simply execute UPDATE on newly created user instance.
On the other hand, if you want to use instance method, it should look like that
{
instanceMethods: {
uniqueId: function() {
let uniqueId = this.get('id') + sid.generate();
return this.set('userId', uniqueId).save().then((self) => {
return self;
});
}
}
}
And then you could call it as normal instance method, however it returns a promise
userInstance.uniqueId().then((user) => {
// updated user
done(null, user);
}).catch(e => {
// handle error
done(e);
});

How to extend data-model with new method?

I use Sequelize to store and save data to a database.
var sequelize = require('../database.js').sequelize,
Sequelize = require('../database.js').Sequelize;
User = sequelize.define('user', {
authID: Sequelize.STRING,
name: Sequelize.STRING,
});
User.prototype.createRider = () => {
console.log('test');
};
module.exports = User;
I'm trying to extend the model with a new method to store a specific kind of user, but User.prototype.createRider doesn't work. What can be done?
There is an "expansion of models" section in their documentation
I believe you can use the classMethods object
var Foo = sequelize.define('foo', { /* attributes */}, {
classMethods: {
createRider: function(){ return 'smth' }
}
})
You can define both instance and class methods on your Model, during the define.
Change your sequelize.define as follows:
User = sequelize.define('user', {
authID: Sequelize.STRING,
name: Sequelize.STRING,
}, classMethods: {
createRider: function() {
console.log('test');
}
});
You can then use the classMethods in the expected way:
User.createRider();
See the Sequelize documentation: Model Definition.

Resources