Loopback - remote method and flattening related model query result - javascript

I'm developing an app using Loopback 3. I've created a remote method on on the ServiceEvaluation Model to return a list of ServiceEvaluations and related properties in the Service Model.
ServiceEvaluation.evaluationList = function(cb) {
ServiceEvaluation.find({
fields: {
status: true,
createdAt: true,
serviceId: true
},
include: {
relation: 'rel_ServiceEval_Service',
scope: {
fields: {
serviceName: true,
supplierId: true
}
}
}
}, function(err, response) {
cb(null, response);
});
};
The above works when called from the API Explorer returning;
{
"list": [
{
"status": "Draft",
"serviceId": "5b8e215d81c76325b409b960",
"createdAt": "2018-09-04T06:08:29.623Z",
"rel_ServiceEval_Service": {
"serviceName": "Workplace software and SaaS",
"id": "5b8e215d81c76325b409b960",
"supplierId": "5b8e215d81c76325b409b949"
}
}, ...
However, rather than returning an array of objects with embedded objects, I want to return a array of flattened objects to display in a data grid. The below is an attempt to do so.
ServiceEvaluation.evaluationList = function(cb) {
ServiceEvaluation.find({
fields: {
status: true,
createdAt: true,
serviceId: true
},
include: {
relation: 'rel_ServiceEval_Service',
scope: {
fields: {
serviceName: true,
supplierId: true
}
}
}
}, function(err, response) {
var responseLength = response.length;
var myEntry = {};
var myList = [];
for (var i = 0; i < responseLength; i++) {
myEntry.status = response[i].status;
myEntry.createdAt = response[i].createdAt;
myEntry.serviceName = response[i].rel_ServiceEval_Service.serviceName;
myEntry.supplierId = response[i].rel_ServiceEval_Service.supplierId;
myList.push(myEntry);
}
cb(null, myList);
});
};
The result of this is that the remote method seems not to find the fields in the rel_ServiceEval_Service.
{
"list": [
{
"status": "Draft",
"createdAt": "2018-09-04T06:20:40.889Z"
}, ...
I've resorted to flattening the return value in a service on the client side but this isn only a temp solution while in Dev. Any guidance on how to do this in the remote method?

you need to use .toJSON() to serialize the returned data:
ServiceEvaluation.evaluationList = function(cb) {
ServiceEvaluation.find({
fields: {
status: true,
createdAt: true,
serviceId: true
},
include: {
relation: 'rel_ServiceEval_Service',
scope: {
fields: {
serviceName: true,
supplierId: true
}
}
}
}, function(err, response) {
var myEntry = {};
var myList = [];
async.map(response, function(singleItem,callback){
serializedSingleItem = singleItem.toJSON()
var myEntry = {status: serializedSingleItem.status, createdAt: serializedSingleItem.createdAt, serviceName: serializedSingleItem["rel_ServiceEval_Service"]["serviceName"], supplierId: serializedSingleItem["rel_ServiceEval_Service"]["supplierId"]}
callback(null, myEntry)
}, function(err, myList){
cb(null, myList)
})
});
};

Related

Given this model, how to delete a nested array item with updateOne (mongoose)?

I am trying to remove an array item with "updateOne" method but my query is not matching the right record in the model structure that I have. Given an email, I would like to find the array item with the provided email and pulls it out, remove it. (There is no array item with the same email)
My model is like so:
var mongoose = require('mongoose');
var teamMemberModelSchema = new mongoose.Schema({
_id: false,
"email": {
"type": String,
"required": true,
"minlenght": 5,
"maxheight": 50
},
"name": {
"type": String,
"required": true,
"minlenght": 5,
"maxheight": 256
},
"role": {
"type": String,
"required": true,
"minlenght": 20,
"maxheight": 256
},
"twitter": {
"type": String,
"required": true,
"minlenght": 1,
"maxheight": 100
},
"facebook": {
"type": String,
"required": true,
"minlenght": 1,
"maxheight": 100
},
"linkedin": {
"type": String,
"required": true,
"minlenght": 1,
"maxheight": 100
},
});
var teamModelSchema = new mongoose.Schema({
"title": {
"type": String,
"required": true,
"minlenght": 5,
"maxheight": 20
},
"headline": {
"type": String,
"required": true,
"minlenght": 5,
"maxheight": 30
},
"description": {
"type": String,
"required": true,
"minlenght": 5,
"maxheight": 80
},
"members": [teamMemberModelSchema]
}, { collection: 'team' });
teamModelSchema.set('collection', 'team');
mongoose.model('team', teamModelSchema)
And the approach that I am trying is the following:
module.exports.removeMember = function (req, res) {
const email = req.params.email;
const query = { "members.email": email };
const pull = { $pull: { "members.$.email": email } };
try {
var message = teamMsg.teamMemberRemoveSuccess;
TeamModel.updateOne(query, pull);
responseUtilities.sendJSON(res, false, { message: message });
} catch (err) {
console.log(err.message);
responseUtilities.sendJSON(res, err, { message: err.message });
}
};
It executes with no errors, but nothing is updated.
I have tried some others alternatives with "FindOneAndUpdate" and "FindOneAndRemove" but, I could not find a solution.
Any ideas?
You can use findOneAndUpdate with $pull operator for this task.
For removing items from an array of documents you can check MongoDb docs
You need to use await or then block to query. I used await, and made the function asynchronous by adding async keyword. We also need empty query object.
I also added the new: true option, to return the updated object to check if the item is deleted.
You need to handle the case where no document matches, I added a TODO for you.
module.exports.removeMember = async function(req, res) {
const email = req.params.email;
const query = {};
const pull = {
$pull: {
members: {
email: email
}
}
};
const options = {
new: true
};
try {
var message = teamMsg.teamMemberRemoveSuccess;
const result = await TeamModel.updateOne(query, pull, options);
console.log(result);
if (!result) {
//TODO: return 400-Bad Request or 404 - Not Found
} else {
responseUtilities.sendJSON(res, false, { message: message });
}
} catch (err) {
console.log(err.message);
responseUtilities.sendJSON(res, err, { message: err.message });
}
};
give this query a try:
db.collection.update(
{ 'members.email': 'email#address' },
{ $pull: { members: { email: 'email#address' } } },
{ multi: true }
)
Try using the update() method and async/await:
module.exports.removeMember = async function (req, res) {
const email = req.params.email;
console.log(`email = ${email}`) // Make sure correct value is coming thru
const query = { "members.email": email };
const pull = { $pull: { "members.$.email": email } };
const options = { multi: true }
try {
var message = teamMsg.teamMemberRemoveSuccess;
await TeamModel.update( query, pull, options );
responseUtilities.sendJSON(res, false, { "message": message });
} catch (err) {
console.log(err.message);
responseUtilities.sendJSON(res, err, { "message": err.message });
}
};

Is there any function in KeystoneJS to load only related items for one category?

I'm creating a website with tours, that must be specified by categories, but I don't know how to load only tours related to categories. I tried to load them with find().where() but I get all tours loaded in all 3 categories.
KeystoneJS doesn't have documentation about any sort methods, I found only two examples, that don't work for me.
My trips.js:
let keystone = require('keystone');
let async = require('async');
exports = module.exports = function (req, res) {
let view = new keystone.View(req, res);
let locals = res.locals;
// Set locals
locals.section = 'tours';
locals.filters = {
trip: req.params.trip,
};
locals.data = {
trips: [],
category: [],
};
view.on('init', function (next) {
keystone.list('TripCategory').model.find().sort('name').exec(function (err, results) {
locals.data.category = results;
next(err);
async.each(locals.data.category, function (category, next) {
keystone.list('Trip').model.find().where('category', category.name).exec(function (err, results) {
locals.data.trips = results;
console.log(locals.data.trips);
next(err);
});
});
});
});
view.render('trips');
};
My Trip.js:
let keystone = require('keystone');
let Types = keystone.Field.Types;
let Trip = new keystone.List('Trip', {
map: { name: 'title' },
singular: 'Trip',
plural: 'Trips',
autokey: { path: 'slug', from: 'title', unique: true },
});
Trip.add({
title: { type: String, required: true },
content: {
brief: { type: Types.Html, wysiwyg: true, height: 150 },
extended: { type: Types.Html, wysiwyg: true, height: 400 },
},
category: { type: Types.Relationship, ref: 'TripCategory' },
duration: { type: Types.Html, wysiwyg: true },
distance: { type: Types.Html, wysiwyg: true },
price: { type: Number },
images: { type: Types.CloudinaryImages },
coverImage: { type: Types.CloudinaryImage },
});
Trip.register();
My TripCategory.js:
let keystone = require('keystone');
let Types = keystone.Field.Types;
let TripCategory = new keystone.List('TripCategory', {
autokey: { from: 'name', path: 'slug', unique: true },
});
TripCategory.add({
name: { type: String, required: true, unique: true },
description: { type: Types.Html, wysiwyg: false, height: 500 },
});
TripCategory.relationship({ ref: 'Trip', path: 'trips', refPath: 'category' });
TripCategory.register();
You should just be able to use a regular find query, along with populate.
keystone.list('Trip').model.find()
.populate({path: 'category', options: {sort: {'name'}}})
.exec(function(err, results) {
locals.data.trips = results;
})
This will get all trips, along with their corresponding category info, and sort them by the category name. If this syntax gives you issues (due to keystonejs using an older version of mongoose) try some of the different syntax versions that have continued to evolve. Here's a post that details them

Loopback findOrCreate creates new records even if there is an existing record

As per my understanding of the loopback documentation Persistedmodel.findOrCreate should find a model according to the query and return it, or create a new entry in the database if the model does not exist.
What I have noticed in my case is that it creates a new entry irrespective of whether there is an existing entry.
Not sure what I am missing. Here is my code:
teams-joined.json
{
"name": "teamsJoined",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"teamID": {
"type": "string",
"required": true
},
"playerID":{
"type":"string",
"required":true
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}
teams-joined.js
let queryThatWorks = {where:{
and: [
{teamID: teamID}
]
}
};
let query = {where:{
and: [
{teamID: teamID},
{playerID: req.currentUser.id},
],
}
};
let joinTeamsData = {
teamID: teamID,
playerID: req.currentUser.id,
};
console.log(query.where,'query');
teamsJoined.findOrCreate(query, joinTeamsData,
function(err, data, created) {
console.log(data,created);
});
When I cal the API multiple times, this is what I get
{ and:
[ { teamID: 'bf36e0-93a5-11e8-a8f4-9d86f4dd79ee' },
{ playerID: '5b20887bb6563419505c4590' } ] } 'query'
{ teamID: 'bf36e0-93a5-11e8-a8f4-9d86f4dd79ee',
playerID: '5b20887bb6563419505c4590',
id: 5b61798534fa410d2b1d900a } 'data'
true 'created'
{ and:
[ { teamID: 'bf36e0-93a5-11e8-a8f4-9d86f4dd79ee' },
{ playerID: '5b20887bb6563419505c4590' } ] } 'query'
{ teamID: 'bf36e0-93a5-11e8-a8f4-9d86f4dd79ee',
playerID: '5b20887bb6563419505c4590',
id: 5b61798634fa410d2b1d900b } 'data'
true 'created'
I expected it to return false for created and just return the existing data.
This works fine when I use the 'queryThatWorks' query from my code sample.
You don't need to use and operator in your where clause. See findOrCreate.
{ where: { teamID: teamID, playerID: req.currentUser.id } }
You can include your data into your filter like that:
{ where: joinTeamsData }
To return specific fields in your return statement, you can use the fields option in your query.
Finally, try this:
let data = {
teamID: teamID,
playerID: req.currentUser.id,
};
let filter = { where: data };
teamsJoined.findOrCreate(filter, data,
function(err, instance, created) {
console.log(instance, created);
}
);

Should I be using a list (array) here in GraphQL?

[Previously titled "How to get 1 record from a list..."]
I am very new to GraphQL and trying to understand how to get 1 record from query.
This is the result of my current query:
{
"data": {
"todos": null
}
}
I am not sure what is wrong. I would like the result to be this:
{
"data": {
"todos": {
"id": 1,
"title": "wake up",
"completed": true
}
}
}
Here is my code that I've created as I try to learn GraphQL.
schema.js:
var graphql = require('graphql');
var TODOs = [
{
"id": 1,
"title": "wake up",
"completed": true
},
{
"id": 2,
"title": "Eat Breakfast",
"completed": true
},
{
"id": 3,
"title": "Go to school",
"completed": false
}
];
var TodoType = new graphql.GraphQLObjectType({
name: 'todo',
fields: function () {
return {
id: {
type: graphql.GraphQLID
},
title: {
type: graphql.GraphQLString
},
completed: {
type: graphql.GraphQLBoolean
}
};
}
});
var queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: function () {
return {
todos: {
type: new graphql.GraphQLList(TodoType),
args: {
id: { type: graphql.GraphQLID }
},
resolve: function (source, args, root, ast) {
if (args.id) {
return TODOs.filter(function(item) {
return item.id === args.id;
})[0];
}
return TODOs;
}
}
}
}
});
module.exports = new graphql.GraphQLSchema({
query: queryType
});
index.js:
var graphql = require ('graphql').graphql;
var express = require('express');
var graphQLHTTP = require('express-graphql');
var Schema = require('./schema');
var query = 'query { todos(id: 1) { id, title, completed } }';
graphql(Schema, query).then( function(result) {
console.log(JSON.stringify(result,null," "));
});
var app = express()
.use('/', graphQLHTTP({ schema: Schema, pretty: true }))
.listen(8080, function (err) {
console.log('GraphQL Server is now running on localhost:8080');
});
To run this code I just run node index from the root directory. How can I get one specific record returned by the records id?
You have the wrong type for the todos field of your queryType. It should be TodoType, not a list of TodoType. You're getting an error because GraphQL expects to see a list, but your resolver is just returning a single value.
By the way, I suggest passing the graphiql: true option to graphqlHTTP, which will let you use GraphiQL to explore your schema and make queries.

Push object to a mongoose array and check for duplicate fields inside the array objects

I am trying to push a notification object to a mongoDB array with the follow condition:
Check if there is a notification inside the array with the same postId
If yes: return that object
If no: create a new notification object
I'm using node with Mongoose ODM. In some other questions online they give this as an answer:
user.update({'notifications.postId': {$ne: post._id}}, {$push: { 'notifications': {type: 'POST', postId: post._id}}}, function(err, doc) {
.....
});
But this didn't work for me.
Below you find the structure of the mongodb document.
{
"_id": {
"$oid": "56631b22067ee41847dab6bb"
},
"unreadNotifications": 0,
"notifications": [
{
"type": "POST",
"postId": {
"$oid": "5666b78b61eb95bc03d47a71"
},
"_id": {
"$oid": "56782daa395840efd4ed3f7a"
},
"createdAt": {
"$date": "2015-12-21T16:49:46.903Z"
},
"events": []
}
]
"created": {
"$date": "2015-12-05T17:13:06.782Z"
},
"lastName": "Power",
"firstName": "John",
"__v": 0
}
Note that Schema#update method will not updating records because there are no matched records described by specified match condition.
Anyway, You can create your own method on your User Schema.
Example
UserSchema.method('sendNotification', function (post, done) {
var notification = null;
for (var i = 0, len = this.notifications.length ; i < len ; i++) {
notification = this.notifications[i];
if (notification.postId.equals(post._id)) {
return done(null, notification);
}
}
notification = {
type: 'POST',
postId: post._id
};
this.update({
$push: {
notifications: notification
}
}, function (e, doc) {
if (e) { return done(e); }
return done(null, notification);
});
});
You can use this method after model was created.
User.findOne({
email: 'john#example.com'
}).exec(function (e, john) {
john.sendNotification(post, function (e, notification) {
if (e) { console.log('Ah snap! There are an error.'); }
console.log('notification: ', notification);
});
});

Categories