the best way to exclude some data from a method in sails controller - sails.js

I want's to exclude some data in some controller method and in other method i want's that data. I do it with forEach function right into method after finding that :
nine: function (req, res) {
Dore.find()
.limit(9)
.sort('createdAt DESC')
.populate('file')
.exec(function (err, sh) {
if (err) {
return res.negotiate(err);
} else {
console.log('before : ', sh);
sh.forEach(function (item, i) {
delete item.zaman;
});
console.log('after : ', sh);
return res.send(sh);
}
});
},
I want to know how possible to do that with finding and do not included ever in finding so we don't need to remove that again with forEach.
tanks
As #zabware say we have select method in Query option I try this format but do not work and return all data :
I try to use that with following format but don't working :
Model.find( {
where: {},
limit: 9,
sort: 'createdAt DESC'
},
{
select: [ 'id', 'createdAt' ]
} )
and
Model.find( {
where: {},
limit: 9,
sort: 'createdAt DESC',
select: [ 'id', 'createdAt' ]
} )
and
Model.find( {}, select: [ 'id', 'createdAt' ] )

Although toJson is really designed to solve the kind of problem you are facing, it will not help you, since in some actions you want to exclude certain fields and in others not.
So we need to select fields per query. Luckily there is a solution for this in waterline:
Dore.find({}, {select: ['foo', 'bar']})
.limit(9)
.sort('createdAt DESC')
.populate('file')
.exec(function (err, sh) {
console.log(err);
console.log(sh);
});
This is supported since sails 0.11 but not really documented. You can find it under query options here https://github.com/balderdashy/waterline-docs/blob/e7b9ecf2510646b7de69663f709175a186da10d5/queries/query-language.md#query-options

I accidentally stumbled upon it while playing with the debugger - Sails version 1.2.3
There is something like omit and You can put it into your find method call:
const user = await User.findOne({
where: {email : inputs.email},
omit: ['password']
}); //There won't be password field in the user object
It is a pity that there is no word about it in the documentation.

Related

geoNear with Pagination and sorting on created_at

I´m having an App with the capability of listing articles within a user specified location + radius surrounding. See ruby code with mongo query below.
For Web App, it uses pagination based on "last item" (min_distance) and "excluded_ids" in combination with "limit", what I believe to be the most performant and also better than "limit" and "skip". This solution approach supports "previous page" and "next page". For "jump to page" it makes use of the combination of "limit" and "skip".
geoNear by nature sorts by "distance".
New requirement is to have, additionally, sort by "created_at" (user toggle).
My question:
For sort by "created_at" to work, will it be sufficient to add sort into the query? I´m afraid this won´t work for the current implemented pagination approach and I will need to somehow rewrite to have "last item" based on "created_at", rather than on "min_distance".
Can someone tell for sure how Mongo handles internally the sort and if simply adding sort for created_at will work with this type of pagination?
Or else, how this would look for replacing mind_distance for created_at.
Also, for supporting both sort types, what index/es is/are needed?
One for both, or one for each?
def self.articles_with_distance(params, excluded_ids=[])
if params[:min_distance].present?
Article.collection.aggregate([
{ :"$geoNear" => {
:near => {
:type => "Point",
:coordinates => [params[:lon].to_f, params[:lat].to_f],
},
:query => {_id: { :"$nin" => excluded_ids }}, # to exclude items already shown if min distance between two pages are same
:distanceField => "distance_from",
:maxDistance => params[:radius].to_i,
:minDistance => params[:min_distance].to_i, # only need to reduce the result set by exclude items had been shown previous pages
:spherical => true,
:limit => (params[:per_page].to_i || 10)
}}
]);
else
Article.collection.aggregate([
{ :"$geoNear" => {
:near => {
:type => "Point",
:coordinates => [params[:lon].to_f, params[:lat].to_f],
},
:distanceField => "distance_from",
:maxDistance => params[:radius].to_i,
:spherical => true,
#
}},
{
:"$skip" => (params[:per_page].to_i * (params[:page].to_i-1))
},
{
:"$limit" => (params[:per_page].to_i || 10)
}
]);
end
end

Mocha MongoDB clean collection before each test

I'm trying to cleanup 2 collections before each test. I'm using mocha --watch to rerun tests while editing the test source files. First run always executes as expected, but consecutive runs gives Topology was destroyed error from mongodb(indicated via result of http request).
I am not really sure why deleteMany deletes my inserted object in consecutive runs.
describe('myCollection1 related tests', () => {
// myCollection1 documents should refer to a valid myCollection2 document.
var foo;
const exampleObject = {name: 'TEST OBJECT', attr1: 'TO'};
beforeEach(() => {
return Promise.all([
mongo.db('mydb').collection('myCollection1').deleteMany({}), // clear collection 1
mongo.db('mydb').collection('myCollection2').deleteMany({}) // clear collection 2
.then(() => mongo.db('mydb').collection('myCollection2').insertOne(exampleObject) // and add a sample object
.then((value) => {
foo = value.ops[0]; // save this as test specific variable so I can use it in my tests.
return Promise.resolve();
})),
]);
});
it('should create a related object', (done) => {
chai.request(server)
.post('/api/v1/foos/')
.send({ related: foo._id })
.then((res) => {
res.should.have.status(200);
res.body.should.be.an('object').with.all.keys('status', 'errors', 'data');
done();
}).catch((err) => {
done(err);
});
});
});
I spotted issue with your promise structure in beforeEach. I'm not sure it is intended or not. I'm afraid it is the culprit. I'm fixing that into below:
beforeEach(() => {
return Promise.all([
mongo.db('mydb').collection('myCollection1').deleteMany({}),
mongo.db('mydb').collection('myCollection2').deleteMany({})
]) // close the promise.all here
.then(() => collections.engines().insertOne(exampleObject)) // close `then` here
.then((value) => {
foo = value.ops[0];
return Promise.resolve();
});
});
Hope it helps

Conditionally populating a path in a Mongo query

I have the following schema:
const connectionSchema = new Schema( {
...
event: {
type: Schema.ObjectId,
ref: 'events'
},
place: {
type: Schema.ObjectId,
ref: 'places'
},
} )
const eventsSchema = new Schema( {
...
place: {
type: Schema.ObjectId,
ref: 'places'
},
} )
When I query for either a single connection or a collection of connections, I want it to check if event is specified and if so return the place from the event, otherwise return the place from the connection.
I'm currently doing something like this when I'm querying a single connection:
let c
const execFunction = ( err, connection ) => {
if ( connection.event ) {
connection.place = connection.event.place
}
c = connection
}
const connectionPromise = Connection.findById( connectionId )
connectionPromise.populate( { path: 'event', populate: { path: 'place' } } )
connectionPromise.populate( 'place' )
connectionPromise.exec( execFunction )
But, I'm trying to avoid having to iterate through the entire collection in the execFunction to do the replace logic on each individual result if I query for a collection instead.
Is there a better (i.e. more performant) way to approach this?
As far as I know, there is no way to do conditional population with mongoose.
In your case, I think you are trying to do "deep population".
Deep population is not supported out of the box, but there is a plugin for that! mongoose-deep-populate
const connectionQuery = Connection.findById( connectionId )
connectionQuery.populate( 'place event event.place' )
connectionQuery.exec( execFunction );
I'm guessing that 'place event.place' will probably do it: 'event' should be implied.
I also expect that, if place or event or event.place are undefined, there won't be an error, they simply won't be populated.
But I don't think there is any way, in one line, to skip populating place when event.place is available.
By the way, if you want to use promises, you could write like this:
return Connection.findById( connectionId )
.populate( 'place event event.place' )
.then( onSuccessFunc );
to propagate errors, or:
Connection.findById( connectionId )
.populate( 'place event event.place' )
.then( onSuccessFunc )
.catch( onErrorFunc );
to handle errors locally. I find that preferable to callbacks, because we don't have to worry about if (err) at every stage.

How to get last inserted record in Meteor mongo?

I want to get last inserted item in minimongo. currently i'm doing it in this way (code on client side):
addBook()
{
BookCollection.insert(
this.book , ( err , insertedBook_id )=>
{
this.book_saved = true;
console.group('Output:')
console.log(err);
console.log( insertedBook_id );
console.log(
BookCollection.find({_id: insertedBook_id})
.fetch()
);
console.groupEnd();
//this.router.navigate(['/book', insertedBook_id]);
}
);
}
Output in console:
undefined
t6Lwrv4od854tE7st
[]
As you can see, when i redirect to newly created page, i cant find a book, but in mongo shell i clearly see that record was added. Should i wait for some event like BookCollection.insert(this.book).then( . . . )? Please help me!)
When i go back to the 'all books page', i see new record and can click, all works normal, no errors.
In the /book controller:
ngOnInit()
{
this.sub = this.curr_route.params.subscribe(
params=>
{
this.book = BookCollection.findOne( params[ '_id' ] ); // .fetch() == []
//if(!this.book)
// this.router.navigate(['/books']);
Meteor.subscribe(
'book' , params[ '_id' ] , ()=>
{
this.book = BookCollection.findOne( params[ '_id' ] );
} , true
)
console.groupCollapsed( 'BookDetailsCardComponent log data:' )
console.group( 'Book:' );
console.log( this.book );
console.groupEnd();
console.groupEnd();
} , true
);
}
This is probably a timing issue. The _id is most likely created and returned immediately while the actual data is inserted into the database, which would explain why doing a find() on that _id right away comes back as an empty array.
Is there a reason you are doing your insert from the client rather than doing it on the server and using a Meteor method to initiate it? Changing to this approach might resolve your issue.

Extjs - autocomplete filter 2 properties

By following this example http://dev.sencha.com/deploy/ext-4.1.0-gpl/examples/form/forum-search.html
I could apply the auto-complete feature on my combo box, using php/postgresql query retrieves the names then:
{
xtype: 'combo',
store: ds,
hideTrigger:true,
typeAhead: false,
id: 'search_input_text',
width: 187,
queryMode: 'remote',
hideLabel: true,
queryParam: 'query',
displayField: 'first_name',
valueField: 'first_name',
listConfig: {
loadingText: 'Loading...',
getInnerTpl: function() {
return '{first_name}';
}}
listeners: {
buffer: 50,
change: function() {
var store = this.store;
store.clearFilter();
store.filter({
property: 'first_name',
anyMatch: true,
value : this.getValue()
});
}
}
}
Now, I need to edit this to let user enter either the first or last name of the student, and then each name entered (first or last) will be shown in the combo box.
So, I edited the query to make it retrieve the names :
SELECT first_name, last_name FROM students WHERE first_name ILIKE '%$query%' OR last_name ILIKE '%$query%' ";
and:
displayField: 'first_name'+'last_name',
valueField: 'first_name'+'last_name',
return '{first_name}'+'{last_name}';
store.filter({
property: 'first_name',
anyMatch: true,
value : this.getValue()
},
{
property: 'last_name',
anyMatch: true,
value : this.getValue()
}
but still, it retrieves only first names according to what user types,
Note that if I use this alone:
store.filter({
property: 'last_name',
anyMatch: true,
value : this.getValue()
});
it works fine with last names only.
You can apply multiple filters to data store like this:
var filters = [
new Ext.util.Filter({
filterFn: function(item){return item.get('first_name') == this.getValue() || item.get('last_name') == this.getValue();
}
})
];
store.filter(filters);
The above code block is just an idea on how to apply multiple filters in the same field. Notice the double-pipe "||". You can use "&&" as per your needs. It is like SQL query.