I'm trying to do a nested query as per the instructions and examples here:
https://docs.strongloop.com/display/public/LB/Include+filter
https://github.com/strongloop/loopback/issues/735
but it is not working like I expected it to. I have an Account, that can have many Branches, that can have many Incidents, Risk Assessments and Claims. I have created a custom endpoint for the Branch to query the related Incidents, Risk Assessments, and Claims, but I can't get the information from the Account level.
Here is the working code for the Branch:
Branch.find({
where: {
id: id
},
include: [
{
relation: 'incidents',
scope: {
where: {
incidentDate: {
between: [
beginDate,
today
]
}
}
}
},
{ relation: 'riskAssessments' },
{ relation: 'claims' }
}, function (err, obj) {
if (err) return cb(err);
cb(null, obj);
});
Here is the code that I would expect to work for the Account:
Account.find({
where: {
id: id,
},
include: {
relation: 'branches',
scope: {
include: [
{
relation: 'incidents',
scope: {
where: {
incidentDate: {
between: [
beginDate,
today
]
}
}
}
},
{ relation: 'riskAssessments' },
{ relation: 'claims' }
]
}
}
}, function (err, obj) {
if (err) return cb(err);
cb(null, obj);
});
However, this only gives me the Account and an empty array of Branches. If I run it with this code:
Account.find({
where: {
id: id,
},
include: {
relation: 'branches',
}
},function (err, obj) {
if (err) return cb(err);
cb(null, obj);
});
It gives me the Account with the related Branches. Whenever I add the
scope: {
include: {
relations: 'riskAssessments',
}
}
it doesn't work, and returns the empty Branch array. I can add other parameters in the scope like fields, where, etc and they work as expected, it's just when I add the include.
The issue I was running into is related to a bug that can be found here: https://github.com/strongloop/loopback/issues/2356
In a nutshell, ensure none of your IDs are 0 or you will run into this issue.
Related
I'm learning ho to develop GraphQL service with express, express-graphql, **graphql, mongoose,
db.collection.find has an optional query parameter that specifies selection filter using query operators.
I wonder if it is possible to define a schema in which to define an argument for a query field that ultimately it is passed as it is to the collection find methods.
for example I expect that the graphql query:
{ todosQuerable(query: {title: "Andare a Novellara"})
{ _id, title, completed }
}
responds with:
{
"data": {
"todos": [
{
"title": "Andare a Novellara",
"completed": false
}
]
}
}
since in mongo
> db.Todo.find({title: 'Andare a Novellara'})
{ "_id" : ObjectId("600d95d2e506988bc4430bb7"), "title" : "Andare a Novellara", "completed" : false }
I'm thinking something like:
todosQuerable: {
type: new graphql.GraphQLList(TodoType),
args: {
query: { type: <???????????????> },
},
resolve: (source, { query }) => {
return new Promise((resolve, reject) => {
TODO.find(query, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
I have made a few attempts but have not been able to get an idea of which type I should use in this case
ho help reproduce the problem here the source repository of my tests
Please note that this works fine:
todosByTitle: {
type: new graphql.GraphQLList(TodoType),
args: {
title: { type: graphql.GraphQLString },
},
resolve: (source, { title }) => {
return new Promise((resolve, reject) => {
TODO.find({title: {$regex: '.*' + title + '.*', $options: 'i'}}, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
but what I'm looking for is something more generic: I would like to grab graphql field argument named query and pass it as is to the the query parameter of the mongo collection find.
So the good news is you can do whatever you want. The bad news is that:
You have to do it yourself
You have to add every searchable field, so you'll probably end up with two copies of the Todo object here.
The type you're looking for is just a custom input object type like this:
Notice the GraphQLInputObjectType below is different from GraphQLObjectType.
var TodoQueryType = new graphql.GraphQLInputObjectType({
name: 'TodoQuery',
fields: function () {
return {
_id: {
type: graphql.GraphQLID
},
title: {
type: graphql.GraphQLString
},
completed: {
type: graphql.GraphQLBoolean
}
}
}
});
todosQuerable: {
...
type: new graphql.GraphQLList(TodoType),
...
args: {
query: { type: TodoQueryType },
},
...
}
These two queries work great!
(this is me using aliases so I can make the same query twice in one call)
{
titleSearch: todosQuerable(query:{ title:"Buy orange" }) {
_id
title
completed
}
idSearch: todosQuerable(query:{ _id:"601c3f374b6dcc601890048d" }) {
_id
title
completed
}
}
Footnote:
Just to have it said, this is generally a GraphQL anti-pattern, as this is building an API based on your database choices, rather than as a client-driven API.
Regex Edit as requested:
If you're trying to do regular expression lookups, you have to figure out how to programmatically convert your strings into regular expressions. i.e. your input is a string ("/Novellara/"), but mongoose requires passing a RegExp to do wildcards (/Novellara/, no quotes).
You can do that a number of ways, but I'll show one example. If you change your input fields to use two properties of value & isExpression, like below, you can do it, but you have to specifically craft your query, since it's no longer just a passthrough.
var ExpressableStringInput = new graphql.GraphQLInputObjectType({
name: 'ExpressableString',
fields: {
value: {
type: graphql.GraphQLString
},
isExpression:{
type: graphql.GraphQLBoolean,
defaultValue: false,
}
}
})
var TodoQueryType = new graphql.GraphQLInputObjectType({
name: 'TodoQuery',
fields: function () {
return {
_id: {
type: graphql.GraphQLID
},
title: {
type: ExpressableStringInput
},
completed: {
type: graphql.GraphQLBoolean
}
}
}
});
// resolver
todosQuerable: {
type: new graphql.GraphQLList(TodoType),
args: {
query: { type: TodoQueryType },
},
resolve: async (source, { query }) => {
const dbQuery = {};
if (query.title.isExpression) {
dbQuery.title = new RegExp(query.title.value);
} else {
dbQuery.title = query.title.value;
}
return new Promise((resolve, reject) => {
TODO.find(dbQuery, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
your query would then look like
query {
todosQuerable(query:{ title: { value: "Buy.*", isExpression: true }}) {
_id
title
completed
}
}
This query makes sense in my mind. If I think about the form you would show to a user, there is probably an input box and a checkbox that says "is this a regular expression?" or something, which would populate this query.
Alternatively, you could do like string matching: if the first and last characters are "/", you automagically make it into a regex before passing it into mongoose.
I am trying to delete a post object from a user model, I hold these refrences to the post they have created, this is how I am trying to currently pull the post
userModel.findOneAndUpdate(
{ email: req.query.email, posts: req.query.postid },
// { $pull: { posts: req.query.postid } },
{ $pull: { posts : { number: mongoose.Types.ObjectId(req.query.postid) } }},
{ new: true },
function (error, user) {
if (error) {
res.json("error in /testing backend ===",error)
}
console.log(`Post id ===== ${req.query.postid}`);
console.log(`Email===== ${req.query.email}`);
console.log(`returning user====${user}`)
res.json('Successfully updated user');
}
);
this is how I have created the post
userModel.findOne({ email: req.body.author }, function(error, user) {
const locationURL = req.files.map((item) => item.location);
postModel.create({ ...req.body, image: locationURL }, (error, returnedDocuments) => {
if (error) {
throw new Error(error);
}
user.posts.push({ number: returnedDocuments._id, title: req.body.title, image: locationURL });
user.save((err) => {
console.log(err);
});
});
I originally had only 1 item pushed into the user model, but added a few more items, then I was having issues pulling the object, thanks for your help.
this is from my DB as to my posts array
For an array of objects, you can pull your desired document using the positional operator { "<array>.$" : value }.
{ $pull: { posts.$.number : req.query.postid }}
You can check out the docs on positional operators to learn more.
I'm working on an app using MongoDB and Express.js.
I am creating a post handler that updates a toy (found by its id) with a new proposed name for the toy (which is pushed onto a nameIds array that contains the ids of the other proposed names):
router.post('/names', (req, res) => {
const toyId = req.body.toyId;
const name = req.body.newName;
mdb.collection('names').insertOne({ name }).then(result =>
mdb.collection('toys').findAndModify({
query: { id: toyId },
update: { $push: { nameIds: result.insertedId } },
new: true
}).then(doc =>
res.send({
updatedToy: doc.value,
newName: { id: result.insertedId, name }
})
)
)
});
However, when I test this, I receive this error:
name: 'MongoError',
message: 'Either an update or remove=true must be specified',
ok: 0,
errmsg: 'Either an update or remove=true must be specified',
code: 9,
codeName: 'FailedToParse'
I'm not new to MongoDB, but this simple call is baffling me.
Thanks for any help you can provide!
That is the format for mongo shell. Using mongo driver you would call with these arguments:
.findAndModify( //query, sort, doc, options, callback
{ id: toyId }, //query
[], //sort
{ $push: { nameIds: result.insertedId } }, // doc update
{ new: true }, // options
function(err,result){ //callback
if (err) {
throw err
} else {
res.send({
updatedToy: result.value,
newName: { id: result.insertedId, name }
})
}
}
)
I have two templates that I'd like to render on the same page. One is a template that lists recent items; the other one lists items that are $text search results.
Data for each template is from a separate subscription. The problem is, minimongo doesn't support $text search, so I can't use $text to limit results from the client once the subscriptions are returned. That's a problem because both subscriptions are mixed together at the client side, so both my search results and recent items results look weird, because they each draw from both subscriptions.
I'm attempting to deal with it by using Iron Router to specify which template subscribes to which publication. However, my code doesn't work.
on the server, the file app.js, two separate publications:
if (Meteor.isServer) {
Meteor.publish("myitems", function () {
return Items.find();
});
Items._ensureIndex({
"itemName": "text",
//"tags" : "text"
});
Meteor.publish("search", function (searchValue) {
if (!this.userId) {
this.ready();
return;
}
return Items.find(
{
createdBy: this.userId,
$text: {$search: searchValue},
retired: {$ne: true}
},
{
fields: {
score: {$meta: "textScore"}
},
sort: {
score: {$meta: "textScore"}
}
}
);
});
}
client side code:
helper for the recent items template:
Template.myitems.helpers(
{
items: function () {
var d = new Date();
var currentUser = Meteor.userId();
return Items.find(
{
createdBy: currentUser,
createdAt: {
$gte: new Date(d.setDate(d.getDate() - 30))
}
},
{
sort: {
createdAt: -1
},
limit: 5
});
}
});
helper for the search results template:
Template.searchResults.helpers({
searchitems: function () {
if (Session.get("searchValue")) {
return Items.find({
}, {
sort: {"score": -1, "itemName": -1},
//limit: 10
});
} else {
//return Items.find({});
}
}
});
}
onCreated subscription for each template, separately:
Template.myitems.onCreated (function () {
Meteor.subscribe('myitems');
});
Template.searchResults.onCreated (function () {
Meteor.subscribe('search');
});
Router controller configuration: yes you'll see that it attempts to subscribe as well, but it fails anyway, so there's no duplicate subscription to "myitems"
itemsController = RouteController.extend({
//waitOn: function() {
// return [
// Meteor.subscribe('myitems')
// ];
//},
//data: function() {
// //return { items : Items.find({}), item_id : this.params._id }
// return {items: Items.find()};
//},
action: function() {
this.render('items');
this.render('searchitems', {to: 'region1'});
this.render('myitems', {
to: 'region3',
waitOn: function() {
return [
Meteor.subscribe('myitems')
];
},
data: function(){
return {items: Items.find()};
}
});
}
});
The above iron router code doesn't attempt to subscribe to the search publication. It attempts to subscribe to the recent items ('myitems') publication, but somehow the returned "items" is empty. The issue is not due to any wrong setting in the publication, because the commented out code works: if it were uncommented, then "items" do get returned and isn't empty, even if I don't use onCreated to subscribe to it.
My questions are:
what's wrong with the above code? I know that the subscription to "myitems" fail from the Iron Router. The subscription to "myitems" succeeds in the "onCreate", but the search results also draws from "myitems", instead of drawing from "searchResults" only.
assuming I can fix the above code, is Iron Router the way to go to solve my original problem: the search results subscription and the recent items subscription need to be separate, although the two templates are to be rendered on the same webpage?
Usking shiki/kaiseki to interact with the Parse REST api. I'm trying to get all As that do not have a related B for a user.
I know I can get Bs for a user:
parseClient.getObjects('B', {
where: {
user: {
__type: "Pointer",
objectId: "id here",
className: "_User"
}
},
keys: 'a'
}, callback)
And then pluck object ids to get the bad A ids.
aIds = [ Bs ].map(function (b) {
return b.a.objectId
})
And then query for As negative matching the id.
parseClient.getObjects('A', {
where: {
objectId: { $nin: aIds }
}
}, callback)
But a user can have more B's than the query limit of 200. How can I rewrite it to use a subquery?
$dontSelect
Nested key param doesn't work, which makes something like this fall flat.
parseClient.getObjects('A', {
where: {
objectId: {
$dontSelect: {
query: {
className: 'B',
where: { user query },
},
key: 'a.objectId
}
}
}
}, callback)
// { code: 105, error: 'invalid field name: ' }
$notInQuery
There doesn't seem to be a way to put $notInQuery in the root of the where or to reference the current object.
parseClient.getObjects('A', {
where: {
$notInQuery: {
className: 'B',
where: { user query },
key: 'a'
}
}
}, callback)
// { code: 102, error: 'Invalid key $notInQuery for find' }
Help!
It is a limitation of the Parse model, and the only way I have found around it is to adjust your schema accordingly.
One way you could do it is with a relation on A that contains all related User pointers and then do a $ne against the relation field:
where: {
'relationColumn' : {
'$ne': {
'__type': 'Pointer',
'className': '_User',
'objectId': 'user b id here'
}
}
}