How to use subcriteria in populate (for example : .populate('something',{select:[]})) in sails js - sails.js

.populate('something',{select:[]})) in sails js . for me its an ambiguous usage. Is there any alternate solution for the problem
I used select inside populate ... but it shows sub-criteria doesn't work in this version of sails
Banktransaction.find(newData).populate('project_id',{select:['project_name']}).exec((err,banktrans)=>{
if(err){
return res.json({
error : err
});
}
if(!banktrans){
return res.notFound();
}else{
return res.json({
'responseType':"success",
'responseMessage':"Banktransaction details founded successfully",
'result': banktrans
});
}
});
//result
{
"error": {
"name": "UsageError",
"code": "E_INVALID_POPULATES",
"details": "Could not populate `project_id` because of ambiguous usage. This is a singular (\"model\") association, which means it never refers to more than _one_ associated record. So passing in subcriteria (i.e. as the second argument to `.populate()`) is not supported for this association, since it generally wouldn't make any sense. But that's the trouble-- it looks like some sort of a subcriteria (or something) _was_ provided!\n(Note that subcriterias consisting ONLY of `omit` or `select` are a special case that _does_ make sense. This usage will be supported in a future version of Waterline.)\n\nHere's what was passed in:\n{ select: [ 'project_name' ] }"
}
}

You've probably figured it out by now, but .populate() is limited, and sometimes the documentation for sails is wrong. I wrote a helper specifically to populate based on 3rd-normal O2M/M2M association tables. My method also required model methods giving the basic config of each association's field name and a reference to its model class. It's NOT graceful, it's a separate query for each record, but perhaps some of the details of my approach may interest you:
My populate helper.
Example usage.

Related

Mutation cache update not working with vue-apollo and Hasura

I'm completely new to these technologies, and am having trouble wrapping my head around it, so bear with me. So, my situation is that I've deployed Hasura on Heroku and have added some data, and am now trying to implement some functionality where I can add and edit certain rows of a table. Specifically I've been following this from Hasura, and this from vue-apollo.
I've implemented the adding and editing (which works), and now want to also reflect this in the table, by using the update property of the mutation and updating the cache. Unfortunately, this is where I get lost. I'll paste some of my code below to make my problem more clear:
The mutation for adding a player (ADD_PLAYER_MUTATION) (same as the one in Hasura's documentation linked above):
mutation addPlayer($objects: [players_insert_input!]!) {
insert_players(objects: $objects) {
returning {
id
name
}
}
}
The code for the mutation in the .vue file
addPlayer(player, currentTimestamp) {
this.$apollo.mutate({
mutation: PLAYER_ADD_MUTATION,
variables: {
objects: [
{
name: player.name,
team_id: player.team.id,
created_at: currentTimestamp,
updated_at: currentTimestamp,
role_id: player.role.id,
first_name: player.first_name,
last_name: player.last_name
}
]
},
update: (store, { data: { addPlayer } }) => {
const data = store.readQuery({
query: PLAYERS
});
console.log(data);
console.log(addPlayer);
data.players.push(addPlayer);
store.writeQuery({ query: PLAYERS, data });
}
});
},
I don't really get the update part of the mutation. In most examples the { data: { x } } bit uses the function's name in the place of x, and so I did that as well, even though I don't really get why (it's pretty confusing to me at least). When logging data the array of players is logged, but when logging addPlayer undefined is logged.
I'm probably doing something wrong that is very simple for others, but I'm obviously not sure what. Maybe the mutation isn't returning the correct thing (although I'd assume it wouldn't log undefined in that case), or maybe isn't returning anything at all. It's especially confusing since the player is actually added to the database, so it's just the update part that isn't working - plus, most of the guides / tutorials show the same thing without really much explanation.
Okay, so for anyone as stupid as me, here's basically what I was doing wrong:
Instead of addPlayer in update: (store, { data: { addPlayer } }), it should be whatever the name of the mutation is, so in this case insert_players.
By default a mutation response from Hasura has a returning field, which is a list, and so the added player is the first element in the list, so you can get it like so: const addedPlayer = insert_players.returning[0];
I didn't want to just delete my question after realising what was wrong shortly after posting it, in case this is useful to other people like me, and so I'll leave it up.

Mongoose - populate return _id only instead of a Object [duplicate]

In Mongoose, I can use a query populate to populate additional fields after a query. I can also populate multiple paths, such as
Person.find({})
.populate('books movie', 'title pages director')
.exec()
However, this would generate a lookup on book gathering the fields for title, pages and director - and also a lookup on movie gathering the fields for title, pages and director as well. What I want is to get title and pages from books only, and director from movie. I could do something like this:
Person.find({})
.populate('books', 'title pages')
.populate('movie', 'director')
.exec()
which gives me the expected result and queries.
But is there any way to have the behavior of the second snippet using a similar "single line" syntax like the first snippet? The reason for that, is that I want to programmatically determine the arguments for the populate function and feed it in. I cannot do that for multiple populate calls.
After looking into the sourcecode of mongoose, I solved this with:
var populateQuery = [{path:'books', select:'title pages'}, {path:'movie', select:'director'}];
Person.find({})
.populate(populateQuery)
.execPopulate()
you can also do something like below:
{path:'user',select:['key1','key2']}
You achieve that by simply passing object or array of objects to populate() method.
const query = [
{
path:'books',
select:'title pages'
},
{
path:'movie',
select:'director'
}
];
const result = await Person.find().populate(query).lean();
Consider that lean() method is optional, it just returns raw json rather than mongoose object and makes code execution a little bit faster! Don't forget to make your function (callback) async!
This is how it's done based on the Mongoose JS documentation http://mongoosejs.com/docs/populate.html
Let's say you have a BookCollection schema which contains users and books
In order to perform a query and get all the BookCollections with its related users and books you would do this
models.BookCollection
.find({})
.populate('user')
.populate('books')
.lean()
.exec(function (err, bookcollection) {
if (err) return console.error(err);
try {
mongoose.connection.close();
res.render('viewbookcollection', { content: bookcollection});
} catch (e) {
console.log("errror getting bookcollection"+e);
}
//Your Schema must include path
let createdData =Person.create(dataYouWant)
await createdData.populate([{path:'books', select:'title pages'},{path:'movie', select:'director'}])

Cannot read property 'length' of undefined on one GET request

working with a MEAN Stack and I have three GET requests for the same URL/Route. One is to get a generalised summary of long-term emotions, the other is to get a summary of emotions by dates entered, and lastly, a summary of emotions related to a user-entered tag associated with individual emotion entries.
My first GET request is throwing no issues but the second GET request throws an error: Cannot read property 'length' of undefined
The error points to the following line:
48| each emotion in dateEmotions
Below is the relative code associated with the error:
Jade
each emotion in dateEmotions
.side-emotions-group
.side-emotions-label
p.emotion-left= emotion.emotionName
p.pull-right(class= emotion.emotionLevel) (#{emotion.emotionLevel}%)
.side-emotions-emotion.emotion-left
GET Request
module.exports.emotionsListByDates = function (req, res) {
Emo.aggregate([
{ $match :
{ "date" : { $gte: ISODate("2018-04-09T00:00:00.000Z"), $lt: ISODate("2018-04-13T00:00:00.000Z") } }
}, { "$group": {
"_id": null,
"averageHappiness": {"$avg": "$happiness"},
"averageSadness": {"$avg": "$sadness"},
"averageAnger": {"$avg": "$anger"},
"averageSurprise": {"$avg": "$surprise"},
"averageContempt": {"$avg": "$contempt"},
"averageDisgust": {"$avg": "$disgust"},
"averageFear": {"$avg": "$fear"},
}}
], function (e, docs) {
if (e) {
res.send(e);
} else {
res.render('dashboard', {
title: "ReacTrack - User Dashboard",
pageHeader: {
title: "User Dashboard",
strapline: "View your emotional data here."
},
dateEmotions: docs
})
}
});
};
This question is already getting pretty long, but I have another GET Request pointed to that URL and it is not throwing any errors, and the only difference is that I am not matching the db records by date in that query. I can post the working code if need be.
Edit
After some experimenting, I am able to get each of the three routes working individually if I comment out the other two. It's when multiple routes pull in the multiple requests that causes issues. For example, here are the routes at present where the ctrlDashboard.emotionsListByDates is working:
// Dashboard Routes
//router.get(/dashboard', ctrlDashboard.emotionsListGeneralised);
router.get('/dashboard', ctrlDashboard.emotionsListByDates);
//router.get('/dashboard', ctrlDashboard.emotionsListByTag);
If I comment out two routes and leave one running, and comment out the respective each emotion in emotions each emotion in dateEmotions and each emotion in tagEmotions blocks in the Jade file and leave the correct one uncommented, then that route will work, it seems to be when I am firing multiple routes. Is this bad practice, or incorrect? Should all queries be in the one GET request if on the same URL?
Thanks.
Apologies, new to routing and RESTful APIs but after some researching into the topic, I now understand the fault.
I assumed that the URL used in routing was the URL you wanted the data to populate...which it still kinda is, but I thought if I wanted to populate the dashboard page, I had to use that exact route and I did not realise I could post the data to different URL routes and take the data from those URLs to populate the one page.
Fixed by adding /date and /tag to those routes and using AJAX to perform those requests and populate the main page.
Thanks all.
I have the same problem but I'm using React+Redux+Fetch. So is it not a good practice dispatch more the one request in the same time and from the same page to a specific url?
I would know what causes that problem. I've found some discussions about it could be a mongoose issue.
My code:
MymongooObject.find(query_specifiers, function(err, data) {
for (let i = 0; i < data.length; ++i) {
...
}
}
Error:
TypeError: Cannot read property 'length' of undefined

Apigility: How to render embedded objects?

How do I render embedded objects in Apigility? For example, if I have a 'user' object and it composes a 'country' object, should I be rendering the 'country' object as an embedded object? And how should I do this?
I am using the Zend\Stdlib\Hydrator\ArraySerializable. My getArrayCopy() method simply returns an array of properties that I want exposed. The array keys are the property names. The array values are the property values. In the case of user->country, the value is an object, not a scalar.
When I return the user object from UserResource->fetch(), here's how it is rendered:
{
"id": "1",
"firstName": "Joe",
"lastName": "Bloggs",
"status": "Active",
"email": "test#example.com",
"country": {
"code": "AU",
"name": "Australia"
},
"settings": "0",
"_links": {
"self": {
"href": "http://api.mydomain.local/users/1"
}
}
}
Note that 'country' is not in an _embedded field. If it is supposed to be in _embedded, I would have thought that Apigility would automatically do that (since it automatically adds the _links object).
As a related issue, how do I go about returning other rel links, such as back, forward, etc?
The easiest way to get Apigility to render embedded resources is when there is an API/resource associated to the embedded object. What I mean for your example is that you'd have an API resource that has a country entity. In that case, if your getArrayCopy returned the the CountryEntity, Apigility would render it automatically as an embedded resource.
If your getArrayCopy is returning country as an array with code and name, you'll end up with what you saw.
For the other part, the rel links for first, last, prev and next will come from the fetchAll method when you return a Paginator. Your collection extends from this already, but it needs an adapter. The code could look something like this:
public function fetchAll($params)
{
// Return a \Zend\Db\Select object that will retrieve the
// stuff you want from the database
$select = $this->service->fetchAll($params);
$entityClass = $this->getEntityClass();
$entity = new $entityClass();
$hydrator = new \Zend\Stdlib\ArraySerializable();
$prototype = new \Zend\Db\ResultSet\HydratingResultSet($hydrator, $entity);
$paginator = new \Zend\Paginator\Adapter\DbSelect($select, $this->sql, $prototype);
$collectionClass = $this->getCollectionClass();
return new $collectionClass($paginator);
}
There are other paginator adapters as well - an ArrayAdapter which will take in an array of however big and then paginate it so you only get the desired number of results. The downside to this if you use it with database results, you'll potentially be retrieving and discarding a lot of results. The DbSelect paginator will modify the $select object to add the limit and order clause automatically so you only retrieve the bits you need. There are also adapters if you're using DbTableGateway, Iterators or even callbacks. You can also implement your own of course.
Hope this helps. If you have more specific needs or clarification, please comment and I'll do my best.
I posted this example on github.
https://github.com/martins-smb/apigility-renderCollection-example
Hope this helps.

Query sailsjs blueprint endpoints by id array using request

I'm using the request library to make calls from one sails app to another one which exposes the default blueprint endpoints. It works fine when I query by non-id fields, but I need to run some queries by passing id arrays. The problem is that the moment you provide an id, only the first id is considered, effectively not allowing this kind of query.
Is there a way to get around this? I could switch over to another attribute if all else fails but I need to know if there is a proper way around this.
Here's how I'm querying:
var idArr = [];//array of ids
var queryParams = { id: idArr };
var options: {
//headers, method and url here
json: queryParams
};
request(options, function(err, response, body){
if (err) return next(err);
return next(null, body);
});
Thanks in advance.
Sails blueprint APIs allow you to use the same waterline query langauge that you would otherwise use in code.
You can directly pass the array of id's in the get call to receive the objects as follows
GET /city?where={"id":[1, 2]}
Refer here for more.
Have fun!
Alright, I switched to a hacky solution to get moving.
For all models that needed querying by id arrays, I added a secondary attribute to the model. Let's call it code. Then, in afterCreate(), I updated code and set it equal to the id. This incurs an additional database call, but it's fine since it's called just once - when the object is created.
Here's the code.
module.exports = {
attributes: {
code: {
type: 'string'//the secondary attribute
},
// other attributes
},
afterCreate: function (newObj, next) {
Model.update({ id: newObj.id }, { code: newObj.id }, next);
}
}
Note that newObj isn't a Model object as even I was led to believe. So we cannot simply update its code and call newObj.save().
After this, in the queries having id arrays, substituting id with code makes them work as expected!