SailsJS Perform sorting on populate data - node.js

Here is my current event model:
module.exports = {
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
client: {
model: 'client',
required: true
},
location: {
model: 'location',
required: true
}
}
};
The client Model:
module.exports = {
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
address: {
type: 'string'
},
clientContact: {
model: 'user',
required: true
}
}
};
So how do I implement sorting based on the client name and also have the skip and limit property(for pagination) to work along with it.
I tried using the following query:
Event.find({ id: ['583eb530902db3d926345215', '583eb6dd91b972ee26dd97b1'] },
{ select: ['name', 'client'] })
.populate('client', { sort: 'name DESC' })
.exec((err, resp) => resp.map(r => console.log(r.name, r.client)));
But this does not seem to do it.

Waterline doesn't support sorting a result by child records like this. If client was a collection of child records attached to an event, then your code would sort each of the returned Event record's populated client array by name, but I'm assuming in this case client is a singular association (i.e. model: 'client').
If what you want is an array of Event records sorted by the name of their client, you can do it relatively easily using Lodash after you retrieve the records:
var _ = require('lodash');
Event.find(queryCriteria).populate('client').exec((err, resp) => {
if (err) { /* handle error */ }
var sortedRecords = _.sortBy(eventRecords, (record) => { return record.client.name; });
});

Related

Concurrent WriteConflict with MongoDB and NodeJs

I'm creating an application that serves transportation order, while I'm stress testing the application by injecting about 100 orders simultaneously, the mongodb hits WriteConflict due to the numeric sequence id (nid it is required) that I created using the hook. I have tried to add ?retryWrites=true&w=majority into the connection URI and set those in the transaction level, but none of those work. May I know any parts that I'm missing and should give a try (Knowing that is critical section, and using lock/queue should fix, but want to know any other way)? Thank you very much.
The schema for sequence and order (will keep _id that default created, and mongoose did the thing so haven't mention in the schema)
const SequenceSchema = new Schema<ISequenceDoc>(
{
collectionName: {
type: String,
trim: true,
required: true
},
nid: {
type: Number,
required: true
}
},
{
collection: 'sequences',
timestamps: true
}
);
const OrderSchema = new Schema<IOrderDoc>(
{
name: {
type: String,
trim: true,
required: true
},
nid: {
type: Number
}
},
{
collection: 'orders',
timestamps: true
}
);
Pre saving hook to assign an numeric id index of hex id to the schema
OrderSchema.pre('save', async function(next) {
if (this.isNew) {
const data = await this.db.model<ISequenceDoc>('Sequence').findOneAndUpdate(
{
collectionName: this.collection.collectionName
},
{
$inc: { nid: 1 }
},
{
upsert: true,
new: true,
session: this.$session()
}
);
this.set('nid', data.nid);
}
next();
});
The code that create the order model and save
async createOrder(orderInfo) => {
const session = await this.connection.startSession();
session.startTransaction({
readConcern: {
level: 'majority'
},
writeConcern: {
w: 'majority',
j: true
}
});
......
await order.save({session});
await session.commitTransaction();
return order;
}
The problem is that all of your transactions modify the same document in the findOneAndUpdate.
Try acquiring your sequence number before starting the transaction.

Joining collections in mongodb with node js

I have two collections in mongodb "components" and "airframes" which I am trying to join together (with a one to many relationship). I have the following code which gets the airframe and component data separately from the database, however after days of effort, I cannot figure out how to join the two together. I assume I need to use $lookup to achieve the desired result but any assistance in constructing the code would be greatly appreciated.
my models are as follows and I am trying to join all the component records under the associated Airframe. the airframe field on the Component holds the related Airframes' id.
const airframeSchema = mongoose.Schema({
name: { type: String, required: true },
sNumber: { type: String, required: true },
aStatus: { type: String, required: true },
components: [ {
type: mongoose.Schema.Types.ObjectId,
ref: 'Component' } ]
});
module.exports = mongoose.model('Airframe', airframeSchema);
const componentSchema = mongoose.Schema({
name: { type: String, required: true },
serial: { type: String, required: true },
type: { type: String, required: true },
airFrame: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Airframe'},
});
module.exports = mongoose.model('Component', componentSchema);
the AirFramesService is as follow. I would like to join the component data under a array called "component".
getAirframes() {
this.http
.get<{ message: string; airframes: any }>("http://3.135.49.46:8080/api/airframes")
.pipe(
map(airframeData => {
return airframeData.airframes.map(airframe => {
return {
name: airframe.name,
sNumber: airframe.sNumber,
aStatus: airframe.aStatus,
id: airframe._id,
};
});
})
)
.subscribe(transformedAirframes => {
this.airframes = transformedAirframes;
this.airframesUpdated.next([...this.airframes]);
});
}
getAirframeUpdateListener() {
return this.airframesUpdated.asObservable();
}
getAirframe(id: string) {
return this.http.get<{ _id: string; name: string; sNumber: string ; aStatus: string}>(
"http://3.135.49.46:8080/api/airframes/" + id
);
}
The airframes route code is as follows:
router.get("", (req, res, next) => {
Airframe.find().then(documents => {
res.status(200).json({
message: "Airframes fetched successfully!",
airframes: documents
});
});
});
and here is the code within the ts component file that gets the airframe data is as follows.
constructor( public airframesService: AirframesService) {
this.airframesService.getAirframes();
this.airframesSub = this.airframesService.getAirframeUpdateListener()
.subscribe((airframes: Airframe[]) => {
this.isLoading = false;
this.airframes = airframes;
}, 0);
});
}
the desired outcome would be the following (at the moment I only get the airframe data):
{
_id: "123"
name: "Airframe01"
sNumber: "757"
aStatus: "Active"
id: "5e8052ad1fa18f1c73524664"
components: [
{
name: "Left Tank",
serial: "3456789",
type: "Landing Gear",
airFrame: "5e8052ad1fa18f1c73524664"
},
{
name: "Right Tank",
serial: "45678",
type: "Landing Gear",
airFrame: "5e8052ad1fa18f1c73524664"
}
]
}
Your document structure already established a one-to-many kinda relationship between the two models, you can use Mongoose population to get the join you described in the question. The populate code should be somewhere in the airframes route like this:
router.get("", (req, res, next) => {
// Notice the populate() method chain below
Airframe.find().populate('components').then(documents => {
// The documents output should have their "components" property populated
// with an array of components instead of just Object IDs.
res.status(200).json({
message: "Airframes fetched successfully!",
airframes: documents
});
});
});
You can read more about Mongoose population here.

I am trying to get data from the related tables in the database using sails js. But I always get undefined error. Thank you

TransmitterController
transmitter: function (req, res) {
Transmitter.find().populate('TrceiverId').exec(function (errr, transmitter) {
Tranceiver.find().exec(function (err, tranceiver) {
res.view('pages/transmitter', {layout: "layouts/layout", 'transmitter': transmitter, 'tranceiver': tranceiver})
});
})
},
I'm trying to pull data from the database but the answer is: undefined
Model tranceiver
module.exports = {
datastore: 'mysql',
tableName: 'tranceiver',
attributes: {
id: { type: 'number', unique: true, autoIncrement: true, columnName: 'trnsId' },
Name: { type: 'string', required: true, columnName: 'Name' },
Feature: { type: 'string', required: true, columnName: 'Feature'}
},
};
Model Transmitter
module.exports = {
datastore:'mysql',
tableName:'transmitter',
attributes: {
id:{type:'number',unique:true,autoIncrement:true,columnName:'trnsId'},
Name:{type:'string',required:true,columnName:'Name'},
TrceiverId:{model:'Tranceiver'}
},
};
I am waiting for your answers
Check if your collection transmitter has correct TrceiverId in them. I tried same with mongodb and got correct result when used correct TrceiverId in my collections.

How to trigger a function whenever a mongoose document is updated

I have a user model schema in mongoose which contains a list of friends and groups and stats info like so...
var user = new Schema({
email: { type: String, required: true, unique: true },
password: { type: String, required: true, select: false },
roles: [{ type: String, required: true }],
friends: [{ type: Schema.Types.ObjectId, ref: 'User' }],
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
stats : {
nbrFriends: { type: Number, required: false },
nbrGroups: { type: Number, required: false }
}
}, {
timestamps: true
});
I need to update the users stats whenever a change is made to the friends or groups fields to contain the new number of friends or groups etc. For example, when the following function is called on a user:
var addGroup = function(user, group, cb) {
user.groups.push(group);
User.findOneAndUpdate({ _id: user._id }, { $set: { groups: user.groups }}, { new: true }, function(err, savedResult) {
if(err) {
return cb(err);
}
console.log('updated user: ' + JSON.stringify(savedResult));
return cb(null, savedResult);
});
};
How could I make sure the stats is automatically updated to contain the new number of groups the user has? It seems like a middleware function would be the best approach here. I tried the following but this never seems to get called...
user.pre('save', function(next) {
var newStats = {
nbrGroups: this.groups.length,
nbrPatients: this.friends.length
};
this.stats = newStats;
this.save(function(err, result) {
if(err) {
console.log('error saving: ' + err);
} else {
console.log('saved');
}
next();
});
});
You need to use the middleware a.k.a. hooks:
Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions.
See the docs:
http://mongoosejs.com/docs/middleware.html
From version 3.6, you can use change streams.
Like:
const Users = require('./models/users.js')
var filter = [{
$match: {
$and: [{
$or:[
{ "updateDescription.updatedFields.friends": { $exists: true } },
{ "updateDescription.updatedFields.groups": { $exists: true } },
]
{ operationType: "update" }]
}
}];
var options = { fullDocument: 'updateLookup' };
let userStream = Users.watch(filter,options)
userStream.on('change',next=>{
//Something useful!
})
You should update with vanilla JS and then save the document updated to trigger the pre-save hooks.
See Mongoose docs
If you have many keys to update you could loop through the keys in the body and update one by one.
const user = await User.findById(id);
Object.keys(req.body).forEach(key => {
user[key] = req.body[key];
}
const saved = await user.save();

Is there any way to populate created data in sails js (mongodb)

i have a model named Business which have a feild address with type modal: Address,
i want to send the address object with business data instead of only id when the business is created..
simple create method is just return id of created address object
i want to populate the response
that's why i am using this:
register: function(req, res) {
const values = req.body;
Business.create(values).then(function(response) {
Business.findOne({ id: response.id }).populate('address').then(function(response) {
res.ok(response);
});
});
},
But i dont think this is a proper way.. i am querying database again to populate the result. this is giving me exactly what i want.
i want to use something like this:
register: function(req, res) {
const values = req.body;
Business.create(values).then(function(response).populate('address') {
res.ok(response);
});
},
or anything that remove the second query to the database
i know this is crazy... but please help
The model is
module.exports = {
schema: true,
attributes: {
name: {
type: 'string'
},
description: {
type: 'string'
},
address: {
model: 'Address'
},
email: {
type: 'email',
unique: true
},
landline: {
type: 'string'
},
logo: {
model: 'Image'
},
status: {
type: 'string',
enum: ['open', 'closed'],
defaultsTo: 'open'
},
uniqueBusinessName: {
type: 'string',
unique: true
},
},
};
The create class method does not support populate, so the way you're doing this is correct. If you'd like to propose this as a feature, please check out the Sails.js project's guidelines for proposing features and enhancements!

Resources