Sequelize associations: Can't add associations, throws TypeError - postgresql

I am a newbie to postgres and Sequelize. I come from NoSQL background, so this is a bit daunting to me.
First of all this is my project directory:
server/
components/
center/
center.model.js
center.controller.js
center.routes.js
So, I am following a modules based architecture.
Here's my center.models.js:
const Sequelize = require('sequelize');
const sequelize = require('../../config/sequelize');
const Inventory = require('../inventory/inventory.model');
const Center = sequelize.define('center', {
name: {
type: Sequelize.TEXT,
allowNull: false,
validate: {
notEmpty: true,
},
},
zone: {
type: Sequelize.TEXT,
allowNull: false,
validate: {
notEmpty: true,
},
},
lat: {
type: Sequelize.DOUBLE,
},
long: {
type: Sequelize.DOUBLE,
},
nearest_metro: {
type: Sequelize.TEXT,
},
metro_distance: {
type: Sequelize.DOUBLE,
},
address: {
type: Sequelize.TEXT,
},
google_map: {
type: Sequelize.TEXT,
},
landmark: {
type: Sequelize.TEXT,
},
landmark_directions: {
type: Sequelize.TEXT,
},
active: {
type: Sequelize.BOOLEAN,
defaultValue: true,
},
created_at: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
},
updated_at: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
}
});
Center.hasMany(Inventory);
module.exports = Center;
And this is my SpacePartner model:
const Sequelize = require('sequelize');
const sequelize = require('../../config/sequelize.js');
const Center = require('../center/center.model.js');
const SpacePartner = sequelize.define('space_partner', {
name: {
type: Sequelize.TEXT,
allowNull: false,
validate: {
notEmpty: true,
},
},
active: {
type: Sequelize.BOOLEAN,
defaultValue: true,
},
created_at: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
},
updated_at: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
}
});
SpacePartner.hasMany(Center);
module.exports = SpacePartner;
And this is how I am using the two models in my space_partner.controller.js:
const CreateSpacePartner = async (req, res) => {
try {
const {
spacePartner,
center,
inventory,
} = req.body;
const [createdSP, createdCenter, createdInventory] = await Promise.all([SpacePartner.create(spacePartner), Center.create(center), Inventory.create(inventory)]);
createdCenter.addSpacePartner(createdSP);
createdInventory.addCenter(createdCenter);
createdInventory.addInventoryType(inventory.inventory_type_id);
const [updatedCenter, updatedInventory] = await Promise.all([createdCenter.save({
fields: ['space_partner_id']
}), createdInventory.save({
fields: ['center_id', 'inventory_type_id']
})]);
return res.status(200).json({
spacePartner: createdSP,
center: updatedCenter,
inventory: updatedInventory,
});
} catch (e) {
console.log(e);
return res.status(500).json({
message: 'Sorry, we are facing some issue right now. Please, try again later.',
});
}
};
Now, the issue is that I am getting TypeError on addSpacePartner addCenter and addInventoryType.
I was following the official docs.
I did go through other tutorials, but they had more of MVC architecture, I don't want to go back to restructuring my code.
Any help would this would be great.

For this to work createdCenter.addSpacePartner(createdSP);, to work, we need to define the relationship between Center and SpacePartner. So it should be
Center.hasMany(SpacePartners)
Once you have this, these functions should be available to you.
You can read more about association here
The code for Inventory is not posted, so its hard to say about that.

Related

How to I resolve below graphql query in mongodb nested array

my model schema look like this
const mongoose = require("mongoose")
const userSchema = new mongoose.Schema(
{
username: {
type: String,
required: true,
},
password: {
type: String,
required: true,
select: false,
},
email: {
type: String,
required: true,
unique: true,
match: [
/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/,
"Please enter a valid email",
],
},
followers:[
{
type:mongoose.Schema.Types.ObjectId,
ref:"user"
}
],
following:[
{
type:mongoose.Schema.Types.ObjectId,
ref:"user"
}
],
displayName: {
type: String,
required: false,
},
},
{ timestamps: true }
)
module.exports = mongoose.model("user", userSchema)
in this schema all working good like mutation work fine but when i fetch query of all user then in that query followers and following field return null like bellow image
and my graphql query is
const users = {
type: new GraphQLList(UserType),
description: "Retrieves list of users",
resolve(parent, args) {
return User.find()
},
}
and typedef is
const UserType = new GraphQLObjectType({
name: "User",
description: "User types",
fields: () => ({
id: { type: GraphQLID },
username: { type: GraphQLString },
email: { type: GraphQLString },
post:{
type: GraphQLList(PostType),
resolve(parent, args) {
return Post.find({ authorId: parent.id })
},
},
savePost:{
type:GraphQLList(savedPosts1),
resolve(parent, args) {
return SavePost.findById({ authorId: parent.id })
},
},
followers:{
type:GraphQLList(UserType),
},
following:{
type:GraphQLList(UserType)
}
// displayName: { type: GraphQLString },
}),
})
so please tell me how to i resolve that followers and following query in graphql with mongodb and tell me what i write in my userType typedef

How to fetch data from multiple collections in MongoDB?

How to fetch data from multiple collections in MongoDB by using a common field and sending data (in those fetched collections) using one response?
Employee Scheema:
const mongoose = require("mongoose");
const employeeSchema = new mongoose.Schema(
{
profilePic: {
type: String,
},
employeeID: {
type: String,
required: true,
},
employeeFirstName: {
type: String,
required: true,
},
employeeLastName: {
type: String,
required: true,
},
birthday: {
type: String,
},
streetNo: {
type: String,
},
city: {
type: String,
},
phoneNumber: {
type: String,
},
jobRole: {
type: String,
required: true,
},
NIC: {
type: String,
required: true,
unique: true,
},
companyEmail: {
type: String,
required: true,
unique: true,
},
status: {
type: String,
required: true,
},
resignDate: {
type: String,
},
jobType: {
type: String,
required: true,
},
candidateID: {
type: String,
required: true,
},
teamID: {
type: String,
},
lastSeen: {
type: String,
},
token: {
type: String,
},
},
{ timestamps: true }
);
module.exports = mongoose.model("employee", employeeSchema);
Acadamic Qualification Scheema:
const mongoose = require("mongoose");
const academicQualificaationSchema = new mongoose.Schema(
{
employeeID: {
type: String,
required: true,
},
ordinaryLevelResult: {
type: Array,
required: true,
},
advancedLevelResults: {
type: Array,
required: true,
},
achievements: {
type: Array,
required: true,
},
},
{ timestamps: true }
);
module.exports = mongoose.model(
"academicQualification",
academicQualificaationSchema
);
Professional Qualification Scheema:
const mongoose = require("mongoose");
const proffesionalQualificaationSchema = new mongoose.Schema(
{
employeeID: {
type: String,
required: true,
},
degree: {
type: Array,
required: true,
},
language: {
type: Array,
required: true,
},
course: {
type: Array,
required: true,
},
},
{ timestamps: true }
);
module.exports = mongoose.model(
"proffesionalQualification",
proffesionalQualificaationSchema
);
Controller:
exports.viewEmployees = async (req, res) => {
try {
let accQuali, profQuali;
const employees = await employeeSchema.find();
accQuali = await academicQualificaationSchema.find();
profQuali = await ProffesionalQualificationSchema.find();
if (employees || accQuali || profQuali) {
return res.status(200).json({ data: { employees, profQuali, accQuali } });
} else {
return res.status(404).json({ message: message });
}
} catch (err) {
return res.status(404).json({ err: err.message });
}
};
This controller is working properly and sends all data in 3 collections with the use of one response. But, I am comfortable if I will be able to Fetch data separately for each employee.
If you want to get the data from the three collections for specific employee or employees, you can use an aggregation pipeline with a $lookup stage, as suggested by #1sina1. For example:
db.employee.aggregate([
{
$match: {"employeeID": "IDA"}
},
{
$lookup: {
from: "academicQualification",
localField: "employeeID",
foreignField: "employeeID",
as: "academicQualification"
}
},
{
$lookup: {
from: "proffesionalQualification",
localField: "employeeID",
foreignField: "employeeID",
as: "proffesionalQualification"
}
}
])
As you can see on the playground

How to set common where param to all models in query, in sequelize

I'm trying to realize the query, where I can find all records, which at least one of the attributes includes the text, user send with request, for that I use where, but it can search, as I understand, in only own model, but i need it to search it in parent model and in associated filds together, not apart.
There is my models:
const Picture = sequelize.define<IPictureInstance>('picture', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
img: { type: DataTypes.STRING, allowNull: false },
mainTitle: { type: DataTypes.STRING, allowNull: false },
description: { type: DataTypes.TEXT }
});
const PictureInfo = sequelize.define<IPictureInfoInstance>('pictureInfo', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
title: { type: DataTypes.STRING, allowNull: false },
description: { type: DataTypes.STRING, allowNull: false }
});
const PictureTag = sequelize.define<IPictureTagInstance>('pictureTag', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
text: { type: DataTypes.TEXT, allowNull: false }
});
const PicturesTags = sequelize.define<IPicturesTagsInstance>('picturesTags', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }
});
And their associations:
Picture.belongsToMany(PictureTag, { through: PicturesTags, as: "tags", onDelete: 'cascade' });
PictureTag.belongsToMany(Picture, { through: PicturesTags, as: "pictures", onDelete: 'cascade'
});
Picture.hasMany(PictureInfo, { onDelete: "cascade" });
PictureInfo.belongsTo(Picture);
That's what I tried to do:
static async getPictures(query: string | undefined) {
const pictures = await models.Picture.findAll({
where: {
[Op.or]: {
mainTitle: { [Op.iRegexp]: `${query}` },
description: { [Op.iRegexp]: `${query}` },
},
},
include: [
{
model: models.PictureInfo,
as: "pictureInfos",
where: {
[Op.or]: {
title: { [Op.iRegexp]: `${query}` },
description: { [Op.iRegexp]: `${query}` }
}
},
required: false
},
{
model: models.PictureTag,
as: "tags",
attributes: ["id", "text"],
where: { text: { [Op.iRegexp]: `${query}` } },
through: {
attributes: [],
},
required: false
}
],
});
return pictures;
}
But in this case, when it can't find records in first where param it returns an empty array, I understand it, but it isn't a behavior I need.
I need to check every attribute together.
So, if user send query=cat, it will check mainTitle and description, if there is nothing, it will check associated pictureInfos fields and after, if there is nothing, check pictureTags associated fields, that's what I need, will be grateful for the help.
My solution:
static async getPictures(query: string | undefined) {
const whereStatement = {
[Op.or]: {
mainTitle: { [Op.iRegexp]: `${query}` },
description: { [Op.iRegexp]: `${query}` },
"$tags.text$": { [Op.iRegexp]: `${query}` },
"$pictureInfos.title$": { [Op.iRegexp]: `${query}` },
"$pictureInfos.description$": { [Op.iRegexp]: `${query}` },
}
};
const pictures = await models.Picture.findAll({
where: whereStatement,
include: [
{
model: models.PictureInfo,
as: "pictureInfos",
},
{
model: models.PictureTag,
as: "tags",
}
],
});
return pictures;
}
But I'm still confused with those $ symbols, what they are for, I couldn't find the answer in documentation?
Link to the Sequelize documentation, to confirm my answer

Sequelize Migration addIndex not adding index in descending order

I am trying to create an index on an existing table (Postgres) on a date column so that I can get the latest posts first
Model file feeditem.js
module.exports = (sequelize, DataTypes) => {
const FeedItem = sequelize.define('FeedItem', {
feedItemId: {
//...
},
pubdate: {
allowNull: false,
type: DataTypes.DATE,
validate: {
isDate: true,
notEmpty: true,
},
},
link: {
//...
},
title: {
//...
},
description: {
//...
},
summary: {
//...
},
author: {
//...
},
hash: {
//...
},
}, {
timestamps: false,
underscored: true,
indexes: [
{
fields: [{ attribute: 'pubdate', order: 'DESC' }],
unique: false,
},
],
});
FeedItem.associate = (models) => {
// associations can be defined here
//...
};
return FeedItem;
};
Migration file create-feed-item.js
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('feed_items', {
feed_item_id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
pubdate: {
allowNull: false,
type: Sequelize.DATE,
},
link: {
allowNull: false,
type: Sequelize.STRING,
},
title: {
allowNull: false,
type: Sequelize.STRING,
},
description: {
type: Sequelize.TEXT,
},
summary: {
type: Sequelize.TEXT,
},
author: {
type: Sequelize.STRING,
},
hash: {
allowNull: false,
type: Sequelize.UUID,
unique: true,
},
}),
// eslint-disable-next-line no-unused-vars
down: (queryInterface, Sequelize) => queryInterface.dropTable('feed_items'),
};
Migration file add-index.js
module.exports = {
/*
Add altering commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
// eslint-disable-next-line no-unused-vars
up: (queryInterface, Sequelize) => queryInterface.addIndex('feed_items', ['pubdate'], {
fields: [{
attribute: 'pubdate', order: 'DESC',
}],
unique: false,
name: 'feed_items_pubdate_index',
}),
/*
Add reverting commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.dropTable('users');
*/
// eslint-disable-next-line no-unused-vars
down: (queryInterface, Sequelize) => queryInterface.removeIndex('feed_items', 'feed_items_pubdate_index'),
};
The migrations are run in perfect order where table is created first and index is added later.
Logging is enabled and when I check the logs
Expected:
It should create a DESC index on pubdate which goes like
CREATE INDEX "feed_items_pubdate_index" ON "feed_items" ("pubdate" DESC)
Actual Output
Executing (default): CREATE INDEX "feed_items_pubdate_index" ON "feed_items" ("pubdate")
Any ideas what is going wrong here?
Your question is still relevant because it is undocumented.
The proper way to define index in descending order is extended definition in fields argument.
So the migration file add-index.js should look like:
module.exports = {
...
up: (queryInterface, Sequelize) => queryInterface.addIndex(
'feed_items',
[{
attribute: 'pubdate', order: 'DESC',
}],
{
unique: false,
name: 'feed_items_pubdate_index',
}
),
...
};
Resolved issue reference.
use uniqueKeys:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
allowNull: true,
type: Sequelize.STRING
},
order: {
allowNull: false,
type: Sequelize.INTEGER
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
},
{
uniqueKeys: {
actions_unique: {
fields: ["name", "order"],
},
},
}
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Users');
}
};

Eager Loading : How to disable specific fields of included table

I am trying to do Eager Loading in Sequelize with PostgreSQL where I need to find the Users which have a given specific Mail id or basically, i am performing find operation on Mail model while using include to include User model
UserModel :
module.exports = function (sequelize, Sequelize) {
var User = sequelize.define('User', {
userId: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
firstname: {
type: Sequelize.STRING,
require: true
},
lastname: {
type: Sequelize.STRING,
require: true
},
age: {
type: Sequelize.INTEGER,
require: true
},
phone: {
type: Sequelize.STRING,
require: true
},
location: {
type: Sequelize.STRING,
require: true
},
createdAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
},
updatedAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
}
});
return User;
};
MailModel :
module.exports = function (sequelize, Sequelize) {
var User = require('./User.js')(sequelize, Sequelize)
var Mail = sequelize.define('Mail', {
name: {
type: Sequelize.STRING,
require: true
}
});
Mail.belongsTo(User);
return Mail;
};
MailController :
var db = require('../services/db.js');
module.exports = {
create: function (req, res, next) {
var Mailm = db.MailModel;
var name = req.body;
try {
db.sequelize.sync().then(function () {
Mailm.create(name).then(function (found) {
return res.json({
success: true,
message: found.get({
plain: true
})
});
})
});
} catch (ex) {
res.json({
success: false,
exception: ex
});
return;
}
},
query: function (req, res, next) {
var Mailm = db.MailModel;
var Userm = db.UserModel;
var name = req.body;
var option = {};
option.where = name;
option.include = [{
model: Userm
}];
try {
Mailm.findAll(option).then(function (found) {
console.log(found);
return res.json({
success: true,
message: found
});
});
} catch (ex) {
res.json({
success: false,
exception: ex
});
return;
}
}
};
It is returning me the records of both User and Mail table in exactly the right way .
Output :
{
"success": true,
"message":[
{
"id": 2,
"name": "Mailb2",
"createdAt": "2015-07-30T07:32:51.807Z",
"updatedAt": "2015-07-30T07:32:51.807Z",
"UserUserId": 2,
"User":{
"userId": 2,
"firstname": "Prerna",
"lastname": "Jain",
"age": 20,
"phone": "9812123456",
"location": "Sirsa",
"createdAt": "2015-07-30T07:30:48.000Z",
"updatedAt": "2015-07-30T07:30:48.000Z"
}
}
]
}
But I want to disable createdAt and updatedAt fields of User table so that it does not give me these two fields in the output for User.
I have tried a lot as of how to do this but still in vain.Can anyone please help me out.
I bet this is coming late, add attribute/properties to your models called timestamps, it accepts a boolean as a value. For example:
module.exports = function (sequelize, Sequelize) {
var User = require('./User.js')(sequelize, Sequelize)
var Mail = sequelize.define('Mail', {
name: {
type: Sequelize.STRING,
require: true
}
},
{
// This does the magic
timestamps: false,
});
Mail.belongsTo(User);
return Mail;
};
Also, add it to the User model:
var User = sequelize.define('User', {
userId: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
firstname: {
type: Sequelize.STRING,
require: true
},
lastname: {
type: Sequelize.STRING,
require: true
},
age: {
type: Sequelize.INTEGER,
require: true
},
phone: {
type: Sequelize.STRING,
require: true
},
location: {
type: Sequelize.STRING,
require: true
},
createdAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
},
updatedAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
}
},
{
timestamps: false
});
return User;
};
You can use
Model.findAll({
attributes: { exclude: ['baz'] }
});
more examples with attributes - http://docs.sequelizejs.com/en/latest/docs/querying/#attributes