Node.js code only working at top of file with sequelize - javascript

When at the top of my server-side code, this works fine and the results produced are correct:
var data_playlists = {};
models.Playlist.findAll({
attributes: ['id', 'name']
}).then(function (playlists){
data_playlists['playlists'] = playlists.map(function(playlist){
return playlist.get({plain: true})
});
addsongs(data_playlists, 1);
addsongs(data_playlists, 2);
addsongs(data_playlists, 3);
});
but when it's inside one of my Express methods, it isn't functioning properly; particularly, the addsongs method is not working as it should.
function addsongs(playlist_object, id_entered){
var arraysongs = [];
models.Playlist.findOne({
attributes: ['id'],
where: {
id: id_entered
}
})
.then(function(playlist) {
playlist.getSongs().then(function (thesongs){
for(var k = 0; k < thesongs.length ; k++){
arraysongs.push(thesongs[k].Songs_Playlists.SongId);
}
playlist_object.playlists[(id_entered - 1)]['songs'] = arraysongs;
});
});
}
I cannot for the life of me figure out why it works when the top segment of code is at the top, but doesn't work when inside my app.get() call.

From your code I have conducted that you want to return playlists (id and name) together with their songs (id). First of all your code will not work because the calls of addsongs(data_playlists, id) are run before data_playlists is filled with data by code above it. Moreover, the addsongs function performs asynchronous operations returning Promises, so calling them one by one will not give expected result. I suppose you can do it completely differently.
I suggest you use include attribute of options object that can be passed to findAll() method. include says which association model you also want to return from current query. In this case you want to return playlists together with their songs (M:M relation according to your code), so you need to include Song model in the query.
function getPlaylistsWithSongs() {
return models.Playlist.findAll({
attributes: ['id', 'name'],
include: [
{
model: models.Song,
as: 'Songs', // depends on how you have declare the association between songs and playlists
attributes: ['id'],
through: { attributes: [] } // prevents returning fields from join table
}
]
}).then((playlistsWithSongs) => {
return playlistsWithSongs;
});
}
Example result of getPlaylistsWithSongs result would be (after translating it to JSON e.g. like playlistsWithSongs.toJSON())
[
{
id: 1,
name: 'playlist #1',
Songs: [
{ id: 1 },
{ id: 2 }
]
}
]
Above code returns all playlists (their id and name) with their songs (only their id). Now in your route resolver you can simply call above function to return the result
app.get('/api/playlists', function (request, response) {
response.setHeader("Content-Type", "application/json; charset=UTF-8");
getPlaylistsWithSongs().then(function(playlistsWithSongs){
response.status(200).send(JSON.stringify(playlistsWithSongs));
});
});
EDIT
In order to simply return array of IDs instead array of objects with id (songs), you need to map the result. There is no simple sequelize way to return array of IDs in such a case.
}).then((playlistWithSongs) => {
let jsonPlaylists = playlistsWithSongs.map((singlePlaylist) => {
// return JSON representation of each playlist record
return singlePlaylist.toJSON();
});
jsonPlaylists.forEach((playlist) => {
// at every playlist record we map Songs to array of primitive numbers representing it's IDs
playlist.songs = playlist.Songs.map((song) => {
return song.id;
});
// when we finish we can delete the Songs property because now we have songs instead
delete playlist.Songs;
});
console.log(jsonPlaylists);
// example output: [{ id: 1, name: 'playlist #1', songs: [1, 2, 3] }]
return jsonPlaylists;
});

Related

Sequelize Op.notIn with Sequelize Model

Hello i have a mysql query which is working fine in sequelize.query and the query is
select list_name from lists l where l.list_id not in
(SELECT sub.list_id from list_sub_activities sub left join.
Activities a on a.list_act_id = sub.list_act_id where a.agency_id = 2)
and i want to do the same using the sequelize model, i have tried but i think i am missing something.
List of Package ---> lists
List_of_Packages.findAll({
attributes: ['list_name'],
where: {
list_id: {
[Op.notIn]: [List_sub_Activities.findAll({
attributes: ['list_id'],
include: {
model: Activities,
required: false,
where: {
agency_id: 2
}
}
})
]
}
}
}).then((response) => {
console.log(response);
})
I appreciate that if you help me.
Thank you !!!
The findAll() (and other query methods) are asynchronous so you will need to resolve the promise (or use a callback) to resolve the value before you can pass the list_ids to Op.notIn. It will also return an array of objects with a property of list_id, so you will need to map this to an array of integers before you can use it. You can also pass in raw: true so that it will not generate Sequelize Instances from your results and will instead return plain javascript objects - this is more efficient than creating objects just to fetch a single property.
By setting required: false on the Activities include you will be returning all List_sub_Activities and not filtering on them (some will be null in your results). This is likely not what you intended.
This example uses async/await for clarity instead of thenables. Note that this is not the most efficient as it requires multiple database queries, the ideal solution would be to use a LEFT JOIN and then remove items where the package.list_id IS NULL (see second example).
// get an array of Activities with the list_id set
const activities = await List_sub_Activities.findAll({
attributes: ['list_id'],
include: {
model: Activities,
// don't use required: false to only return results where List_sub_Activities.Activities is not null
// required: false,
where: {
agency_id: 2,
},
},
raw: true,
});
// map the property to an array of just the IDs
const activityIds = activities.map((activity) => activity.list_id);
// now you can pass the activityIds to Op.notIn
const packages = await List_of_Packages.findAll({
attributes: ['list_name'],
where: {
list_id: {
[Op.notIn]: activityIds,
},
},
});
With thenables.
List_sub_Activities.findAll(...)
.then((activities) => activities.map((activity) => activity.list_id))
.then((activityIds) => List_of_Packages.findAll(...))
.then((packages) => {
console.log(packages);
});
This example LEFT JOINs List_of_Packages to List_sub_Activities which is JOINed to Activities with a WHERE setting the agency_id to 2, then only returns results from List_of_Packages where the List_sub_Activities.list_id is NULL (nothing was matched on the LEFT JOIN). This should return the same results as above in a single query.
// Get List_of_Packages where there is no match in List_sub_Activities after
// it is joined to Activities with the agency_id set.
const agencyId = 2;
const packages = await List_of_Packages.findAll({
attributes: ['list_name'],
include: {
model: List_sub_Activities,
// we don't need to actually fetch the list_id
attributes: [],
include: {
model: Activities,
where: {
agency_id: agencyId,
},
},
// uses a LEFT JOIN
required: false,
},
// only return results where the List_sub_Activities.list_id is null
where: sequelize.where(sequelize.col('List_sub_Activities.list_id'), 'IS', null),
});

How do I pull a nested object out of an array with an api request returned json?

I have an API that I am calling to return a query. This query's format cannot be changed to be easier to manipulate. It has a nested array within it that I need to associate with the data from the higher levels.
Specifically, I am trying to pull the higher level id field and and the "value" field within "column_values" and associate them with one another preferably within a new array. I feel like the answer is here but I just can't grasp how to pull the data in the correct format and associate it together. Most of the comment lines can probably be ignored, they are my other attempts at making the syntax work correctly. Sorry about the mess. I'm really new to this.
const axios = require('axios')
const body = {
query: ` query {boards(ids:307027197) {name, items {name id column_values(ids:lockbox_) {title id value text}}}} `,
}
console.log("Requesting Query....");
function getApi (callback){
setTimeout(function() {axios.post(`https://api.monday.com/v2`, body, {
headers: {
MY_API_KEY_DATA
},
})
.catch(err => {
console.error(err.data)
})
.then(res => {
var queried = res
var array = queried.data.data.boards[0].items
//console.log(queried)
//console.log(array)
console.log(array.length)
//console.log("Total Items:", array.length)
var i;
for (i = 0; i < array.length; i++){
callback(queried.data.data.boards[0].items)
//callback([(queried.data.data.boards[0].items[i].column_values[0])])
}
}, 0);
})
};
getApi(callback => {
console.log(callback)
//console.log(parsed)
//output for above
//{"name":"address","id":"1234","column_values":
//[{"title":"Lockbox#","id":"lockbox_","value":"\"31368720\"","text":"31368720"}]}
//console.log(JSON.parse(parsed))
//output for above
//[
// {
// name: 'address',
// id: '353428429',
// column_values: [ [Object] ]
// }
//]
});
setTimeout(function() {
console.log("Query Returned")},1000);
From your data, column_values is an array with objects in it. For an array, you will have to access it with the key. For your case, if your data is like
var data = {
"name":"address",
"id":"1234",
"column_values": [{"title":"Lockbox#","id":"lockbox_","value":"\"31368720\"","text":"31368720"}]
}
You can access the id of column_values as data.column_values[0].id

JS find() not working on array of objects

I have an array, clients, which I want to run array.find() on. This array contains objects, and usually looks something like this:
[ { customId: 'user1', clientId: 'TPGMNrnGtpRYtxxIAAAC' },
{ customId: 'user2', clientId: 'G80kFbp9ggAcLiDjAAAE' } ]
This is where I encounter a problem. I am trying to use find() to see if any object (or part of an object) in the array matches a certain variable, recipient, which usually contains a value like user1. the code I am using to do this is:
function checkID(recipient) {
return recipient;
}
var found = clients.find(checkID);
This always returns the first object in the array. Am I using find() wrong, or is there a better way to do this?
find takes a predicate (a function that returns true if item is a match and false if item is not a match).
const arr = [ { customId: 'user1', clientId: 'TPGMNrnGtpRYtxxIAAAC' },
{ customId: 'user2', clientId: 'G80kFbp9ggAcLiDjAAAE' } ]
const result = arr.find(item => item.customId === 'user1')
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// This should evaluate to true for a match and to false for non-match
The reason you're getting the first item of your array all the time, is because your checkId function is returning something which evaluates to true. So, the first item is evaluated and produces a truthy result, and therefore it gets picked as the first match.
If unfamiliar with the lambda syntax () => {}, then that line is similar to:
const result = arr.find(function (item) { return item.customId === 'user1' })
You are using find wrong.
If recipient contains information about the target value you should name the first param of checkID with a different name. And compare any property of it with recipient.
var found = clients.find(function(element) { return element.prop1 === recipient.anyProp; });
To check the objects in the array for the presence of a certain customId, put the value you're searching for in an object, and pass that object to find():
let clients = [{
customId: "user1",
clientId: "TPGMNrnGtpRYtxxIAAAC"
},
{
customId: "user2",
clientId: "G80kFbp9ggAcLiDjAAAE"
}
];
function checkID(el){
return el.customId === this.param;
}
let found = clients.find(checkID, {param: "user1"});
console.info(found);

[] vs [{...}] in browser tools, while both having same objects

If you look at the picture both arrays consist of same kind of object. first I create it with empty data as placeholder, but second one I create it with data coming from server.
writeValue(v: any) {
console.log('aaa');
console.log(v);
console.log('aaa');
this.form = new FormArray([]);
for (const value of v) {
console.log('bbb');
console.log(value);
console.log('bbb');
this.form.push(new FormControl(value));
}
this.form.valueChanges.subscribe(res => {
if (this.onChange) {
this.onChange(this.form.value);
}
});
}
for first case it goes through all of the writeValue code, for second one it doesn't go through the for(const values of v) code. why is this happening? when I print them out they seem to be the same other than one difference [{...}] vs [] in browser tools.
If you want to see how I create them. the first one is routes and the second one is routeslocal. I put them in angular formcontrol, and thats how it gets to writeValue via controlvalueaccessor. If you want to know how it works you could check my previous question here. there is more code, but it doesn't include the service.
ngOnInit() {
const routes: any[] = [];
routes.push({ ...dataI });
this.requestForm = this.fb.group({
statusId: null,
requestVehicles: this.fb.array([
this.fb.group({
garageId: 0,
routes: new FormControl(routes),
endDateTime: 0,
})
])
});
if (this.data.isEdit) {
this.Title = 'Edit';
this.data.fService.getRequest(this.data.requestId).subscribe(thisRequest => {
this.requestForm = this.fb.group({
statusId: thisRequest.status,
requestVehicles: this.fb.array([
])
});
thisRequest.requestVehicles.forEach((element, index) => {
const routeslocal: any[] = [];
element.routes.forEach((elementt, indexx) => {
this.data.fService.getAddressPoint(elementt).subscribe(sbed => {
const newRoute = {
addressPointId: sbed.addressPointId,
municipalityId: sbed.municipalityId,
regionId: sbed.regionId,
rvId: element.rvId,
sequenceNumber: indexx,
settlementId: sbed.settlementId,
regionName: sbed.regionName,
municipalityName: sbed.municipalityName,
settlementName: sbed.settlementName,
description: sbed.description,
};
routeslocal.push({...newRoute});
});
});
this.requestVehicles.push(this.fb.group({
endDateTime: new Date(element.endDateTime),
garageId: element.garageId,
routes: new FormControl(routeslocal),
}));
});
});
});
});
}
}
The opening line, [] or [{}], is immediately drawn in the console.
In the case of [], there was nothing in the array at logging time, so the browser draw it as an empty array. But the data was present when you looked at it and clicked on the small triangle, later.
You can reproduce this behavior with this code in your console:
;(function(){ let arr=[]; setTimeout(()=>{ arr[0] = {b:3}; }); return arr;})()
So the difference you saw is related to the (a)synchronicity of array filling.
Vato, you has two functions in your service:getRequest(requestId) and getAddressPoint(requestVehicles). The idea is return a whole object. You can create the function in the own service or in the component. I'd like in the service, and that return an objservable. You must use forkJoin and swithMap So . It's for me impossible check if work
**Update, see the stackblitz
getFullRequest(id): Observable<any> {
return this.getRequest(id).pipe(
switchMap((request: any) => {
//here you has the request. We create an array of observables
return forkJoin(
request.requestVehicles.map(
(r: any) => this.getAddressPoint(r))).pipe(map((res: any[]) => {
res.forEach((x: any, index: number) => {
x.sequenceNumber = index
})
return {
statusId: request.statusID,
routes: res
}
})
)
}))
}
then, in your component
if (this.data.isEdit) {
this.Title = 'Edit';
this.data.fService.getFullRequest(this.data.requestId).subscribe(thisRequest => {
this.requestForm = this.fb.group({
statusId: thisRequest.status,
requestVehicles: thisRequest.routes
});
Update 2 briefly explain about switchMap and forkJoin.
When we make this.getRequest(id) we received in request an object. In this object we has in requestVehicles an array (can be an array of objects or an array of numbers -or strings-). With each element of this array we can make a call, But instead of make the calls one to one, we want to make all these together. For this we use forkJoin. forkJoin received an array of observables and, in subscribe received the response in an array
//if we has an observable like:
getValue(id:number):Observable<any>{
return of({one:id})
}
//and an array like
myArray=[1,2]
//and an array of response whe we can store the responses
response:any[]
//we can do
for (let id of myArray)
{
this.getValue(id).susbcribe(res=>{
this.response.push(res)
})
}
//or
observables:any[]
for (let id of myArray)
{
this.observables.push(this.getValue(id))
}
forkJoin(this.observables).subscribe((res;any[])=>{
//in res[0] we have the result of this.getValue(1)
//in res[1] we have the result of this.getValue(2)
//so, simply
this.response=res
})
//or in a compact way
//with each element of the array
observables=myArray.map(x=>this.getValues(x))
forkJoin(this.observables).subscribe((res;any[])=>{
this.response=res
})
Well, there are two problems more. We want add a new propertie "sequenceNumber" to all the response. So we use res.forEach(...) to add the property. And we want return an object with somes properties of our original request (statusID) and in "routes" the array with the response. So we use map to transform the response. In our simple example above
//not return simple {one:1}
//return {id:1,one:1}
getResponse(2).pipe.map(res=>{
return {
id:1,
one:res.one
}
}

Meteor, query Collections based on ids retrieved from another collection

I have 2 collections.
ItemList = new Mongo.Collection('items');
BorrowerDetails = new Mongo.Collection('borrow');
ItemList.insert({
brand: "brand-Name",
type: "brand-Type",
._id: id
});
BorrowerDetails.insert({
key: "ItemList.id", //equals to .id of the ItemList Collection
name : "borrowerName"
});
Question !
How can i retrieve records from the BorrowerDetails Collection based on a certain type from the ItemList Collection.
ex. Retrieve all records from the BorrowerDetails Collection where key is equals to the id of a record on the ItemList Collection whose type is equals to "Desktop".
return BorrowerDetails.find(
{ key :
ItemList.find(
{ type : 'Desktop' },
{ fields: {'_id':1 } }
)
}
); //error!
Note that I don't have nodejs right now in my laptop, so might have several errors since I am not able to test it.
First, Create a publication file (eg. server\publications\borrowPub.js). inside the file, you should create a publication method. The logic here is simple, get the itemid array first, and then pass it as parameter in Mongo select $in.
Meteor.publish('queryBorrowerTypeDesktop', function(criteria)
{
var itemArr = ItemList.find( { type : 'Desktop' },
{ fields: {'_id':1 } });
if(itemArr){
//itemArr found, so do a select in using the array of item id we retrieved earlier
var borrowers = BorrowerDetails.find({ key: { $in: itemArr } });
return borrowers;
}else{
//found nothing- so return nothing
return []
}
});
Second, in the router :
Router.route('/test', {
name: 'test',
action: function()
{
//change accordingly.
},
waitOn: function()
{//subscribe from publisher that we just created...
return [
Meteor.subscribe('queryBorrowerTypeDesktop')
];
},
data: function() {
if(Meteor.userId())
{
// also include the sorting and limit so your page will not crash. change accordingly.
return {
borrowerDetails: BorrowerDetails.find({},{sort: {name: -1}, limit: 30}),
}
}
}
});
Note that in data, BorrowerDetails.find() does not need to filter by anything because it has been filtered during the subscribe, which has been cached in MiniMongo in your browser.

Categories