Backbonejs - filtering collections within models - backbone.js

I have a collection of Vehicle Ranges. Each Range has an array of VehicleModels containing the models in that range. i.e.
Backbone Model and Collection:
App.Models.Range = Backbone.Models.extend({});
App.Collections.Ranges = Backbone.Collection.extend({
model: Range
});
JSON:
[{
"Name": "Range A",
"VehicleModels": [
{
"Name": "1.2",
"Fuel": "Petrol"
},
{
"Name": "1.3",
"Fuel": "Petrol"
},
{
"Name": "1.6",
"Fuel": "Petrol"
}
]
},
{
"Name": "Range B",
"VehicleModels": [
{
"Name": "x1",
"Fuel": "Diesel"
},
{
"Name": "x2",
"Fuel": "Diesel"
},
{
"Name": "x3",
"Fuel": "Diesel"
}
]
}]
How can a filter the collection to return ranges than have VehicleModels with attributes that match a filter condition. i.e. return all ranges that have vehicle models with the Fuel attribute equal to 'Petrol'. Easy enough to filter the collection by model attributes like:
filterByFuel: function(fuel){
return this.models.where({'Fuel': fuel});
}
or
filterByFuel: function(fuel){
return this.models.filter(function(vehicle) {
return vehicle.get('Fuel') === fuel;
});
}
But need to search the VehicleModel collection in each Range and return the Range if there is a match.
Tips greatly appreciated.
EDIT
I have the following which seems to work, but was curious if there was a better way. Underscores "contains" function seemed like the right thing, but could not get it to work:
models = models.filter(function(range) {
if (_.where(range.get('VehicleModels'), { 'Fuel': params.fuel }).length > 0)
return range;
});

First of all Backbone doesn't treat nested objects as separate collections. They are just arrays.
So when you do :
this.model.get("VehicleModels")
You will only get an Array type object and not a Backbone.Collection.
To filter a plain array, you should use underscore's filter method.
_.filter( this.model.get("VehicleModels"), function(mod) { /* your condition */ } );

Related

How to query in a nested array and project only the matching items from array?

The structure looks like this:
{
clientName: "client1",
employees: [
{
"employeename": 1,
"configuration": {
"isAdmin": true,
"isManager": false
}
}
{
"employeename": 2,
"configuration": {
"isAdmin": false,
"isManager": false
}
}...
]
},
{
...
}
`
I want to see the employees who are admins inside a specific client, given that, I have the client name. How can I write a query in MongoDB for this? I want to be able to see (project) only employees who match?
Can I combine it to match multiple conditions? for example: someone who is an admin and a manager.
I have tried doing:
db.collection.find({clientName: "client1", "employees.configuration.isAdmin": true}, {"employees.employeename": 1})
This just return all the employees.
I've also tried using $elemMatch, to no avail.
Any help is appreciated.
You can do it with Aggregation framework:
$match - to filter the document based on clientName property
$filter with $eq - to filter only employees that have admins
db.collection.aggregate([
{
"$match": {
"clientName": "client1"
}
},
{
"$set": {
"employees": {
"$filter": {
"input": "$employees",
"cond": {
"$eq": [
"$$this.configuration.isAdmin",
true
]
}
}
}
}
}
])
Working example

Pouchdb view returning empty result

Good morning,
I'm currently working with Couchdb and Pouchdb and I'm having a problem with one query on Pouchdb side.
I have a database with different documents setup like this:
{
"_id": "fd87b66087503d760fa501fa49029f94",
"_rev": "1-e2be19d447c98d624c2c8492eaf0a3f4",
"type": "product",
"name": "Blanc de Morgex et de la Salle Brut Extreme 2014",
"category": "Wine",
"subcategory": null,
"zone": "Italy",
"nation": "Valle d'Aosta",
"province": "Morgex, AO",
"cellar": "Cave Mont Blanc",
"price": 30,
"structure": null,
"year": 2014,
"mescita": null,
"tag": null
}
The query I wrote should return the available years of products that match some filters. This is the query, with reduce : _count:
function (doc) {
if(doc.category && doc.type == 'product' && doc.year != null) {
emit(doc.year , 1);
}
}
If I try it with Postman adding the group = true parameter everything works and the result is something like:
{
"rows": [
{
"key": 2004,
"value": 2
},
{
"key": 2006,
"value": 2
},
{
"key": 2008,
"value": 2
}
]
}
The problem is when i run this view with Pouchdb with the following code which return a JSON with an empty array:
wine_db.query('wine_list/years', {reduce: '_count', key : "Bollicine", group : true, group_level: 2}).then(function(doc) {
years_list = doc;
console.log('getting year list');
console.log(doc);
}).catch(function(err){
console.log(err);
});
I've tried to play a little with the parameters of the function and even changing the function to return just a list of all the years, but nope.
I can't find the problem neither a different solution so I'm open to every suggestion you can have.
Another solution (group result)
Working on the indications and on the solution suggested by #user3405291 I finally found a way to group the results by year.
Since the emit function return a complex key ['CATEGORY', YEAR] I can use the startkey and endkey parameters to query the result just for a section of the index returned keeping this way the reduce function enable to group the result.
In the end the view function is:
function (doc) {
if(doc.category && doc.type == 'product' && doc.year) {
emit([doc.category, doc.year], doc.year );
}
}
And the Pouchdb query:
wine_db.query('wine_list/years',
{
startkey : ['CATEGORY'],
endkey : ['CATEGORY', {}],
group: true
}
).then(function (doc) {
years_list = doc;
console.log(years_list);
}).catch(function (err) {
console.log(err);
});
The result, where value is the total number of elements with that index:
{
"rows": [
{
"key": [
"Bollicine",
2004
],
"value": 2
},
{
"key": [
"Bollicine",
2006
],
"value": 2
},
{
"key": [
"Bollicine",
2008
],
"value": 2
}
]
}
In your view map function you emit the year as the key/index:
emit(doc.year , 1);
Now, I'm not sure why your are doing your query with a key like {key : "Bollicine"}:
wine_db.query('wine_list/years', {key : "Bollicine"})
.then(res=>{console.log(res)})
Of course you would get an empty response, because your view is actually indexing your docs according to year. I think you might want to do a query with a key like: {key : "2014"}
UPDATE
Based on your comments, I feel like you need to find docs based on both year and category. I'm not sure if I understand what you want, but this may help you: change your view map function like this:
function (doc) {
if(doc.category && doc.type == 'product' && doc.year) {
emit([doc.year, doc.category] , 1);
}
}
The above view will index your docs according to both year and category. You then query your view like this:
wine_db.query('wine_list/years', {key : ['2014', 'Bollicine']})
.then(res=>{console.log(res)})
The above query will give you all the docs with year field equal to 2014 and category field equal to Bollicine.
Second Update
your code works, but I just get the result for the year 2014. What I'm trying to accomplish is to get all the available years given a specific category
One solution is this:
function (doc) {
if(doc.category && doc.type == 'product' && doc.year) {
emit(doc.category, doc.year);
}
}
The above view will index your docs according to category as key and will return the year as value. Therefore you can query like this:
wine_db.query('wine_list/years', {key : 'Bollicine'})
.then(res=>{console.log(res)})
You should get a response like this, by which you have all the available years for Bollicine category:
{
"total_rows": 400,
"offset": 0,
"rows": [
{
"key": "Bollicine",
"value": "2014"
},
{
"key": "Bollicine",
"value": "2015"
},
{
"key": "Bollicine",
"value": "2018"
}
]
}

ElasticSearch Painless script: How to iterate in an array of Nested Objects

I am trying to create a script using the script_score of the function_score.
I have several documents whose rankings field is type="nested".
The mapping for the field is:
"rankings": {
"type": "nested",
"properties": {
"rank1": {
"type": "long"
},
"rank2": {
"type": "float"
},
"subject": {
"type": "text"
}
}
}
A sample document is:
"rankings": [
{
"rank1": 1051,
"rank2": 78.5,
"subject": "s1"
},
{
"rank1": 45,
"rank2": 34.7,
"subject": "s2"
}]
What I want to achieve is to iterate over the nested objects of rankings. Actually, I need to use i.e. a for loop in order to find a particular subject and use the rank1, rank2 to compute something.
So far, I use something like this but it does not seem to work (throwing a Compile error):
"function_score": {
"script_score": {
"script": {
"lang": "painless",
"inline":
"sum = 0;"
"for (item in doc['rankings_cug']) {"
"sum = sum + doc['rankings_cug.rank1'].value;"
"}"
}
}
}
I have also tried the following options:
for loop using : instead of in: for (item:doc['rankings']) with no success.
for loop using in but trying to iterate over a specific element of the object, i.e. the rank1: for (item in doc['rankings.rank1'].values), which actually compile but it seems that it finds a zero-length array of rank1.
I have read that _source element is the one which can return JSON-like objects, but as far as I found out it is not supported in Search queries.
Can you please give me some ideas of how to proceed with that?
Thanks a lot.
You can access _source via params._source. This one will work:
PUT /rankings/result/1?refresh
{
"rankings": [
{
"rank1": 1051,
"rank2": 78.5,
"subject": "s1"
},
{
"rank1": 45,
"rank2": 34.7,
"subject": "s2"
}
]
}
POST rankings/_search
POST rankings/_search
{
"query": {
"match": {
"_id": "1"
}
},
"script_fields": {
"script_score": {
"script": {
"lang": "painless",
"inline": "double sum = 0.0; for (item in params._source.rankings) { sum += item.rank2; } return sum;"
}
}
}
}
DELETE rankings
Unfortunately, ElasticSearch scripting in general does not support the ability to access nested documents in this way (including Painless). Perhaps, consider a different structure to your mappings where rankings are stored in multi-valued fields if you need to be able to iterate across them in such a way. Ultimately, the nested data will need to de-normalized and put into the parent documents to be able to gets scores in the way described here.
For Nested objects in an array, iterated over the items and it worked.
Following is my sample data in elasticsearch index:
{
"_index": "activity_index",
"_type": "log",
"_id": "AVjx0UTvgHp45Y_tQP6z",
"_version": 4,
"found": true,
"_source": {
"updated": "2016-12-11T22:56:13.548641",
"task_log": [
{
"week_end_date": "2016-12-11",
"log_hours": 16,
"week_start_date": "2016-12-05"
},
{
"week_start_date": "2016-03-21",
"log_hours": 0,
"week_end_date": "2016-03-27"
},
{
"week_start_date": "2016-04-24",
"log_hours": 0,
"week_end_date": "2016-04-30"
}
],
"created": "2016-12-11T22:56:13.548635",
"userid": 895,
"misc": {
},
"current": false,
"taskid": 1023829
}
}
Here is the "Painless" script to iterate over nested objects:
{
"script": {
"lang": "painless",
"inline":
"boolean contains(def x, def y) {
for (item in x) {
if (item['week_start_date'] == y){
return true
}
}
return false
}
if(!contains(ctx._source.task_log, params.start_time_param) {
ctx._source.task_log.add(params.week_object)
}",
"params": {
"start_time_param": "2016-04-24",
"week_object": {
"week_start_date": "2016-04-24",
"week_end_date": "2016-04-30",
"log_hours": 0
}
}
}
}
Used above script for update: /activity_index/log/AVjx0UTvgHp45Y_tQP6z/_update
In the script, created a function called 'contains' with two arguments. Called the function.
The old groovy style: ctx._source.task_log.contains() will not work since ES 5.X stores nested objects in a separate document. Hope this helps!`

Add new object inside array of objects, inside array of objects in mongodb

Considering the below bad model, as I am totally new to this.
{
"uid": "some-id",
"database": {
"name": "nameOfDatabase",
"collection": [
{
"name": "nameOfCollection",
"fields": {
"0": "field_1",
"1": "field_2"
}
},
{
"name": "nameOfAnotherCollection",
"fields": {
"0": "field_1"
}
}
]
}
}
I have the collection name (i.e database.collection.name) and I have a few fields to add to it or delete from it (there are some already existing ones under database.collection.fields, I want to add new ones or delete exiting ones).
In short how do I update/delete "fields", when I have the database name and the collection name.
I cannot figure out how to use positional operator $ in this context.
Using mongoose update as
Model.update(conditions, updates, options, callback);
I don't know what are correct conditions and correct updates parameters.
So far I have unsuccessfully used the below for model.update
conditions = {
"uid": req.body.uid,
"database.name": "test",
"database.collection":{ $elemMatch:{"name":req.body.collection.name}}
};
updates = {
$set: {
"fields": req.body.collection.fields
}
};
---------------------------------------------------------
conditions = {
"uid": req.body.uid,
"database.name": "test",
"database.collection.$.name":req.body.collection.name
};
updates = {
$addToSet: {
"fields": req.body.collection.fields
}
};
I tried a lot more but none did work, as I am totally new.
I am getting confused between $push, $set, $addToSet, what to use what not to?, how to?
The original schema is supposed to be as show below, but running queries on it is getting harder n harder.
{
"uid": "some-id",
"database": [
{ //array of database objects
"name": "nameOfDatabase",
"collection": [ //array of collection objects inside respective databases
{
"name": "nameOfCollection",
"fields": { //fields inside a this particular collection
"0": "field_1",
"1": "field_2"
}
}
]
}
]
}

How to get a count of a items within a collection that match a specific criteria?

I have a collection like so:
{ "items": [
{
"id": "123",
"meta": {
"activity": 2
}
},
{
"id": "13423",
"meta": {
"activity": 4
}
}
]}
Given the collection, how can I get the collection's total activity? In the case above the result would be 6.
I'm using backbone and underscore.
You're using underscore.js, so you have some good tools at your disposal. Look at _.map() to get started.
var flatArray = _.map(collection.items, function(x){return x.meta.activity;});
// should return: [2,4]
Then you can use _.reduce() to turn this into a single value.
var total = _.reduce(flatArray, function(memo, num) {return memo + num;}, 0);
// should return: 6
There's lots of other great tools in underscore.js, it's worth a look to see if anything else would work for you too.
In underscore you can use the reduce function which will reduce a list of values into a single value using the given iterator function.
var myCollection = { "items": [
{
"id": "123",
"meta": {
"activity": 2
}
},
{
"id": "13423",
"meta": {
"activity": 4
}
}
]};
var totalActivity = _.reduce(myCollection.items, function(memo, item){ return memo + item.meta.activity; }, 0);

Resources