Querying nested objects in PouchDB - javascript

I googled some examples and tutorials but couldn't find any clear example for my case.
I get a JSON response from my server like this:
var heroes = [
{
id: 5,
name: 'Batman',
realName: 'Bruce Wayne',
equipments: [
{
type: 'boomarang',
name: 'Batarang',
},
{
type: 'cloak',
name: 'Bat Cloak',
},
{
type: 'bolas',
name: 'Bat-Bolas',
}
]
},
{
id: 6,
name: 'Cat Woman',
realName: 'Selina Kyle',
equipments: [
{
type: 'car',
name: 'Cat-illac',
},
{
type: 'bolas',
name: 'Cat-Bolas',
}
]
}
];
I would like to query for example: "get heroes with equipment type of bolas"
and It should return both hero objects in an array.
I know it is not right but what I am trying to do is to form a map function like this:
function myMapFunction(doc) {
if(doc.equipments.length > 0) {
emit(doc.equipment.type);
}
}
db.query(myMapFunction, {
key: 'bolas',
include_docs: true
}).then(function(result) {
console.log(result);
}).catch(function(err) {
// handle errors
});
Is it possible? If not what alternatives do I have?
P.S: I also checked LokiJS and underscoreDB. However PouchDB looks more sophisticated and capable of such query.
Thank you guys in advance

Your map function should be:
function myMapFunction(doc) {
doc.equipments.forEach(function (equipment) {
emit(equipment.type);
});
}
Then to query, you use {key: 'bolas'}:
db.query(myMapFunction, {
key: 'bolas',
include_docs: true
}).then(function (result) {
// got result
});
Then your result will look like:
{
"total_rows": 5,
"offset": 0,
"rows": [
{
"doc": ...,
"key": "bolas",
"id": ...,
"value": null
},
{
"doc": ...,
"key": "bolas",
"id": ...,
"value": null
}
]
}
Also be sure to create an index first! Details are in the PouchDB map/reduce guide :)

Related

How can I apply the cluster $in with the where in an array type property using strapi V4

My intention is to be able to apply the $in cluster to the categories property, but it is an array type property. I know that my implementation is not correct, that's why I want some solution to my case.
I will appreciate any solution.
Query Example
where: {
$and: [
categories: {
name: {
$in:["fire"],
},
},
]
},
const entries = await strapi.db
.query("api::data.data")
.findMany({
populate: ["*"],
orderBy: "id",
where: {} // todo
});
Entry Example
{
"id": 1,
"description: "....",
"categories": [
{"id": 1, "name": "fire"},
{"id": 2, "name": "water"}
]
}
After several tests my implementation was functional for an array type property.
where: {
$and: [
categories: {
name: {
$in:["fire"],
},
},
]
},
Can you try once with following code?
const entries = await strapi.db
.query("api::data.data")
.findMany({
populate: ["*"],
orderBy: "id",
where: {
$and: [
categories: {
name: {
$in:["fire"],
},
},
]
},
});

updateMany() on array

In my application i have a collection called Blog and i run this query every 24h
await Blog.updateMany({}, [
{
$set: {
viewed: {
$add: [{ $size: "$visitorIps" }, "$viewed"]
},
visitorIps: []
}
}
]);
My problem is that i have a second collection called Users.
Inside of Users i have an array called posts and here are all posts from that user saved.
{
_id: 234klj2รถ34,
user: "Max",
posts: [
{
_id: 5dgewef323523,
name: "My first blogpost",
content: "...",
viewed: 0,
visitorIps: ["192.168.23.12"]
}
]
}
Now i need the same query on my second collection for each array. How do i do it? I tried something like this but it doesnt worked:
await User.updateMany({}, [
{
$set: {
"posts.$[].viewed: {
$add: [{ $size: "posts.$[].visitorIps" }, "posts.$[].viewed"]
},
"posts.$[].visitorIps": []
}
}
]);
But thats completely wrong. Could somebody help me here out?
You can try using $map,
your logic and code remain same for viewed and visitorIps
$mergeObjects will merge current cursor fields and viewed and visitorIps that we have calculated
await User.updateMany({},
[{
$set: {
posts: {
$map: {
input: "$posts",
as: "post",
in: {
$mergeObjects: [
"$$post",
{
"viewed": {
$add: [{ $size: "$$post.visitorIps" }, "$$post.viewed"]
},
"visitorIps": []
}
]
}
}
}
}
}]
)

Find the value and delete the whole object associated with that array from a deeply nested array of object

I browsed So many questions to find out logics for finding Index in a deeply nested array of object, I didn't find it useful for my requirement though.
in search of Solution in Javascript, Lodash/Underscore would be Fine too.
Let me just phrase out the Whole requirement, Hoping I get a path to find the solution for this issue.
Requirement:
I have an array of objects
arrObj =[
{
"id":3208,
"name":"List",
"issueResponses":[
],
"isActive":false
},
{
"id":3209,
"name":"Me",
"issueResponses":[
],
"isActive":false
},
{
"id":3314,
"name":"SNew",
"issueResponses":[
],
"isActive":false
},
{
"id":3315,
"name":"Olive",
"issueResponses":[
{
"id":3282,
"name":"related to Olive",
"issueResponses":[
],
"isActive":false
},
{
"id":3316,
"name":"My olives are not yet picked",
"issueResponses":[
{
"id":3317,
"name":"Pickup Not Done",
"issueResponses":[
],
"isActive":false
}
]
}
]
}
]
As we can see its deeply nested, I have another array
delValue = [3317,3282], And tomorrow it might be anything in these Deep Nesting.
I have to find these delValue arrays in arrObj and delete all those Objects which has "id" as these Values.
I am trying to solve this in a generic way which can support any number deep search Level and Deletion of the Object from that.
Please help me out in this, if More Information needed will be happy to provide.
You can use filter to achieve this:
function removeDeletions(array, deletion) {
return array.filter(el => {
if (Array.isArray(el.issueResponses)) el.issueResponses = removeDeletions(el.issueResponses, deletion);
return ! deletion.includes(el.id);
})
}
DEMO:
let arrObj = [{
"id": 3208,
"name": "List",
"issueResponses": [
],
"isActive": false
},
{
"id": 3209,
"name": "Me",
"issueResponses": [],
"isActive": false
},
{
"id": 3314,
"name": "SNew",
"issueResponses": [
],
"isActive": false
},
{
"id": 3315,
"name": "Olive",
"issueResponses": [{
"id": 3282,
"name": "related to Olive",
"issueResponses": [
],
"isActive": false
},
{
"id": 3316,
"name": "My olives are not yet picked",
"issueResponses": [{
"id": 3317,
"name": "Pickup Not Done",
"issueResponses": [
],
"isActive": false
}]
}
]
}
]
let delValue = [3317, 3282];
function removeDeletions(array, deletion) {
return array.filter(el => {
if (Array.isArray(el.issueResponses)) el.issueResponses = removeDeletions(el.issueResponses, deletion);
return !deletion.includes(el.id);
})
}
console.log(removeDeletions(arrObj, delValue));
Just keep checking issueResponses - like so:
function deleteIssueResponses(issues, ids) {
for (let i = issues.length; i >= 0; i--) {
if (issues[i].issueResponses.length) {
deleteIssueResponses(issues[i].issueResponses, ids);
}
if (ids.contains(issues[i].id)) {
issues.splice(i, 1);
}
}
}
And call it:
deleteIssueResponses(arrObj, [3317,3282]);
This should be quite simple to solve with some recursion. This function will delete the given ids in the array passed to it, or else call itself to do the same for the nested arrays.
function deleteIds(arr, ids) {
for (let i = 0; i < arr.length; i++) {
if (ids.indexOf(arr[i].id) !== -1) {
arr.splice(i, 1);
i--;
} else {
deleteIds(arr[i].issueResponses, ids);
}
}
}
Call with deleteIds(arrObj, delValue) as in your question.
We use object-scan for basic data processing tasks like this. Once you wrap your head around how to use it, it's pretty good and powerful. Here is how you could answer your questions:
// const objectScan = require('object-scan');
const prune = (values, data) => objectScan(['**[*].id'], {
rtn: 'count',
filterFn: ({ gparent, gproperty, value }) => {
if (values.includes(value)) {
gparent.splice(gproperty, 1);
return true;
}
return false;
}
})(data);
const arrObj = [{ id: 3208, name: 'List', issueResponses: [], isActive: false }, { id: 3209, name: 'Me', issueResponses: [], isActive: false }, { id: 3314, name: 'SNew', issueResponses: [], isActive: false }, { id: 3315, name: 'Olive', issueResponses: [{ id: 3282, name: 'related to Olive', issueResponses: [], isActive: false }, { id: 3316, name: 'My olives are not yet picked', issueResponses: [{ id: 3317, name: 'Pickup Not Done', issueResponses: [], isActive: false }] }] }];
console.log(prune([3317, 3282], arrObj)); // return number of deletes
// => 2
console.log(arrObj);
// => [ { id: 3208, name: 'List', issueResponses: [], isActive: false }, { id: 3209, name: 'Me', issueResponses: [], isActive: false }, { id: 3314, name: 'SNew', issueResponses: [], isActive: false }, { id: 3315, name: 'Olive', issueResponses: [ { id: 3316, name: 'My olives are not yet picked', issueResponses: [] } ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#15.0.0"></script>
Disclaimer: I'm the author of object-scan

Meteor cross collection arrays

I am trying to pull an array from a different collection using collection2. I have been able to do this with objects using the following example for users:
users: {
type: String,
label: "Inspector",
optional: true,
autoform: {
firstOption: 'Choose an Inspector',
options: function() {
return Meteor.users.find({}, {
sort: {
profile: 1,
firstName: 1
}
}).map(function(c) {
return {
label: c.profile.firstName + " " + c.profile.lastName,
value: c._id
};
});
}
}
},
I would like to do the same but for an array of objects. Here is what the source data looks like:
{
"_id": "xDkso4FXHt63K7evG",
"AboveGroundSections": [{
"sectionName": "one"
}, {
"sectionName": "two"
}],
"AboveGroundItems": [{
"itemSection": "one",
"itemDescription": "dfgsdfg",
"itemCode": "dsfgsdg"
}, {
"itemSection": "two",
"itemDescription": "sdfgsdfg",
"itemCode": "sdfgsdgfsd"
}]
}
Here is what my function looks like:
agSection: {
type: String,
optional: true,
autoform: {
firstOption: 'Select A Section Type',
options: function() {
return TemplateData.find({}, {
sort: {
AboveGroundSections: 1,
sectionName: [0]
}
}).map(function(c) {
return {
label: c.AboveGroundSections.sectionName,
value: c.AboveGroundSections.sectionName
}
});
}
}
},
I know this, it's just not pulling the data for me. I am sure, I am just missing something small. I am trying to pull all objects within the AboveGroundSection array.
Your .map() is iterating over the set of documents but not over the arrays inside each document. Also I don't think your sorting is going to work the way you hope because of the inner nesting.
Try:
agSection: {
type: String,
optional: true,
autoform: {
firstOption: 'Select A Section Type',
options() {
let opt = [];
TemplateData.find().forEach(c => {
c.AboveGroundSections.forEach(s => { opt.push(s.sectionName) });
});
return opt.sort().map(o => { return { label: o, value: o } });
}
}
},
Also if your AboveGroundSections array only has a single key per element then you can simplify:
"AboveGroundSections": [
{ "sectionName": "one" },
{ "sectionName": "two" }
]
To:
"AboveGroundSections": [
"one",
"two"
]

Javascript Array Manipulation - Is there a more declarative approach?

Thanks for taking a look here. I'm working with an API, and need to change the format of the data. Here's an example of the return data:
data: [
{
status: "planned work",
name: "123"
},
{
status: "all good",
name: "nqr"
}
];
Each train line has a name like "123" or "nqr", and I want to split each train into their own objects so that it would look something like this:
data: [
{
status: "planned work",
name: "1"
},
{
status: "planned work",
name: "2"
},
{
status: "planned work",
name: "3"
},
{
status: "all good",
name: "n"
},
{
status: "all good",
name: "q"
},
{
status: "all good",
name: "r"
}
];
I have some working code which splits the name and uses nested .forEach loops to push items to an array. Working code:
function formatLinesData(lines) {
var trains = [];
lines.forEach( line => {
line.name.split("").forEach(train => {
trains.push({name: train, status: line.status});
});
});
return trains;
}
Is there a way to accomplish this without the nested loops? Looking for an elegant solution if you have one.
Thanks
You might do as follows;
var data = [
{
status: "planned work",
name: "123"
},
{
status: "all good",
name: "nqr"
}
],
newData = [].concat(...data.map(o => o.name.split("").map(c => ({status: o.status, name: c}))));
console.log(newData);
You can use reduce - initialize it with an empty array, and iterate over the data
array using your logic.
data.reduce((prev,curr) => {
for (let i=0; i<curr.name.length; i++) {
prev.push({ name : curr.name[i], status : curr.status});
}
return prev;
},[]);

Categories