I am using sequalize in a nodeJs application. I have an entity with a one to may relation (user to many permissions).
I have the following sequalise query to get all users with their permissions... but i think i have been in nosql land for too long :/ and i fear my head no longer works in this way...
models.User.findAll( {
raw: true,
include: [ {
model: models.UserPermissions,
required: false
} ],
logging: console.log
} ).then(function( users ) {
deferred.resolve( users );
}, function(err) {
deferred.reject(err);
});
Produces the following mysql query:
SELECT
`User`.`id`,
`User`.`username`,
`User`.`password`,
`User`.`reset_password_token` AS `resetPasswordToken`,
`User`.`reset_password_expires` AS `resetPasswordExpires`,
`User`.`created_at`, `User`.`updated_at`,
`UserPermissions`.`id` AS `UserPermissions.id`,
`UserPermissions`.`user_id` AS `UserPermissions.user_id`,
`UserPermissions`.`permission` AS `UserPermissions.permission`,
`UserPermissions`.`created_at` AS `UserPermissions.created_at`,
`UserPermissions`.`updated_at` AS `UserPermissions.updated_at`
FROM
`user` AS `User`
LEFT OUTER JOIN `user_permissions` AS `UserPermissions`
ON `User`.`id` = `UserPermissions`.`user_id`;
The result of course is not what I am no used with NoSQL. Instead of recieving one object containing the user and also an array of documents within containing the related permissions I get multiple results for the same user but with different permissions.
See the snapshot of the result set below (I have 2 users in the users table, and 3 permissions in the permissions table).
Is it possible with sequalise, or even mysql for that matter, to return one object for a user with a sub array of their pemissions? or will this really require another query and post query match up?
HACKY FIX:
models.User.findAll( {
raw: true,
include: [ {
model: models.UserPermissions,
required: false
} ]
} ).then(function( users ) {
var newUsersObj = {};
for( var i = 0 ; i < users.length ; ++i ){
if( newUsersObj[users[i].id] ){
newUsersObj[users[i].id].permissions.push( users[i]['UserPermissions.permission'] );
} else {
newUsersObj[users[i].id] = users[i];
newUsersObj[users[i].id].permissions = [ users[i]['UserPermissions.permission'] ];
}
}
deferred.resolve( newUsersObj );
}, function(err) {
deferred.reject(err);
});
Related
Regarding to this post: https://stackoverflow.com/a/69903276/1547821
it shows an approach with less boilerplate, to prevent a users query output (GET) to only the user who is logged in.
But how to do this, to get i.e. all members of the related group which the logged-in user participates?
This example below is my hook, but i want to have it in before->find hook as a query injection or addition.
module.exports = (options = {}) => {
return async context => {
const groupMembers = await context.app.service('groups').options.model.relatedQuery('users').for(context.params.user.id)
console.log(groupMembers)
return context
}
}
It doesn't help me as after->find hook, because total and etc. doesn't match then. Aswell my keeps doesn't work.
Appreciating any hints.
One solution would be to add a userId column to your groups model. This will contain a user of the group.
//in your /services/groups.hook.js
const { authenticate } = require('#feathersjs/authentication').hooks;
const { setField } = require('feathers-authentication-hooks');
module.exports = {
before: {
all: [],
find: [
authenticate('jwt'),
setField({
from: 'params.user.id',
as: 'params.query.userId',
}),
],
get: [],
create: [],
update: [],
patch: [],
remove: [disallow('external')],
},
//after and error options go here
};
This should return only results where the logged in user's Id matched the Id in the group table.
Ps: try console logging the context object for more insight. Also the userId column in groups model should be associated to the id column of users model.
Thanks. Meanwhile I found a solution for the before->find.hook:
context => {
if(context.params.user !== undefined) {
const groupMembers = await context.service.options.model.relatedQuery('groups').for(context.params.user.id)
if(groupMembers) {
context.params.query['$joinEager'] = 'groups'
context.params.query['groups.id'] = groupMembers[0].id
}
}
return context;
};
i am building resful api with node js and sequelize (mysql).
i have two models with (event) has many (users).
for creating event with models i am doing this :
add(req, res) {
return event.create(
{
name: req.body.name,
users: req.body.users,
},
{
include: [
{
model: User,
as: "users",
},
],
}
)
it's working fine .
but when i want to update i have big issue here.
for example my current events looks like :
"events" : [
{
"id" : 1 ,
"name" :"name"
"users" :[
{
"id" :1 ,
"name" :"name1",
},
{
"id" :2 ,
"name" :"name2",
}
]
}]
what i want to achieve :
i want to update users :
if the id of user exists in the request i want to update the user ,but if the id doesn't exists in the request i want to delete do user.
how i can do that in sequelize and node js .i'm stack here.
Show your router where you try to handle that logic.
When you say that user exists do you mean user exists at all or only belongs to requested event, please be more specific!!!
// lets say you want to update users of event id=1
const event = await Event.findByPk(1)
// you can get users belonging to event by association mixins
const users = await event.getUsers()
// loop through users to update them
for (let i=0; i < users.length; i++) {
// put your logic to make changes to users
if (users[i].getDataValue('id') == 1) {
await event.removeUser(users[i])
}
}
I have 2 models, business and documents in a 1:n relationship, i want to filter the business in two ways,
business that has documents where every document.due_balance is greater than 0
business that has documents where every document.due_balance is equals to 0
I want to make something like this
select
A.name, B.due_balance, sum(B.due_balance) as total_due_balance
from
business A
inner join documents B ON A.id = B.business_id
group by A.id
having total_due_balance > 0;
select
A.name, B.due_balance, sum(B.due_balance) as total_due_balance
from
business A
inner join documents B ON A.id = B.business_id
group by A.id
having total_due_balance = 0;
these will get me what i want, the problem, is that the previus code was made with sequelize ORM, and i can't change it, something like this
const businesses = await db.business.paginate({
attributes: [
...
],
where: {
... //bunch of where
},
page: parseInt(params.page, 10) || 1,
paginate: parseInt(params.limit, 10) || 10,
});
here is where the problem begins, i don't know how to join the tables and use the having to filter it, i have tried addind this
let toInclude;
if (params.contactability === 'with_balance') {
toInclude = {
include : [
{
attributes: [
[db.Sequelize.fn('sum', db.Sequelize.col('due_balance')), 'total_due_balance'],
],
model: db.document,
as: 'documents',
having: db.Sequelize.where(db.Sequelize.fn('sum', db.Sequelize.col('due_balance')), {
$gt: 0,
}),
},
],
};
} else if(params.contactability === 'without_balance') {
toInclude = {
include : [
{
attributes: [
[db.Sequelize.fn('sum', db.Sequelize.col('due_balance')), 'total_due_balance'],
],
model: db.document,
as: 'documents',
having: db.Sequelize.where(db.Sequelize.fn('sum', db.Sequelize.col('due_balance')), {
$eq: 0,
}),
},
],
};
} else {
toInclude = {};
}
const businesses = await db.business.paginate({
attributes: [
...
],
where: {
...
},
...toInclude,
page: parseInt(params.page, 10) || 1,
paginate: parseInt(params.limit, 10) || 10,
});
but that does not work at all, how can i solve this problem?
I don't think HAVING will work without GROUP.
I would move the having clause outside the include section and use the AS aliases.
So, roughly:
group: ['id'], // and whatever else you need
having : { 'documents.total_balance_due' : {$eq : 0 }}
(Making some guesses vis the aliases)
To filter the date from joined table which uses groupby as well, you can make use of HAVING Property, which is accepted by Sequelize.
So with respect to your question, I am providing the answer.
You can make use of this code:
const Sequelize = require('sequelize');
let searchQuery = {
attributes: {
// include everything from business table and total_due_balance as well
include: [[Sequelize.fn('SUM', Sequelize.col('documents.due_balance')), 'total_due_balance']]
},
include: [
{
model: Documents, // table, which you require from your defined model
as: 'documents', // alias through which it is defined to join in hasMany or belongsTo Associations
required: true, // make inner join
attributes: [] // select nothing from Documents table, if you want to select you can pass column name as a string to array
}
],
group: ['business.id'], // Business is a table
having: ''
};
if (params.contactability === 'with_balance') {
searchQuery.having = Sequelize.literal(`total_due_balance > 0`);
} else if (params.contactability === 'without_balance') {
searchQuery.having = Sequelize.literal(`total_due_balance = 0`);
}
Business // table, which you require from your defined model
.findAll(searchQuery)
.then(result => {
console.log(result);
})
.catch(err => {
console.log(err);
});
Note : Change model name or attributes according to your requirement.
Hope this will help you or somebody else!
I'm trying to extract specific document fields from a mongodb collection (v 3.0.8 at MongoLab). The returned documents must fall within a date range. My goal is to extract specific fields from these documents. My nodejs code,
var query = {}, operator1 = {}, operator2 = {}, operator3 = {} ;
operator1.$gte = +startDate;
operator2.$lte = +endDate;
operator3.$ne = 'move';
query['xid'] = 1; // Problem here?
query['date'] = Object.assign(operator1, operator2);
query['type'] = operator3;
console.log(query);
MongoClient.connect(connection, function(err, db) {
if(err){
res.send(err);
} else {
db.collection('jbone')
.find(query)
.toArray(function(err, result){
console.log(err);
res.json(result);
});
};
});
If I opt to return all fields in the date range, the query works fine. If I select only field xid I get no results. My query object looks sensible according to the docs. console.log(err) gives:
{ xid: 1,
date: { '$gte': 20160101, '$lte': 20160107 },
type: { '$ne': 'move' } }
null
null is the err.
Can anyone help me understand what I'm doing wrong?
Or point me to another similar SO questions with an answer?
Thanks
To select the specific field could be done as below
.find(
{date: { '$gte': 20160101, '$lte': 20160107 }, type: { '$ne': 'move' }},
{ xid: 1} )
Sample codes as following.
query['date'] = Object.assign(operator1, operator2);
query['type'] = operator3;
db.collection('jbone')
.find(query, {xid: 1})
.toArray(function(err, result){
console.log(err);
res.json(result);
});
I am using Sequelize, MySQL and Node to write a web application.
For most of my DB needs, I usually do some verification, then fetch my models (eagerly with associations) and send them back to the client, almost always as-is (at least, up to now).
I wrote a little utility function getValuesFromRows to extract the values from a returned row array:
getValuesFromRows: function(rows, valuesProp) {
// get POD (plain old data) values
valuesProp = valuesProp || 'values';
if (rows instanceof Array) {
var allValues = [];
for (var i = 0; i < rows.length; ++i) {
allValues[i] = rows[i][valuesProp];
}
return allValues;
}
else if (rows) {
// only one object
return rows[valuesProp];
}
return null;
}
// ...
...findAll(...)...complete(function(err, rows) {
var allValues = getValuesFromRows(rows);
sendToClient(errToString(err, user), allValues);
});
However, I am adding more and more complex relations to my DB models. As a result, I get more associations that I have to fetch. Now, I don't only have to call above function to get the values from each row, but also I need more complicated utilities to get the values from all included (eagerly loaded) associations. Is there a way to only get values from Sequelize queries (and not the Sequelize model instance) that also includes all associated values from the instance?
Else, I would have to manually "get all values from each Project and add one item to that values object for the values property of each entry of Project.members" (for example). Note that things get worse fast if you nest associations (e.g. members have tasks and tasks have this and that etc.).
I am guessing that I have to write such utility myself?
I found a simple solution to my problem, by extending my existing POD converting function above with a recursion into all include'd associations of the query. The Solution works with find, findAll, all and possibly other operations with non-trivial results.
Code
/**
* Get POD (plain old data) values from Sequelize results.
*
* #param rows The result object or array from a Sequelize query's `success` or `complete` operation.
* #param associations The `include` parameter of the Sequelize query.
*/
getValuesFromRows: function(rows, associations) {
// get POD (plain old data) values
var values;
if (rows instanceof Array) {
// call this method on every element of the given array of rows
values = [];
for (var i = 0; i < rows.length; ++i) {
// recurse
values[i] = this.getValuesFromRows(rows[i], associations);
}
}
else if (rows) {
// only one row
values = rows.dataValues;
// get values from associated rows
if (values && associations) {
for (var i = 0; i < associations.length; ++i) {
var association = associations[i];
var propName = association.as;
// recurse
values[propName] = this.getValuesFromRows(values[propName], association.include);
};
}
}
return values;
}
Example
var postAssociations = [
// poster association
{
model: User,
as: 'author'
},
// comments association
{
model: Comment,
as: 'comments',
include: [
{
// author association
model: User,
as: 'author'
}
]
}
];
// ...
var query = {
where: ...
include: postAssociations;
};
// query post data from DB
return Post.findAll(query)
// convert Sequelize result to POD
.then(function(posts) {
return getValuesFromRows(posts, postAssociations);
})
// send POD back to client
.then(client.sendPosts);
In the above example, client.sendPosts receives an array. Each entry of the array will have properties author and comments. Each comment in the comments array will also have an author property. The entire array only contains POD (plain old data).