Sending array to populate geospatial field using Mongoose + NodeJS - arrays

I'm trying to save a geospatial array into a Schema. Here is my Schema (I'm using Moongose + Express + NodeJS):
var Route = new schema({
route: String,
time: Number,
distance: Number,
geo: {type: [Number], index: '2d'},
created: { type: Date, default: Date.now }
}, {collection: 'route'});
var routeModel = mongoose.model('Route', Route);
And here is an example of the data I'm sending to populate an instance of that schema:
{
distance: 6.899893863658173,
geo:[ [13.695901, -89.24937], [13.706500876975248, -89.24967010761316],
[13.711430396814366, -89.2561502488519] ],
route: "Running route",
time: 31
}
First noob question is: is it possible to do what I'm doing? sending an array of arrays in geo?
And here is how I save the data:
socket.on('new_route', function (data){
var route = new routeModel();
route.route = data.route;
route.time = data.time;
route.distance = data.distance;
route.geo = data.geo;
route.save(function(err) {
if (err) throw err;
socket.emit("route_saved", {mensaje: "Well done!"});
app.listen(8080);
});
});
If I send and empty array on geo, all works fine. However, I'm getting the following error
"Cast to number failed for "13.695901, -89.24937, 13.706500876975248, -89.24967010761316..." at path "geo".
whenever I send an array (like the one posted above) on "geo".
So second question, any ideas on why I'm getting my array threated like a big string?

Mongoose doesn't support arrays of arrays of any type ([[Number]]). It may be supported in the future.
If your use case requires this schema, use a Mixed type which allows ad-hoc structures but provides no change tracking or casting.
new Schema({ geo: { type: Schema.Types.Mixed, index: '2d' }})
As for arrays being treated as a big string, mongoose converts the invalid argument to a string and what you observe is an artifact of how javascript converts arrays to strings.
var a = [3,4,5]
console.log(String(a)) // '3,4,5'

Related

Sorting in mongoose Express does not sort properly

I need help with sorting in Express working with mongooose db.
When i use sort({'price':1}) everythink is good, but when i pass JSON.stringify(sort) which contains and logs out {"price":1} it stops working. Any ideas why?
if(req.query.sortOption){
const str = req.query.sortOption.split(':');
sort[str[0]] = str[1] === 'desc' ? -1:1;
}
console.log(JSON.stringify(sort));
//here logs out {"price":-1} which works when i pass it into sort function as a string
try {
const annoucements = await Annoucement.find(query)
.skip(page * annoucementsPerPage)
.limit(annoucementsPerPage)
.populate('author')
.sort(JSON.stringify(sort))
res.status(200).json({
status: 'Successfully got an annoucement',
results: annoucements.length,
data: {
annoucements,
},
});
} catch (error) {
res.status(500).json({
status: 'Failed to get all annoucements',
message: error,
});
}
};
How to sort in mongoose?
.sort() takes in an object, and does not take in a string. You used JSON.stringify(sort), which made the sort query into a string, hence mongoose could not parse it.
Solution:
You should just pass .sort(sort) instead of transforming the object to a string.
Explanation:
Mongoose either accepts
a string like "price" (for ascending order) or "-price" (for descending order).
an array (not applicable in this case)
an object, where the key is the property name and the value is the order (1, "asc", "ascending" or -1, '"desc", "descending"`)
What you did was basically pass the string value "{price:-1}" to it, which does not match any of the use cases. Therefore your sorting does not work as expected.

Looping over and combining Swift array of structs?

I'm trying to create an array of structs (User structs below) where, if the user.name does not yet exist in the array, it appends the user -- but if the user.name is already present ("Mcdonalds" in the below example), then it will simply add the item.amount to the existing struct.
In other words, the below code should create an array of 4 Users, with the User Mcdonalds item.amount totaling 23.44 + 12.33.
I remember doing this sort of thing in JS no sweat, but I can't for the life of me figure out how to do this in Swift. Thanks for any help!
struct User {
var name: String
var amount: Double
}
var user1 = User(name: "Mcdonalds", amount: 23.44)
var user2 = User(name: "Wendys", amount: 15.44)
var user3 = User(name: "Cabanos", amount: 12.22)
var user4 = User(name: "Shell Gas", amount: 23.33)
var user5 = User(name: "Mcdonalds", amount: 12.33)
To loop over the users they'll need to be in an array to start.
Then you can use .reduce(into:) to reduce them into one condensed dictionary (the dictionary will allow you to have a unique key (the name of the user here) so that you don't have duplicate entries). Then you can use .map() to just get the value and not the key of that dictionary so that the final result will be an array of users.
struct User {
var name: String
var amount: Double
}
var users = [
User(name: "Mcdonalds", amount: 23.44),
User(name: "Wendys", amount: 15.44),
User(name: "Cabanos", amount: 12.22),
User(name: "Shell Gas", amount: 23.33),
User(name: "Mcdonalds", amount: 12.33)
]
var reducedUsers = users.reduce(into: [String: User]()) { (result, nextUser) in
if let existing = result[nextUser.name] {
result[nextUser.name] = User(name: nextUser.name, amount: existing.amount + nextUser.amount)
} else {
result[nextUser.name] = nextUser
}
}.map { $0.value }
A clean and swifty way is to write an extension for Array. Swift is highly protocol-oriented, which means you are able to extend any existing system or self-written class with new functions.
This is just a simple implementation, which uses a function to append or update any given user object:
extension Array where Element == User {
/// Appends a not existing user to the array or updates the amount value if user is already present
mutating func appendOrUpdate(_ userObject: Element) {
// Check if user is already in the array
if let index = self.firstIndex(where: { $0.name == userObject.name }) {
// Update the amount
self[index].amount += userObject.amount
}
else {
// Append the user
self.append(userObject)
}
}
}
As the where clause specifies the extension the Element of the array only to be applied when the given object is your User struct, it is only available when you pass in an user object.
Usage:
var userArray: [User] = []
userArray.appenOrUpdate(User(name: "Mcdonalds", amount: 23.44))
userArray.appenOrUpdate(User(name: "Wendys", amount: 15.44))
userArray.appenOrUpdate(User(name: "Cabanos", amount: 12.22))
userArray.appenOrUpdate(User(name: "Shell Gas", amount: 23.33))
userArray.appenOrUpdate(User(name: "Mcdonalds", amount: 12.33))
This will result in an array with just four entries and the double entry 'Mcdonalds' user's amount cumulated.
Note that the function has the mutating keyword in front, as if not you will not be able to modify the array and its entries. This is necessary due the nature of arrays being Structs themselves.
You can also write a function like the know Array's append(contentsOf:) and pass in an array of user objects and loop through them updating or appending the given objects.
Best way is to put this extension in a separate file called Array+User.swift according to best practise naming conventions.
You can read more about extensions in Swift and their power here: https://docs.swift.org/swift-book/LanguageGuide/Extensions.html
Matthew Gray's answer is very good, and can be used for a wide variety of problems that may be more complex than this one. But for this specific problem, it can be done much more simply.
let reducedUsers = users.reduce(into: [:]) { (result, user) in
result[user.name, default: 0] += user.amount
}
.map(User.init)
The point of this is that it tears apart the struct into key and value in a Dictionary, and then reassembles the values into an Array at the end. Swift is smart enough to figure out the type of the [:], so there's no need to specify that.
Note that there is a time-space tradeoff here. This creates a temporary Dictionary that can be very large. If this kind of operation is common and the dataset is large, you should consider storing this data in a Dictionary ([String: User] or [String: Double]) all the time rather than converting back and forth.

How to check an array with regular expression in GraphQL

I need to check the existence of some elements in an array as such
I have an array as such
ar = ['one','two','three']
I want to know how I can individually check the elements in the regular expression code below instead of "/something/" that would map through my array and check if they exist in graphQL one by one.
similar : allCockpitHello (filter: {Association : {value : {regex: "\/something/" }}} limit:2){
nodes{
Name{
value
}
}
You need to have the regex string as an input parameter to be used by the resolver, GraphQL is not going to do the filter for you, you need to do/call that logic in the resolver based on your inputs.
Based on your example, you could have something like this on the schema and resolver:
type Node {
name: String!
}
type NodeQueries {
nodes (filterRegEx :String): [Node]!
}
Once you have the input string on the resolver, the implementation of the filter mechanism is up to you.
const resolvers = {
...
NodeQueries: {
nodes: (parent, params) => {
const {filterRegEx} = params; // regex input string
const ar = ['one','two','three'];
// Create a RegExp based on the input,
// Compare the with the elements in ar and store the result...
// You might end up with ... res = ['one', 'three'];
// Now map the result to match your schema:
return _.map(res, name => ({name}) ); // to end up with [{name: 'one'}, {name: 'three'}]
}
}
...
}
GraphQL is not a magic bullet - it's only a query language, it 'transports' your needs to the engine (local client, remote server ...) where all the necessary processing takes place.
In this case you probably need to pass your array and expression as variables to the server (resolver). If processing is expensive results (similar relation) should be already defined, cached, preprocessed, etc.
If dataset is small you can do this entirely client-side - iterate over an array (fetched using graphql).

Mongoose querying the array... changing the elements of the array

I am trying to remove all the elements from the array in the MongoDB database, then I insert all the new array elements.
My Model is:
const mongoose = require('mongoose');
var schema = new mongoose.Schema({
email : {
type : String
},
password : {
type : String
},
stocks : {
type : [String]
}
}, {versionKey:false}, {_id: false});
module.exports = final = mongoose.model('users', schema);
My stocks array will then have some values. I am trying to remove those values using the following command:
I read at somewhere in Stack Overflow that to empty your array you can do many things but a set is the fastest way to do this. Please let me know if you know any other way which is better than this.
final
.findOneAndUpdate({email:"abcd#gmail.com"}, {$set:{stocks:[]}})
.then(()=>console.log("Data removed."))
.catch(err=>console.log(err));
Once data is removed it means the array will get emptied. Then I assign the whole set of the new array from my local variable like this:
const newData = {
stocks : ["abcd", "wxyz"]
};
Now I am trying to assign this new array to my database using this command:
final
.findOneAndUpdate({email:"abcd#gmail.com"}, {$set:{stocks:newData.stocks}});
It is emptying the array successfully, but when I am assigning new array it is not working and shows an empty array. Can anyone assist me with this, please?
Try with
final.findOneAndUpdate({email:"abcd#gmail.com"}, {$set:{stocks:newData.stocks}}, {new: true})
.then((doc)=>console.log(doc))
.catch(err=>console.log(err));
If you don't use a callback the query is not executed.
The query executes if callback is passed else a Query object is returned.
Mongoose documentation

mongoose array with 2 (or more) schema types

Building on the answer to this question,
Nested arrays in Mongoose
I am wondering if there is a way to have an array in a mongoose schema that will only accept one of two types of Schema defined documents as elements in the array. What do I mean by this? Consider a schema defined as...
var MyDocSchema = new Schema({
name: String,
stuff: [Stuff],
});
Where stuff is defined as
var Stuff = new Schema({
name: String,
value: Number,
});
That works, but what if I want my stuff: array to be able to contain Stuff or OtherStuff....
var MyDocSchema = new Schema({
name: String,
stuff: [Stuff *or* OtherStuff ],
});
where Stuff is the same as before and OtherStuff is defined as
var OtherStuff = new Schema({
count: Number,
when: Date,
});
There are basically two ways to do this:
store Stuff and OtherStuff in another own collection (the same collection), and store only the id of the objects in the field, you can then populate the result. The trick with this solution is that you want to create two mongoose models pointing to the same collection.
Note: This solution is fine for big and vastly differing Stuff and OtherStuff objects (because of mongoDb storage heuristics), with a low Document count (because population is a slow process). Ultimately if the objects are very different they should have their own field though.
use discriminators. Basically you need three document Schemas for your example (other schemas stay the same):
// -- Stuff --
// Please copy from above
// -- OtherStuff --
// Please copy from above
// MyDocument -
var MyDocSchema = new Schema({ name: String });
// MyDocStuff -
var MyDocStuffSchema = new Schema({ stuff: [Stuff] });
// MyDocOtherStuff -
var MyDocOtherStuffSchema = new Schema({ stuff: [OtherStuff] });
var myDoc = new mongoose.Model( 'myDoc', myDocSchema );
var myDocStuff = myDoc.discriminator( 'myDocStuff', myDocStuffSchema );
var myDocOtherStuff = myDoc.discriminator( 'myDocOtherStuff', myDocOtherStuffSchema );
// when writing with this method, you need to know which model is relevant
// isOtherStuff() just checks for _when_ or _count_ fields
if( isOtherStuff(doc) ) {
myDocOtherStuff.create(doc);
} else {
myDocStuff.create(doc);
}
Note: this is the solution to use with polymorphic objects which look similar, that are fast for large N in doc.

Resources