Mongoose default sorting order - node.js

Is there a way to specify sorting order on the schema/model level in Mongoose?
I have model Posts, and I always fetch posts ordered by 'createdAt' field. Thus on each query I have to write .sort('-createdAt'). Can I make this order default for this model?

There is no way, in Mongoose, directly to define a default sort order on your query.
If you're doing something over and over again though, you might want to abstract this into a function that does it for you:
function findPostsByDate(cb){
Posts.find({}).sort('-createdAt').exec(cb);
}
Or even something more generic than that:
function findXByDate(model, findCriteria, cb){
model.find(findCriteria).sort('-createdAt').exec(cb);
}

You can achieve this by creating a static method in your schema definition.
Mongoose documentation for Methods and statics here: http://mongoosejs.com/docs/2.7.x/docs/methods-statics.html
Example
In your schema file:
PostSchema.statics.sortedFind = function sortedFind(query, fields, options cb){
//First 3 parameters are optional
if( arguments.length === 1){
cb = query;
} else if (arguments.length === 2) {
cb = fields;
} else if(arguments.length === 3){
cb = options;
}
this.find(query, fields, options).sort('-createdAt').exec(cb);
}
Then you can use:
var query = {user_id: currentUser.id}; // query example, modify according to your needs
Post.sortedFind(query, function(err, response){ /* Your code goes here */ });

This is how I enforce sortable columns and provide a default sort. I copy this code into each model and just supply the allowSortOn array.
postSchema.pre('find', function (){
if (typeof this.options.sort !== 'undefined') {
var allowSortOn = ["_id","createdAt"] // add other allowable sort columns here
, propCount = 0;
for (var prop in this.options.sort)
if (this.options.sort.hasOwnProperty(prop)) {
if (allowSortOn.indexOf(prop) === -1) {
console.log('Invalid sort column ' + prop);
delete this.options.sort[prop];
} else {
propCount++;
}
}
if (propCount === 0) {
this.options.sort[allowSortOn[1]] = 1;
console.log('Setting sort column to ' + JSON.stringify(this.options.sort));
}
}
})

Related

How to validate mongoose document by current field in existed doc in mongodb?

I have mongoose schema
const messageSchema = new mongoose.Schema({{
name: String,
code: String //enum ['start', 'waiting', 'complete']
})
for example - I will save an item:
{
name: 'firstItem',
code: 'start'
}
next time - when update this document I want to use validation function
function validateCode(value) {
if (existed.code === 'start' && value === 'waiting') {
return true;
}
if (existed.code === 'waiting' && value === 'complete') {
return true;
}
return false;
}
but How can I call existed item id db in validate function?
Big thx!
I'm fairly certain that validators don't get access to the object that is being updated, so what you are trying to do won't work with validators.
There are however different options for you. For instance you could use a instance method.
For the validation flow you described before, a instance method accomplishing the same would be this:
messageSchema.methods.updateCode = function(value){
const fromStart = this.code === 'start' && value === 'waiting';
const fromWaiting = this.code === 'waiting' && value === 'complete';
if (fromStart || fromWaiting) {
this.code = value;
return this.save();
}
else {
throw Error("Invalid code update");
}
}
With this method you would also be free to choose a more optimized way to handle the "invalid code update" case.
More on instance methods could be found in the mongoose docs

Is this good design for updating/deleting from list using useState and useEffect?

I'm trying to create a React app that:
sends data (json message) from backend to frontend using socket.io
if a json message with same is sent, update the existing list
This is how i'm implementing it right now but i'm not sure if this is a good design methodology or if there's a better way to achieve what I want to do.
function App(){
const [database, setDatabase] = useState([])
useEffect(() => {
socket.on('incoming_data', (data) => {
setDatabase((currentList) => {
if (currentList.length > 0){ //only check for update/delete if more than 1 item present
let exists = !!currentList.find((item) => item.ID === data.ID)
if (exists){ //if new item exists in database list
if (data.deleteFlag === true){ // incoming data will have a json field declaring whether to delete or not
//deleting item
var item = currentList.find(itm => itm.ID === data.ID)
let ind = currentList.indexOf(item)
return (currentList.splice(ind,1))
}
else{ // else if delete flag is not true... update fields
var item = currentList.find(itm => itm.ID === data.ID)
let ind = currentList.indexOf(item)
if (item.dataField !== data.dataField){
currentList[ind].dataField = data.dataField
}
return (currentList)
}
}
//if incoming data doesnt exist in list, add to it
else{ return([...currentList, data]) }
}
}
// if there are 0 items in list, add to list
else { return ([...currentList, data]) }
})
}, [socket])
return(/*using map to display list in front end*/)
}
Right now, this code works in the following ways:
Checks if there are 0 items in 'database', if so, it adds items to it.
What it's not doing:
updating items in database
deleting items properly. Sometimes it deletes items, other times it does nothing.
Any help would be great!
Use higher-order functions to simplify code like filter, findIndex, etc.
use findIndex method to check items exist and use the same index to update currentList.
use the filter function to delete items from the list.
function App() {
const [database, setDatabase] = useState([])
useEffect(() => {
socket.on('incoming_data', (data) => {
setDatabase((currentList) => {
if (currentList.length > 0) { //only check for update/delete if more than 1 item present
// Use same index to find item
let itemIndex = currentList.findIndex((item) => item.ID === data.ID)
if (itemIndex !== -1) { //if new item exists in database list
if (data.deleteFlag === true) { // incoming data will have a json field declaring whether to delete or not
// use filter for delete
return currentList.filter((item) => item.ID !== data.ID);
}
else {
let item = currentList[itemIndex]
const newItem = { ...item, dataField: data.dataField }
if (item.dataField !== newItem.dataField) {
currentList[itemIndex] = newItem;
return [...currentList]; // Set new value for updates
}
return (currentList)
}
}
//if incoming data doesn't exist in list, add to it
else { return ([...currentList, data]) }
}
// if there are 0 items in list, add to list
else { return ([...currentList, data]) }
});
});
}, [socket])
return (/*using map to display list in front end*/)
}

Need to execute function when forEach functions ends

I have this code in node js / firebase :
ref.child("recipts").once("value", function(usersSnap) {
usersSnap.forEach(function(reciptsSnap) {
reciptsSnap.forEach(function(reciptSnap) {
reciptSnap.ref.child("last_recipt").once("value", function(b) {
b.forEach(function(c) { //Here I fill some "product" object
});
});
reciptSnap.forEach(function(b) { //Here I fill some "product" object
});
});
});
});
I need to execute a function just when "reciptSnap" forEachs finished. How can I accomplish this, I try using a variable i++ and i-- but only work for one forEach iteration.
The function I call is for manipulating the product object I created with the filled data from the forEachs loops.
If I have understood correctly, you want to call a function when reciptsSnap.forEach is complete and all async tasks inside it are also complete.
For achieving this, you can use the index parameter and the original array that is passed to the callback function of forEach. (See Documentation)
The code will be like this:
(Note: The following code is without changing the current forEach loop structure used. However, re-writing the code with Promise or async would be a better & cleaner way to do it).
var loop1Done = false;
var loop2Done = false;
ref.child("recipts").once("value", function (usersSnap) {
usersSnap.forEach(function (reciptsSnap) {
reciptsSnap.forEach(function (reciptSnap, index, colA) {
const idx = index;
const col = colA;
reciptSnap.ref.child("last_recipt").once("value", function (b) {
const i = idx;
const c = col;
b.forEach(function (c, j, colB) { //Here I fill some "product" object
// Do what you want here
// Check if all done for this loop
if ((j >= colB.length) && (i >= c.length)) {
loop1Done = true;
// Check if all loops done
if (loop1Done && loop2Done) {
// Call final callback function
// e.g. myFinalCallback();
}
}
});
});
reciptSnap.forEach(function (b, k, colC) { //Here I fill some "product" object
const i = idx;
const c = col;
// Do what you want here
// Check if all done for this loop
if ((k >= colC.length) && (i >= c.length)) {
loop2Done = true;
// Check if all loops done
if (loop1Done && loop2Done) {
// Call final callback function
// e.g. myFinalCallback();
}
}
});
});
});
});
Try:
reciptSnap.child("last_recipt").forEach(function(b) {
b.forEach(function(c) {
//Here I fill some "product" object
});
});
This should work since all of your data should already have been fetched when you did "value" on the receipts node.
If this works, your code is no longer asynchronous and right after the last forEach, you can execute the function you wanted to.
reciptSnap.forEach(function(b) {
//Here I fill some "product" object
});
//Execute your function here
});

Leaflet, geojson: filter out entire features/objects with a null value in them

I have a geojson file which I'm getting from this website which somehow contains corrupt data, with a coordinate value = null.
http://measuringamsterdam.nl/datalist/kijk/
And I'm using it in my code like this:
//Retrieve all data and add to map
$.each(datalistObject['idlist'], function(key, value) {
$.getJSON('http://measuringamsterdam.nl/datalist/kijk/' + value['id'], function(data) {
textbox = value['name'];
var dataid = L.geoJson([data], {
style: function (feature) {
return feature.properties && feature.properties.style;
},
onEachFeature: onEachFeature,
pointToLayer: function (feature, latlng) {
return L.marker(latlng, {
icon: value['icon']
});
}
}).addTo(jsonGroup);
console.log(jsonGroup);
},function(xhr) { console.error(xhr); });
});
Now somehow I need to filter out the features/objects where the coordinates have a null value.
I really need to filter the data that point in my code since I need the + value['id'] part in the getJSON code.
Ane ideas?
Using the following code you will generate a new array. Which will include only the filtered data.
var newArray = data.filter(function (el) {
return el.value != 'null';
});
You can also apply multiple filters, for example:
return el.value_a != 'null' && el.value_b > 100;
Hopefully this will work!

Good way to handle Q Promises in Waterline with Sails.js

I have a problem that I'm importing some data and each new row depends on previous row being added (since each row has its order attribute set based on current maximum order from other objects). The flow is that I first try to find object with the same name, if not found I first check maximum order and create new object with order + 1 from that query.
I tried doing this with Q promises which are available under Waterline. I tried using all method as well as combining queries with then from Q docs:
var result = Q(initialVal);
funcs.forEach(function (f) {
result = result.then(f);
});
return result;
But all objects had the same order, just like they would be executed in parallel, instead of waiting for the first chain to finish.
I finally found a solution with recurrency, but I doubt it's the best way of working with promises. Here's the code that works (+ needs some refactor and cleaning etc.), to show the rough idea:
function findOrCreateGroup(groupsQuery, index, callback) {
var groupName = groupsQuery[index];
Group.findOne({ 'name' : groupName }).then(function(group) {
if (!group) {
return Group.find().limit(1).sort('order DESC').then(function(foundGroups) {
var maxOrder = 0;
if (foundGroups.length > 0) {
maxOrder = foundGroups[0].order;
}
return Group.create({
'name' : groupName,
'order' : (maxOrder + 1)
}).then(function(g) {
dbGroups[g.name] = g;
if (index + 1 < groupsQuery.length) {
findOrCreateGroup(groupsQuery, index + 1, callback);
} else {
callback();
}
return g;
});
});
} else {
dbGroups[group.name] = group;
if (index + 1 < groupsQuery.length) {
findOrCreateGroup(groupsQuery, index + 1, callback);
} else {
callback();
}
return group;
}
});
}

Resources