How do you exclude and include fields in Sequelize by scope? - node.js

const getMe = await UserModel.scope("test").findOne({
where: {
uid: uid,
},
include: [
{
model: GroupModel,
as: "groups",
include: ["product"],
},
],
});
I am trying to manage excluding fields and allowing fields based on scope.
defaultScope: {
attributes: {
exclude: ["id"],
},
},
scopes: {
test: {
atrributes: {
exclude: ["email"],
},
},
},
associations
UserModel.hasMany(GroupModel, { as: "groups" });
Groupmodel.belongsTo(UserModel, {
foreignKey: "userId",
as: "user",
});
GroupModel.belongsTo(ProductModel, {
foreignKey: "productId",
as: "product",
});
As a test I am by default excluding "id", and with the test scope I am excluding "email". I have tried everything from exclude include setting attributes directly in the findOne call. Nothing works.
What is the proper way to exclude certain fields say for "Public" returns, and include all fields for an "Admin scope" of some sort?

If you have defaultScope like this.
defaultScope: {
attributes: {
exclude: ['email']
}
}
When you do find query, it excludes "email" by default and use unscoped to disable the defaultScope.
// This should not return email
UserModel.findOne()
// Admin case: unscoped to disable defaultScope. This should return email.
UserModel.unscoped().findOne()
Alternatively, if you would like to be more explicit, you can have scope named "admin".
{
defaultScope: {
attributes: {
exclude: ['email']
}
},
scopes: {
admin: {} // No special options for admin scope. No exclusion.
}
}
This way when you do find query, it is excluding "email" by default. Then, if you use "admin" scope, it won't exclude anything.
// This should not return email
UserModel.findOne()
// Admin case: This should overwrite the defaultScope.
UserModel.scope('admin').findOne()
.scope(str) function overwrites the defaultScope, so any options in defaultScope is ignored when you use .scope(str).

Related

Sequelize exclude returns different value

So when I query without attribute: exclude... it returns me the correct user, but when I insert that in the query it returns a completly different user. Here is the code that I'm inserting.
const user = await Users.findOne(
{
attributes: { exclude: ["password", "admin", "createdAt", "updatedAt"] },
},
{ where: { id: req.user.id } }
);
res.json({ user });
Any ideas why is this happening?
Thanks in advance
You'r passing the where condition to the second paramter, but according to the docs: https://sequelize.org/docs/v6/core-concepts/model-querying-finders/#findone
Model.findOne only has one parameter which is the query options.
Move your where condition into the object for the first parameter.
const user = await Users.findOne({
attributes: {
exclude: ["password", "admin", "createdAt", "updatedAt"],
},
where: { id: req.user.id },
});
res.json({ user });

Sequelize - trying to make models dynamic

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;
}
}```

Doing to joins on a table that already has an alias

I have an association on a self referencing table which requires an alias.
I'm currently trying to query the table twice. However I'm running into a
Transaction (OneTimeDebits) is not associated to Transaction! error.
It seems that since the self referencing association requires an alias there is no way to query the association with a different alias.
Is there some way I can get around this?
models.Transaction.hasMany(models.Transaction, { as: 'Debits', foreignKey: 'PaymentId' });
models.Transaction.belongsTo(models.Transaction, { as: 'Payment', foreignKey: 'PaymentId' });
models.Transaction.findAll({
where: {
'$OneTimeDebits.id$': null,
},
include: [{
model: models.Transaction,
where: {
OneTime: true,
},
required: false,
as: 'OneTimeDebits',
},
{
model: models.Transaction,
where: {
FinalDebit: true,
},
as: 'FinalDebits',
}],
});

Sequelize Default Exclude

I have a table named person, I want a column to be excluded as default,
const Person = sequelize.define('person',{
secretColumn: Sequelize.STRING,
//... and other columns
});
I see that there is a feature called Scope in Sequelize:
http://docs.sequelizejs.com/manual/tutorial/scopes.html
I tried to exclude like this;
const Person = sequelize.define('person',{
secretColumn: Sequelize.STRING,
//... and other columns
}, {
defaultScope: {
exclude: ['secretColumn']
}
});
But that does't work. Is there any other way to exclude a column by default?
I firgured it out. exclude needs to be in attributes part:
const Person = sequelize.define('person',{
secretColumn: Sequelize.STRING,
//... and other columns
}, {
defaultScope: {
attributes: { exclude: ['secretColumn'] }
}
});

Loopback include relations and where

I have two loopback models: user and backendUser. One (user) is stored with loopback and the other one (backendUser) is stored on a distant mysql database. They have a relation togerther based on a field user has (backendUserId) corresponding to the ID field of backendUser
There is a case where I get every user with its corresponding backendUser. But I would like to get the users depending on some backendUser values. Example: I need every user where the backendUser.role is 4.
I use this filter to have the expected result:
{ include: { relation: "backendUser", scope: { where: { "role" : 4 } } } }
My issue is that I get an array of every user but I only get related backendUser when they have role to 4. The where is only applying on backendUser not on the whole entity.
I don't know if it's clear, but I really need this.
Use the following:
{
where: {
role: 4,
include: {
relation: "backendUser",
scope: {
where: { "role": 4 }
}
}
}
}
{
where: {
// here specific object of query user
},
include: [
{
relation: 'backendUser',
scope: {
where: {
role: 4,
},
},
},
],
}

Resources