Sequelize - trying to make models dynamic - node.js

I've been trying to automate the creation of my sequelize models by creating them with a generic model that I can pass definitions into, rather than creating a model file specifically for each one.
I have an array of model definitions which looks something like this:
const modelDefinitions = [
{
name: "User",
fieldDefinitions: [
{
name: "first_name",
label: "First Name",
column_type: Sequelize.DataTypes.STRING,
},
{
name: "last_name",
label: "Last Name",
column_type: Sequelize.DataTypes.STRING,
},
{
name: "email",
label: "Email",
column_type: Sequelize.DataTypes.STRING,
},
{
name: "password",
label: "Password",
restricted: true,
column_type: Sequelize.DataTypes.STRING,
},
],
},
{
name: "Audit",
fieldDefinitions: [
{
name: "ref",
column_type: Sequelize.DataTypes.STRING,
label: "Audit Ref",
},
{
name: "result",
column_type: Sequelize.DataTypes.STRING,
label: "Result",
},
{
name: "auditor_id",
column_type: Sequelize.DataTypes.INTEGER,
label: "Auditor",
},
],
},
];
When my array of models contains just one model it works perfectly fine, but when I have multiple, the GenericModel of the previously defined models is then "changed" to ne the last one in the list that was initialised.
I'm new to node so I think I'm either missing something or there's some sort of model caching happening, meaning that all instances of GenericModel become what it is initialised as last.
Please see my example below (the commented out code is what I used to use to define the models and the reduce is my new way of defining these)
// {
// User: User.init(sequelize, modelDef),
// Article: Article.init(sequelize, modelDef),
// Audit: Audit.init(sequelize, modelDef),
// Form: Form.init(sequelize, modelDef),
// };
const models = modelDefinitions.reduce((acc, modelDef) => {
return { ...acc, [modelDef.name]: GenericModel.init(sequelize, modelDef) };
}, {});
console.log({ models });
My console.log() returns the following - notice both are Group :
{
models: {
User: Group,
Group: Group
}
}
As you can see, what ever the last model is defined as, the previous ones inherit that instead of keeping what I defined them as originally.
But what I actually want is :
{
models: {
User: User,
Group: Group
}
}
If my list only had User in it, it works fine.

I managed to get this working in the end.
I think my issue was that my GenericModel was treated as a Singleton, so to get around this I changed GenericModel from extending the Sequelize.Model and instead made a new class with a contructor to consume my arguments and then created a method on the new class to return the sequelize model.
The main change there was instead of defining the models with GenericModel.init(), I defined them by calling sequelize.define(modelName, attributes, options)
so my map now looks like this :
const models = modelDefinitions.reduce((acc, modelDef) => {
return { ...acc, [modelDef.name]: new GenericModel(sequelize, modelDef).getDBModel() };
}, {});
and my class:
class TestModel {
constructor(sequelize, modelDef) {
this.sequelize = sequelize;
this.modelDef = modelDef;
this.modelName = modelDef?.name;
this.definitions = modelDef?.fieldDefinitions;
this.restrictedFieldList = this.definitions.filter((field) => field?.restricted).map((definition) => definition.name);
}
getDBModel() {
const model = this.sequelize.define(
this.modelName,
this.definitions.reduce((acc, definition) => {
return { ...acc, [definition.name]: definition.column_type };
}, {}),
{
defaultScope: {
attributes: {
exclude: this.restrictedFieldList,
},
},
sequelize: this.sequelize,
modelName: this.modelName,
}
);
return model;
}
}```

Related

Sequelize - Select on associated table

My problem:
I am creating an route which will return some informations about a group, it has an id, an user assigned and also has some documents. I just want to show how much documents exists, in SQL would be SELECT COUNT, but how can i do this in this in sequelize?
My code:
async list(req, res){
const docGroups = await DocGroup.findAll({
raw: true,
include: [{
model: User,
as: 'userAssigned'
},
{
model: Document,
as: 'Document'
}
]
}).then(groups => {
const result = groups.map(group => {
return Object.assign(
{},
{
id: group.id,
name: group.name,
userAssinged: group['userAssigned.firstName'],
docAmount: // I want to put documents' count here
}
)
})
console.log(groups)
})
}
What is printed in console.log(groups):
[
{
id: 1,
name: 'pleaseworks',
createdAt: 2020-06-10T02:38:11.531Z,
updatedAt: 2020-06-10T02:38:11.531Z,
'userAssigned.id': 1,
'userAssigned.firstName': 'Please',
'userAssigned.lastName': 'Works',
'userAssigned.email': 'pleaseworks#gmail.com',
'userAssigned.password': '$2a$08$3BA4I4dsaQ3lsHy342344b5P41v5eHWjwqv6dve28nSdqbGvhsdS',
'userAssigned.createdAt': 2020-06-10T02:37:29.062Z,
'userAssigned.updatedAt': 2020-06-10T02:37:29.062Z,
'userAssigned.groupId': null,
'Document.id': 2,
'Document.description': 'deowkdopewkdwe',
'Document.content': 'odepodkewokodwe',
'Document.groupId': 1,
'Document.createdAt': 2020-06-10T02:43:46.005Z,
'Document.updatedAt': 2020-06-10T02:43:46.005Z
}
]
If DocGroup has many Document try something like this:
{
model: Document,
attributes: [[sequelize.fn('COUNT', sequelize.col('id')), 'docAmount']]
as: 'Document'
}

sequelize filtering for nested model

I've been working with sequelize in a couple of weeks, still trying to learn..
My Models are defined in models folder.
/models/endpoint.js
import Sequelize from 'sequelize';
import { sequelize } from '../data';
import Permission from './permission';
const { Model } = Sequelize;
class Endpoint extends Model { }
Endpoint.init({
endpoint: {
type: Sequelize.VIRTUAL(Sequelize.STRING, [ 'method', 'path' ]),
get() {
return `${this.get('method')} ${this.get('path')}`;
}
},
method: {
type: Sequelize.ENUM,
values: [ 'GET', 'POST', 'PUT', 'DELETE', 'PATCH' ],
allowNull: false
},
path: {
type: Sequelize.STRING,
allowNull: false
},
description: {
type: Sequelize.TEXT
}
}, {
sequelize,
modelName: 'endpoint'
});
Endpoint.Permissions = Endpoint.hasMany(Permission);
Permission.Endpoint = Permission.belongsTo(Endpoint, { as: 'permission' });
export default Endpoint;
/models/permission.js
import Sequelize from 'sequelize';
import { sequelize } from '../data';
const { Model } = Sequelize;
class Permission extends Model { }
Permission.init({
entity: {
type: Sequelize.ENUM,
values: [ '*', 'user', 'extension', 'queue', 'group' ],
defaultValue: '*'
},
role: {
type: Sequelize.STRING,
defaultValue: '*'
}
}, {
sequelize,
modelName: 'permission'
});
export default Permission;
found something in docs: Top level where with eagerly loaded models
User.findAll({
where: {
'$Instruments.name$': { [Op.iLike]: '%ooth%' }
},
include: [{
model: Tool,
as: 'Instruments'
}]
})
as following that, I made this
Endpoint.findAndCountAll({
where: {
path: { [Symbol(iLike)]: '%user%' },
'$permissions.role$': { [Symbol(eq)]: 'admin' }
},
include: [
{ model: permission, as: 'permissions' }
],
order: [],
offset: 0,
limit: 20
})
my code throws this error:
SequelizeDatabaseError: missing FROM-clause entry for table
"permissions"
and generated SQL query:
SELECT "endpoint".*,
"permissions"."id" AS "permissions.id",
"permissions"."entity" AS "permissions.entity",
"permissions"."role" AS "permissions.role",
"permissions"."createdAt" AS "permissions.createdAt",
"permissions"."updatedAt" AS "permissions.updatedAt",
"permissions"."endpointId" AS "permissions.endpointId",
"permissions"."permissionId" AS "permissions.permissionId"
FROM (
SELECT "endpoint"."id",
"endpoint"."method",
"endpoint"."path",
"endpoint"."description",
"endpoint"."createdAt",
"endpoint"."updatedAt"
FROM "endpoints" AS "endpoint"
WHERE "endpoint"."path" ilike '%user%'
AND "permissions"."role" = 'admin' limit 20 offset 0) AS "endpoint"
LEFT OUTER JOIN "permissions" AS "permissions"
ON "endpoint"."id" = "permissions"."endpointId";
Don't try to use limit and offset with hasMany associations and with findAndCountAll.
If you have 2 permissions in each endpoint (for instance you have 10 endpoints and set LIMIT to 10) then you'll get first 5 endpoints because in SQL query endpoints will be multiplied with permissions.
If you still want to filter parent records (endpoints) by some conditions on child records then I suppose you need to use sequelize.literal for that purpose.
If you want to filter parent and child records separately (and still use LIMIT and OFFSET) just move the permissions condition clause to association's where clause and also set separate:true in an association include to make LIMIT and OFFSET work correctly (this will make sequelize to execute separate SQL query to get permissions for each endpoint. This latter case will look like this:
Endpoint.findAndCountAll({
where: {
path: { [Symbol(iLike)]: '%user%' }
},
include: [
{
model: permission,
as: 'permissions',
separate: true,
where: {
role: { [Symbol(eq)]: 'admin' }
}
}
],
order: [],
offset: 0,
limit: 20
})

Sequelize - Postgres - How to create new object from junction table

I'm trying to use a junction table model to create a new object in Sequelize. I have read through the docs pretty thoroughly and do not believe that this use case is covered, unless I am misunderstanding (which is always a possibility).
Docs:
https://sequelize.org/master/manual/assocs.html
https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html
Example code:
import { Model as SQLModel } from 'sequelize';
class CarCompany extends SQLModel {};
class BodyStyle extends SQLModel {};
class Model extends SQLModel {};
// A car company can make multiple types of cars
CarCompany.hasMany(BodyStyle);
// A type of car can be made by multiple companies.
// We have a junction table to represent this relationship
BodyStyle.belongsToMany(CarCompany, { through: CarCompanyBodyStyles });
// Each model has only one type that it fits into and only one company that makes it
Model.belongsTo(CarCompanyBodyStyles);
// Now let's create some example car types
const convertible = new BodyStyle({ name: 'Convertible' });
const suv = new BodyStyle({ name: 'SUV' });
// Now let's create some example car companies
const toyota = new CarCompany({ name: 'Toyota' });
const ferrari = new CarCompany({ name: 'Ferrari' });
// Now let's specify which types of cars are made by each company
const toyotaConvertibles = toyota.addBodyStyle(convertible);
const toyotaSUVs = toyota.addBodyStyle(suv);
const ferrariConvertibles = ferrari.addBodyStyle(convertible);
// Now let's define some specific models
const solara = toyotaConvertibles.createModel({ name: 'Solara' });
const rav4 = toyotaSUVs.createModel({ name: 'RAV-4' });
const spider = ferrariConvertibles.createModel({ name: '488 Spider' });
// Now lets see some nested relational objects that we have created:
const toyotaModels = CarCompany.findByPk(
toyota.id,
{
include: [
{
all: true,
nested: true,
},
],
},
);
const ferrariModels = CarCompany.findByPk(
ferrari.id,
{
include: [
{
all: true,
nested: true,
},
],
},
);
console.log({ ferrariModels, toyotaModels });
What I was hoping to see is something like:
{
ferrariModels: {
bodyStyles: [
{
name: 'Convertible',
models: [
{ name: '488 Spider' },
],
},
],
},
toyotaModels: {
bodyStyles: [
{
name: 'SUV',
models: [
{ name: 'RAV-4' },
],
},
{
name: 'Convertible',
models: [
{ name: 'Solara' },
],
},
],
},
}
But instead I get an error:
TypeError: toyotaConvertibles.createModel is not a function
What am I doing wrong? How am I supposed to go about creating this type of relationship?

Looping through objects and adding them to Sequelize db

I have an array of objects that I like to put into SQL via Sequelize and I'm running into issues.
[
{ owner: false,
id: '2342365',
name: 'awrAPI' },
{ owner: false,
id: '5675689699',
name: 'TgatAPI' },
{ owner: true,
id: '57456767',
name: 'ytasyAPI' }
[
Currently the way i have it set up is through a simple for in.
for( var key in guilds ) {
Guild
.findOrCreate({where: {primid: guilds[key].id}, defaults: {
primid: guilds[key].id,
name: guilds[key].name,
owner: guilds[key].icon
}})
.spread(function(guild, created) {
console.log(guild.get({
plain: true
}))
console.log(created)
})
}
I assume this is wrong and was wondering if there is a better way to loop through my object and chain the findorcreates. Currently it goes through the first object, but then does not add any more. I've been looking into using Bluebird, but I'm not too familiar with using promises. Thoughts?
var myData = [
{
owner: false,
id: '2342365',
name: 'awrAPI'
},
{
owner: false,
id: '5675689699',
name: 'TgatAPI'
},
{
owner: true,
id: '57456767',
name: 'ytasyAPI'
}
];
Promise.all(myData.map(function(value) {
// Do your thing
return Guild.findOrCreate...
})).then(function() {
// All is resolved do your next thing
})
An alternative would be to utilize squelize's bulkCreate model method for multiple records insertion.
link to sequelize's bulkCreate docs
quick snippet here:
const dataForInsertion = [
{
username:"daniel",
currentORM:"Sequelize"
},
{
username:"lekan",
currentORM:"Mongoose"
},
]
Guild.bulkCreate(dataForInsertion)
.then(()=>{})
.catch(()=>{})

Unable to fetch list in react relay

I am following schema same as mentioned here
I want to fetch all users so I updated my schema like this
var Root = new GraphQLObjectType({
name: 'Root',
fields: () => ({
user: {
type: userType,
resolve: (rootValue, _) => {
return getUser(rootValue)
}
},
post: {
type: postType,
args: {
...connectionArgs,
postID: {type: GraphQLString}
},
resolve: (rootValue, args) => {
return getPost(args.postID).then(function(data){
return data[0];
}).then(null,function(err){
return err;
});
}
},
users:{
type: new GraphQLList(userType),
resolve: (root) =>getUsers(),
},
})
});
And in database.js
export function getUsers(params) {
console.log("getUsers",params)
return new Promise((resolve, reject) => {
User.find({}).exec({}, function(err, users) {
if (err) {
resolve({})
} else {
resolve(users)
}
});
})
}
I am getting results in /graphql as
{
users {
id,
fullName
}
}
and results as
{
"data": {
"users": [
{
"id": "VXNlcjo1Nzk4NWQxNmIwYWYxYWY2MTc3MGJlNTA=",
"fullName": "Akshay"
},
{
"id": "VXNlcjo1Nzk4YTRkNTBjMWJlZTg1MzFmN2IzMzI=",
"fullName": "jitendra"
},
{
"id": "VXNlcjo1NzliNjcyMmRlNjRlZTI2MTFkMWEyMTk=",
"fullName": "akshay1"
},
{
"id": "VXNlcjo1NzliNjgwMDc4YTYwMTZjMTM0ZmMxZWM=",
"fullName": "Akshay2"
},
{
"id": "VXNlcjo1NzlmMTNkYjMzNTNkODQ0MmJjOWQzZDU=",
"fullName": "test"
}
]
}
}
but If I try to fetch this in view as
export default Relay.createContainer(UserList, {
fragments: {
userslist: () => Relay.QL`
fragment on User #relay(plural: true) {
fullName,
local{
email
},
images{
full
},
currentPostCount,
isPremium,
}
`,
},
});
I am getting error Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.
Please tell me what I am missing .
I tried a lot with and without #relay(plural: true).
Also tried to update schema with arguments as
users:{
type: new GraphQLList(userType),
args: {
names: {
type: GraphQLString,
},
...connectionArgs,
},
resolve: (root, {names}) =>connectionFromArray(getUsers(names)),
},
but I got error Cannot read property 'after' of undefined in implementing react-relay
Thanks in Advance.
Relay currently only supports three types of root fields (see facebook/relay#112):
Root field without arguments, returning a single node:
e.g. { user { id } } returning {"id": "123"}
Root field with one argument, returning a single node:
e.g. { post(id: "456") { id } } returning {"id": "456"}
Root field with one array argument returning an array of nodes with the same size as the argument array (also known as "a plural identifying root field"):
e.g. { users(ids: ["123", "321"]) { id } } returning [{"id": "123"}, {"id": "321"}]
A workaround is to create a root field (often called viewer) returning a node that has those fields. When nested inside the Viewer (or any other node), fields are allowed to have any return type, including a list or connection. When you've wrapped the fields in this object in your GraphQL server, you can query them like this:
{
viewer {
users {
id,
fullName,
}
}
}
The Viewer type is a node type, and since there will just be one instance of it, its id should be a constant. You can use the globalIdField helper to define the id field, and add any other fields you want to query with Relay:
const viewerType = new GraphQLObjectType({
name: 'Viewer',
interfaces: [nodeInterface],
fields: {
id: globalIdField('Viewer', () => 'VIEWER_ID'),
users:{
type: new GraphQLList(userType),
resolve: (viewer) => getUsers(),
},
},
});
On the client you'll need to change the root query in your route to { viewer } and define the fragment on Viewer:
export default Relay.createContainer(UserList, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
users {
fullName,
local {
email,
},
images {
full,
},
currentPostCount,
isPremium,
}
}
`,
},
});

Resources