How to extract an object from an array? - javascript

I have this code, where a username is required from a fake db:
const MY_FAKE_DB = require('./my_db.js')
const username = 'jdoe2';
const user = MY_FAKE_DB.users[username];
console.log(user.name, user.passwordHash)
And the "dataBase" (my_db.js) is the next one:
const users =
{
jdoe2: [{
username: "jdoe2",
name: "Jhoe",
passwordHash: "1234"
}],
lmart: [{
username: "lmart",
name: "Luis",
passwordHash: "2345"
}]
}
I donĀ“t know how to get from users the user whose username is jdoe2.
Thanks for the help

You have a few things that are causing problems here.
First to use require, you should export your object from the fake db. Something like:
// my_db.js
const users = {
jdoe2: {
username: "jdoe2",
name: "Jhoe",
passwordHash: "1234"
},
lmart: {
username: "lmart",
name: "Luis",
passwordHash: "2345"
}
}
module.exports = users // export users
Notice I changed the DB. You defined each user as an array, but that doesn' make sense and you weren't accessing them as arrays. Here each user is a single object.
Now to use it you can require it and it should work as expected:
const users = require('./my_db.js') // the exported users becomes the `users`
const username = 'jdoe2';
const user = users[username];
console.log(user.name, user.passwordHash)

Related

NodeJS Performance with working with arrays

I want to retrieve a user's chats with corresponding users from a different collection in NodeJS and MongoDB.
The nature of NodeJS gives me a bad feeling that running the following code will block or decrease performance of my app. I can duplicate some data but I want to learn more about NodeJS.
Please let me know whether my code is ok and will not decrease performance.
Here I fetch 20 chats. I also need their corresponding users.
then I get the userIds and perform another query against the User collection.
Now I have both but I should merge them using Array.map.
I don't use $lookup because my collections are sharded.
$lookup
Performs a left outer join to an unsharded collection in the same database to filter in documents from the "joined" collection for processing. To each input document, the $lookup stage adds a new array field whose elements are the matching documents from the "joined" collection. The $lookup stage passes these reshaped documents to the next stage.
https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#mongodb-pipeline-pipe.-lookup
let chats = await Chat.find({ active: true }).limit(20);
/*
[
{_id: ..., userId: 1, title: 'Chat A'},
...
]
*/
const userIds = chats.map(item => item.userId);
/*
[1, ...]
*/
const users = await User.find({ _id: { $in: userIds }});
/*
[
{_id: 1, fullName: 'Jack'},
...
]
*/
chats = chats.map(item => {
item.user = users.find(user => user._id === item.userId);
return item;
});
/*
[
{
_id: ...,
userId: 1,
user: {_id: 1, fullName: 'Jack'}, // <-------- added
title: 'Chat A'
},
...
]
*/
This is NOT how you should do it. MongoDB has something called Aggregation Framework and $lookup pipeline that will do that for you automatically with only 1 MongoDB query.
But since you are using Mongoose, this query become even more simpler since you can use populate() method of the Mongoose. So your whole code can be replaced with one line like this:
const chats = await Chat.find({ active: true }).populate('userId;).limit(20);
console.log(chats)
Note: If your collections are sharded, in my opinion you already implemented the logic in best possible way.
You are using async/await, so your code will wait a response from every time use await
// Wait to finish here
let chats = await Chat.find({ active: true }).limit(20);
/*
[
{_id: ..., userId: 1, title: 'Chat A'},
...
]
*/
// Wait to finish here too
const users = await User.find({ _id: { $in: userIds }});
/*
[
{_id: 1, fullName: 'Jack'},
...
]
*/
So if you has too many data and you don't have any index on your collection it will be too long to finish those query.
At this case you should create ref in your collection Chat to collection User with chat.userId = user._id
Then when you call query chat, you populate field userId so you don't have to map const userIds = chats.map(item => item.userId); and chats = chats.map...
Sample for chat schema
const { Schema, model } = require("mongoose");
const chatSchema = new Schema({
active: Boolean,
userId: {
type: "ObjectId",
ref: "User",
},
title: String,
message: String
// another property
});
const userSchema = new Schema({
username: String,
email: String
// another property
})
// query for chat
const chatModel = new model('chat', chatSchema)
let chats = await chatModel.find({ active: true }).populate('userId').limit(20);
/*
[
{
_id: ...,
userId: {_id: 1, fullName: 'Jack'}, // <-------- already have
title: 'Chat A'
},
...
]
*/

Mongoose: Count array elements

I have the following Schema with a array of ObjectIds:
const userSchema = new Schema({
...
article: [{
type: mongoose.Schema.Types.ObjectId,
}],
...
},
I will count the array elements in the example above the result should be 10.
I have tried the following but this doesn't worked for me. The req.query.id is the _id from the user and will filter the specific user with the matching article array.
const userData = User.aggregate(
[
{
$match: {_id: id}
},
{
$project: {article: {$size: '$article'}}
},
]
)
console.log(res.json(userData));
The console.log(article.length) give me currently 0. How can I do this? Is the aggregate function the right choice or is a other way better to count elements of a array?
Not sure why to use aggregate when array of ids is already with user object.
Define articles field as reference:
const {Schema} = mongoose.Schema;
const {Types} = Schema;
const userSchema = new Schema({
...
article: {
type: [Types.ObjectId],
ref: 'Article',
index: true,
},
...
});
// add virtual if You want
userSchema.virtual('articleCount').get(function () {
return this.article.length;
});
and get them using populate:
const user = await User.findById(req.query.id).populate('articles');
console.log(user.article.length);
or simply have array of ids:
const user = await User.findById(req.query.id);
console.log(user.article.length);
make use of virtual field:
const user = await User.findById(req.query.id);
console.log(user.articleCount);
P.S. I use aggregate when I need to do complex post filter logic which in fact is aggregation. Think about it like You have resultset, but You want process resultset on db side to have more specific information which would be ineffective if You would do queries to db inside loop. Like if I need to get users which added specific article by specific day and partition them by hour.

Mongoose - How to set default value if new value is not in available enum list

I have the following schema:
const userSchema = new mongoose.Schema({
profile: {
name: {
type: String,
default: "",
enum: ["", "Mike", "John", "Bob"]
}
}
}
I would like to ensure that when a user.save action is triggered and provided name variable is not in the list of available enum values, validation does not fail, but sets the value to default.
For example, in Node:
User
.findById(req.user.id)
.then(user => {
user = Object.assign(user, { name: "Sam" })
return user.save()
})
This will fail validation with Sam is not a valid enum value for path profile.name, but the ask is to have value fallback to an empty string:
{
name: ""
}
I tried tapping into Mongoose pre validate hook, but cannot seem to access provided values and documentation has not been helpful.
maybe you can use a pre save hooks
var schema = new Schema(..);
schema.pre('save', function(next) {
// verify here
next();
});

Get collection by user id in MongoDB

The biggest problem is I'm totally new to MongoDB. I know how to do it in SQL but am unable to shift my thinking into NoSQL. I have this model:
var accountSchema = new mongoose.Schema({
isPremium: Boolean,
website: []
});
I'm using mongoose so it creates Id, username, and password automatically. My registered user looks like this:
{
_id: ObjectId('5a79c89b59b6042a5d89584b'),
websites: ['a.com', 'b.com', 'c.com'],
username: 'a#a.a',
isPremium: false,
hash:
'a long hash',
salt: 'a long salt',
__v: 0,
};
I want want to write out the websites array. I want to grab only the websites under a certain user. (Don't want others to just see all websites).
How would I do it? Would I pass the userId after a click or make it in a session? And would the 'query' look like?
You can do it like this.
First define your model as a separate module
var mongoose = require('mongoose');
const accountSchema = mongoose.Schema(
{
isPremium: Boolean,
website: []
});
module.exports = mongoose.model('account',accountSchema);
Then you can use this model everywhere in your code
const account = require('yourModulePath');
account.findOne({YouSearchParameters}).
then((account) => {
// let websites = account.website
// Do you logic
})
You also can filter result with .select
account.findOne({YouSearchParameters}).select({ "website": 1, "_id": 0}).then((account)
So you will just get your array of websites

Mongoose: Merging two documents that reference each other

I am currently trying to learn how to work with NoSQL, coming from a relational database background. In this project, I am using Express with Mongoose.
I am struggling with callbacks as I try to merge two models together, which reference each other. I am trying to edit each item in a group of one model (Ribbits) to contain the attributes of another (Users who posted a Ribbit). Because the call to find the User associated with a Ribbit is asynchronous, I am unable to return the collection of edited Ribbits (with user info).
In my website, I have ribbits (a.k.a. tweets) which belong to users. Users can have many ribbits. In one of my pages, I would like to list all of the ribbits on the service, and some information associated with the user who posted that ribbit.
One solution I found was embedded documents, but I discovered that this is, in my case, limited to showing ribbits which belong to a user. In my case, I want to start by getting all of the ribbits first, and then, for each ribbit, attach info about who posted that.
Ideally, I'd want my schema function to return an array of Ribbit objects, so that I can then render this in my view.
// models/user.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var userSchema = Schema({
username: String,
email: String,
password: String,
name: String,
profile: String,
ribbits: [{
type: Schema.Types.ObjectId,
ref: 'Ribbit',
}]
});
module.exports = mongoose.model('User', userSchema);
// models/ribbit.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
User = require('./user');
var ribbitSchema = Schema({
content: { type: String, maxlength: 140 },
created: { type: Date, default: Date.now() },
owner: { type: Schema.Types.ObjectId, ref: 'User' },
});
ribbitSchema.methods.getOwnerObj = function(cb) {
return User.findOne({ _id: this.owner }, cb);
}
ribbitSchema.statics.getAllRibbits = function(cb) {
this.find({}, function(err, ribbits) {
console.log('Before Transform');
console.log(ribbits);
ribbits.forEach(function(ribbit) {
ribbit.getOwnerObj(function(err, owner) {
ribbit = {
content: ribbit.content,
created: ribbit.created,
owner: {
username: owner.username,
email: owner.email,
name: owner.name,
profile: owner.profile,
}
};
});
});
});
}
module.exports = mongoose.model('Ribbit', ribbitSchema);
If I understand correctly, you can use Mongoose populate method for this scenario:
ribbitSchema.statics.getAllRibbits = function(cb) {
this.find({}).populate('owner').exec(function(err, ribbits){
console.log(ribbits[0].owner)
return cb(err, ribbits);
})
}

Categories

Resources