How can I reuse code (html + handlebars) in assemble to show categorized lists? - assemble

I'm using assemble
For example, if I have this data:
{"people": [
{
"name": "Sally",
"group": "A"
},
{
"name": "John",
"group": "B"
},
{
"name": "Jane",
"group": "B"
},
{
"name": "Skippy",
"group": "A"
}
]}
How can I render two separate lists of names based on property values (A or B)?
Here is the desired html output:
<p>Group A</p>
<ul>
<li>Sally</li>
<li>Skippy</li>
</ul>
<p>Group B</p>
<ul>
<li>John</li>
<li>Jane</li>
</ul>
Currently, my handlebars template code looks like this. But, I want to avoid copying the same code again and again.
<p>Group A</p>
{{#each index.people }}
{{#is group "A"}}
{{ name }}: {{ group }}
{{ /is }}
{{ /each }}

The solution you're currently using is exactly what I would have recommended:
{{#each people}}
{{#is group "A"}}
....
{{/is}}
{{/each}}
However, you can easily create a handlebars helper to do whatever you want. e.g.
Handlebars.registerHelper('people', function (context, group) {
return context.filter(function (item) {
if (item.group) {
return item.group === group;
}
return false;
});
});
Now, given your list:
{
"list": [
{
"name": "Sally",
"group": "A"
},
{
"name": "John",
"group": "B"
},
{
"name": "Jane",
"group": "B"
},
{
"name": "Skippy",
"group": "A"
}
]
}
you would use the helpers like this:
{{people list 'A'}}
{{people list 'B'}}
There is info on the assemble docs about how to register the helpers with assemble.

How many groups do you actually have? If it's only a few, I would repeat the {{#each people}} loop a few times. I don't know of a way to do this without repeated markup using default assemble/handlebars methods.
If you have many groups, I might write a helper that takes that data and organizes it by group with 'people' lists inside. If you had data structured like this:
{"groups" : [
{
"groupName" : "A",
"people" : [
{
"name" : "Sally"
},
{
"name" : "Skippy"
}
]
},
{
"groupName" : "B",
"people" : [
{
"name" : "John"
},
{
"name" : "Jane"
}
]
}
]}
You could generate the markup with:
{{#each index.groups}}
<p>Group: {{groupName}}</p>
<ul>
{{#each people}}
<li>{{name}}</li>
{{/each}}
</ul>
{{/each}}

Related

How to save deletion in a deeply nested MongoDB document

I am new to MongoDB and I am using MongoDB shell to perform the operations.
I am working to remove the array named Process from all the Items, but it seems that I do not grasp the remove concept correctly.
The documents we use are deeply nested - we do not know how many items there are, or how deep the level of nesting.
What I tried so far is to use recursion to iterate through the items:
function removeAllProcessFields(docItems)
{
if(Array.isArray(docItems))
{
docItems.forEach(function(item)
{
print("idItem: "+item._id);
if(item.Process == null)
{
print("Process null");
}
else
{
$unset: { Process: ""}
}
removeAllProcessFields(item.Items);
})
}
}
var docs = db.getCollection('MyCollection').find({})
docs.forEach(function(doc)
{
print("idDoc: "+doc._id);
removeAllProcessFields(doc.Items);
})
But I have difficulties on using unset properly to save the operation.
An example document would be:
{
"_id": "622226d319517e83e8ed6151",
"Name": "test1",
"Description": "",
"Items": [{
"_id": "622226d319517e83e8ed614e",
"Name": "test-item",
"Description": "",
"Process": [{
"Name": "Step1"
}, {
"Name": "Step2"
}],
"Items": [{
"_id": "622226d319517e83e8ed614f",
"Name": "test-subItem1",
"Description": "",
"Process": [{
"Name": "StepSub1"
}, {
"Name": "StepSub2"
}, {
"Name": "StepSub3"
}],
"Items": []
},
{
"_id": "622226d319517e83e8ed6150",
"Name": "test-subItem2",
"Description": "",
"Process": [{
"Name": "StepSub4"
}, {
"Name": "StepSub5"
}, {
"Name": "StepSub6"
}],
"Items": []
}
]
}]
}
What I hope to achieve would be:
{
"_id": "622226d319517e83e8ed6151",
"Name": "test1",
"Description": "",
"Items": [{
"_id": "622226d319517e83e8ed614e",
"Name": "test-item",
"Description": "",
"Items": [{
"_id": "622226d319517e83e8ed614f",
"Name": "test-subItem1",
"Description": "",
"Items": []
},
{
"_id": "622226d319517e83e8ed6150",
"Name": "test-subItem2",
"Description": "",
"Items": []
}
]
}]
}
Something like this maybe using the $[] positional operator:
db.collection.update({},
{
$unset: {
"Items.$[].Items.$[].Process": 1,
"Items.$[].Process": 1
}
})
You just need to construct it in the recursion ...
playground
JavaScript recursive function example:
mongos> db.rec.find()
{ "_id" : ObjectId("622a6c46ae295edb276df8e2"), "Items" : [ { "a" : 1 }, { "Items" : [ { "Items" : [ { "Items" : [ ], "Process" : [ 1, 2, 3 ] } ], "Process" : [ 4, 5, 6 ] } ], "Process" : [ ] } ] }
mongos> db.rec.find().forEach(function(obj){ var id=obj._id,ar=[],z=""; function x(obj){ if(typeof obj.Items != "undefined" ){ obj.Items.forEach(function(k){ if( typeof k.Process !="undefined" ){ z=z+".Items.$[]";ar.push(z.substring(1)+".Process") }; if(typeof k.Items != "undefined"){x(k)}else{} }) }else{} };x(obj);ar.forEach(function(del){print( "db.collection.update({_id:ObjectId('"+id+"')},{$unset:{'"+del+"':1}})" );}) })
db.collection.update({_id:ObjectId('622a6c46ae295edb276df8e2')},{$unset:{'Items.$[].Process':1}})
db.collection.update({_id:ObjectId('622a6c46ae295edb276df8e2')},{$unset:{'Items.$[].Items.$[].Process':1}})
db.collection.update({_id:ObjectId('622a6c46ae295edb276df8e2')},{$unset:{'Items.$[].Items.$[].Items.$[].Process':1}})
mongos>
Explained:
Loop over all documents in collection with forEach
Define recursive function x that will loop over any number of nested Items and identify if there is Process field and push to array ar
Finally loop over array ar and construct the update $unset query , in the example only printed for safety , but you can improve generating single query per document and executing unset query ...
Assuming you are on v>=4.4 you can use the "merge onto self" feature of $merge plus defining a recursive function to sweep through the collection and surgically remove one or a list of fields at any level of the hierarchy. The same sort of needs arise when processing json-schema data which is also arbitrarily hierarchical.
The solution below has extra logic to "mark" documents that had any modifications so the others can be removed from the update set passed to $merge. It also can be further refined to reduce some variables; it was edited down from a more general solution that had to examine keys and values.
db.foo.aggregate([
{$replaceRoot: {newRoot: {$function: {
body: function(obj, target) {
var didSomething = false;
var process = function(holder, spot, value) {
// test FIRST since [] instanceof Object is true!
if(Array.isArray(value)) {
for(var jj = value.length - 1; jj >= 0; jj--) {
process(value, jj, value[jj]);
}
} else if(value instanceof Object) {
walkObj(value);
}
};
var walkObj = function(obj) {
Object.keys(obj).forEach(function(k) {
if(target.indexOf(k) > -1) {
delete obj[k];
didSomething = true;
} else {
process(obj, k, obj[k]);
}
});
}
// ENTRY POINT:
if(!Array.isArray(target)) {
target = [ target ]; // if not array, make it an array
}
walkObj(obj);
if(!didSomething) {
obj['__didNothing'] = true;
}
return obj;
},
// Invoke!
// You can delete multiple fields with an array, e.g.:
// ..., ['Process','Description']
args: [ "$$ROOT", 'Process' ],
lang: "js"
}}
}}
// Only let thru docs WITHOUT the marker:
,{$match: {'__didNothing':{$exists:false}} }
,{$merge: {
into: "foo",
on: [ "_id" ],
whenMatched: "merge",
whenNotMatched: "fail"
}}
]);

How can I gather all fields with their values except five of them and put them inside new field in the same collection with mongoDB

I have a collection that have many documents with too many fields but I want to gather many of these fields inside new field called Data, here is an example
{
"_id" : ObjectId("***********"),
"name" : "1234567890",
"mobile" : "Test",
.
.
.
.
.
etc
}
I want to use updateMany to make all the documents in the collection looks like this
{
"_id" : ObjectId("***********"),
"name" : "1234567890",
"mobile" : "Test",
"Data":{
.
.
.
.
.
etc
}
}
Option 1(few nested fields): You can do it following way:
db.collection.update({},
[
{
$project: {
data: {
name: "$name",
mobile: "$mobile"
}
}
}
],
{
multi: true
})
playground1
Option 2: (If the fields that need to be nested are too many):
db.collection.update({},
[
{
$project: {
data: "$$ROOT",
name: 1,
mobile: 1
}
},
{
$unset: [
"data.name",
"data.mobile"
]
}
],
{
multi: true
})
playground2

Deleting specific record from an array nested within another array

I have a MongoDB record as follow:
"id": 1,
"Tasks": [
{
"description": "BLAH",
"Tags": [
{
"Name": "test",
"tagID": "YRG+crq3SJucvlUwTo/uSg=="
},
{
"Name": "Cars",
"tagID": "ObwiiZpNTOGECgHb1HehHg=="
}
]
},
......
]
I'm trying to delete the object from 'Tags' with the 'Name: test' by reference to its 'tagID'. The query I have deletes the whole record within 'Tasks' not just that particular Tags object.
db.user.update({ 'id': 1 },
{
'$pull': { 'Tasks': {'Tags.tagID': "YRG+crq3SJucvlUwTo/uSg==" }}
},
{ '$multi': 'true' }
)
How can I ammend my query to only remove that particular tag and not remove the entire record?
Using Pymongo and the $ operator
col.update({"id": 1, "Tasks.description": "BLAH"},
{
"$pull": {"Tasks.$.Tags" : { "tagID": "YRG+crq3SJucvlUwTo/uSg==" }}
}, multi=True
)
Use the positional operator $ together with the $pull update operator to remove the specific array element object:
db.user.update({"id": 1, "Tasks.description": "BLAH"},
{
"$pull": {"Tasks.$.Tags" : { "tagID": "YRG+crq3SJucvlUwTo/uSg==" }}
},
{ multi: true}
);
I think you are looking for the $unset command. Reference here.

How do I replace an entire array of subdocuments in MongoDB?

Here is an example document from my collection:
Books
[
id: 1,
links:
[
{text: "ABC", "url": "www.abc.com"},
{text: "XYZ", "url": "www.xyz.com"}
]
]
I want to replace the links array in one update operation. Here is an example of how the above document should be modified:
Books
[
id: 1,
links:
[
{text: "XYZ", "url": "www.xyz.com"},
{text: "efg", "url": "www.efg.com"}, <== NEW COPY OF THE ARRAY
{text: "ijk", "url": "www.ijk.com"}
]
]
As you can see, the links array has been replaced (old data removed, and new data added).
I am having very hard time with the Update.Set() because it says it MyLinks<> cannot be mapped to a BsonValue
I've tried many different ways of achieving this, and all of them fail, including .PushAllWrapped<WebLinkRoot>("links", myDoc.WebLinks).
Everything I've tried results in the new values being appended to the array, rather than the array being replaced.
As it seems MongoDB doesn't provide a simple method to replace an array of subdocument OR a method like .ClearArray(), what is the best way for me to ensure the array is cleared before adding new elements in a single query?
I am here because I saw 5k views on this post, I'm adding some stuff may be it help other who looking for answer of above
db.collectionName.insertOne({
'links': [
{
"text" : "XYZ",
"url" : "www.xyz.com"
}
]
});
now run this query which help to replace older data
db.collectionName.update(
{
_id: ObjectId("your object Id")
},
{
$set:
{
'links':[ {
"text" : "XYZ1",
"url" : "www.xyz.com1"
} ]
}
});
I think you have to do something like this:
var newArray = new BSONArray {
new BSONDocument { { "text", "XYZ" }, { "url", "www.xyz.com" } },
new BSONDocument { { "text", "efg" }, { "url", "www.efg.com" } },
new BSONDocument { { "text", "ijk" }, { "url", "www.ijk.com" } }
};
var update = Update.Set( "links", newArray );
collection.Update( query, update );
Or whatever method you can to cast as a valid BSONValue.
So equivalent to shell:
{ "links" : [ { "text" : "abc" } ] }
> db.collection.update(
{},
{ $set:
{ links: [
{ text: "xyz", url: "something" },
{ text: "zzz", url: "else" }
]}
})
>db.collection.find({},{ _id: 0, links:1 }).pretty()
{ "links" : [
{
"text" : "xyz",
"url" : "something"
},
{
"text" : "zzz",
"url" : "else"
}
]
}
So that works.
You clearly need something else other than embedded code. But hopefully that puts you on the right track.

How to perform AND operation in Mongodb for a query on nested array of objects

I have the following records:
Record 1:
{
"status": "active",
"users": [
{
"name": "foo",
"status": "a"
},
{
"name": "foo",
"status": "b"
}
]
}
Record 2:
{
"status": "active",
"users": [
{
"name": "bar",
"status": "a"
},
{
"name": "foo",
"status": "b"
}
]
}
Now, I wish to return only those records in which the user.name is "foo" and the user.status is "a" - That is I need to get Record 1 back since it satisfies my requirements.
How do I do the AND operation on the array within the record to achieve this?
I tried the following query:
{
"$and": [
{
"users.name": "foo"
},
{
"users.status": "a"
}
]
}
but I get both the records - I need only the first record (since in the users array, it has an entry where the name is "foo" AND the status is "a").
Any clues?
Ok, I found the answer out here
The solution would be to have a query as follows:
{
"users": {
"$elemMatch": {
"name": "foo",
"status": "a"
}
}
}
That did the trick!
Instead of individual field values you can also use whole objects in find queries. Try:
db.collection.find( {
users: {
"name":"foo",
"status":"a"
}
});
This should return all documents where the users array includes an object (or is an object) which looks as described. But note that this doesn't work when the object has additional fields.