Why Does Looping Through a Mongoose Object Display Metadata? - node.js

Why does looping through a mongoose object with nunjucks display metadata?
I am using mongodb and nunjucks in an app I am writing.
I am trying to iterate through a model called persona, but doing so displays mongoose metadata associated with the record.
If I simply display the persona variable by writing {{persona}}.
My output is as follows. Just the keys/values defined in my schema.
{ _id: 582f186df1f05603132090d5, name: 'Alex', name_lower: 'alex', __v: 0,
meta: { validated: null, contributors: 'Research Team', sources: '4 Interviews' },
pain_points: { points: 'Debugging' },
ideal_day: { responsibilities: 'Coding websites.', goals: 'Finish the research site.', joys: 'Good code, Good food.', hobbies: 'Dance, Hiking, Eating' },
environment: { workspace: 'Desk', tools: 'Atom, Sketch', info_from: null, info_to: null, coworkers_relationship: null, technology_relationship: null },
basic_info: { jobtitle: 'FED', experience: '2', education: 'CS', company: '' } }
However, if I loop through the persona
{% for name, item in persona %}
{{ name }} : {{ item }}
{% endfor %}
In addition to displaying the keys in my schema, all mongoose metadata associated with the record will also be displayed. I would like to understand why different information is displayed when I am looping over the object.
$__
isNew
errors
_doc
$__original_save
save
_pres
_posts
$__original_validate
validate
$__original_remove
remove
db
discriminators
__v
id
_id
meta
pain_points
ideal_day
environment
basic_info
updated_at
created_at
name_lower
name
schema
collection
$__handleSave
$__save
$__delta
$__version
increment
$__where
I was able to fix this problem by using Mongoose's lean(), but still don't understand why I experienced this behavior.

When you call {{persona}} then result is persona.toString().
If object doesn't have override method toString then result will be [Object object] (by default toString method).
When you use loop {% for key, value in persona %} then it's equals to
for(var key in obj)
print(key + ' - ' + obj[key]);
This code prints all object properties and methods.
To exclude methods you must use next loop
for(var key in obj)
if (typeof(obj) != 'function') // or obj.hasOwnProperty(key)
print(key + ' ' + obj[key]);
So, to avoid your problem you must "clear" data before pass it to nunjucks or before output.
You can do it define custom filter
var env = nunjucks.configure(...
env.addFilter('lean', function(obj) {
var res = {};
for(var key in obj)
if (typeof(obj) != 'function') // or obj.hasOwnProperty(key)
res[key] = obj[key];
return res;
});
...
{% for key, value in persona | lean %}
{{key}} - {{value}}
{% endfor %}

Related

Sequelize join when key is inside a JSONB field

Is there a way to use include (which is actually a join table) to another Model, where the key is INSIDE a JSONB field? for example:
Item { id: INTEGER, someJsonbField: JSONB }
(item example: { id: 1, someJsonbField: { storeId: 2 } })
Then, for getting all of the items of store with id 2, you write something like this:
Item.findAll({ include: { model: 'Store', key: 'someJsonbField.storeId', ... } })
OFCOURSE, in a real world scenario, storeId should be inside Item directly, but only for the purpose of this question - How could it be done?

How to set position dynamically in mongoose push

I am trying to push some data to a nested array. My data is like:
{a:[{x:[{'y':'z'}]}]}
I am trying to push values into x array. I tried this way, it works=>
{ "$push": { "a.0.x": data}}
But, is there a way where I can pass the value 0 dynamically. I tried the following, but it doesn't work.
{ $push: { "a.$.x": {
$each: [data],
$position: 1
}}}
I didn't find a mongoose way. But, here is a way I did it by passing index to the object property.
var i = "1";
var obj = { ["prop"+i+"Name"]: "response" };
console.log(obj);

knex js query many to many

i'm having trouble with node & knex.js
I'm trying to build a mini blog, with posts & adding functionality to add multiple tags to post
I have a POST model with following properties:
id SERIAL PRIMARY KEY NOT NULL,
name TEXT,
Second I have Tags model that is used for storing tags:
id SERIAL PRIMARY KEY NOT NULL,
name TEXT
And I have many to many table: Post Tags that references post & tags:
id SERIAL PRIMARY KEY NOT NULL,
post_id INTEGER NOT NULL REFERENCES posts ON DELETE CASCADE,
tag_id INTEGER NOT NULL REFERENCES tags ON DELETE CASCADE
I have managed to insert tags, and create post with tags,
But when I want to fetch Post data with Tags attached to that post I'm having a trouble
Here is a problem:
const data = await knex.select('posts.name as postName', 'tags.name as tagName'
.from('posts')
.leftJoin('post_tags', 'posts.id', 'post_tags.post_id')
.leftJoin('tags', 'tags.id', 'post_tags.tag_id')
.where('posts.id', id)
Following query returns this result:
[
{
postName: 'Post 1',
tagName: 'Youtube',
},
{
postName: 'Post 1',
tagName: 'Funny',
}
]
But I want the result to be formated & returned like this:
{
postName: 'Post 1',
tagName: ['Youtube', 'Funny'],
}
Is that even possible with query or do I have to manually format data ?
One way of doing this is to use some kind of aggregate function. If you're using PostgreSQL:
const data = await knex.select('posts.name as postName', knex.raw('ARRAY_AGG (tags.name) tags'))
.from('posts')
.innerJoin('post_tags', 'posts.id', 'post_tags.post_id')
.innerJoin('tags', 'tags.id', 'post_tags.tag_id')
.where('posts.id', id)
.groupBy("postName")
.orderBy("postName")
.first();
->
{ postName: 'post1', tags: [ 'tag1', 'tag2', 'tag3' ] }
For MySQL:
const data = await knex.select('posts.name as postName', knex.raw('GROUP_CONCAT (tags.name) as tags'))
.from('posts')
.innerJoin('post_tags', 'posts.id', 'post_tags.post_id')
.innerJoin('tags', 'tags.id', 'post_tags.tag_id')
.where('posts.id', id)
.groupBy("postName")
.orderBy("postName")
.first()
.then(res => Object.assign(res, { tags: res.tags.split(',')}))
There are no arrays in MySQL, and GROUP_CONCAT will just concat all tags into a string, so we need to split them manually.
->
RowDataPacket { postName: 'post1', tags: [ 'tag1', 'tag2', 'tag3' ] }
The result is correct as that is how SQL works - it returns rows of data. SQL has no concept of returning anything other than a table (think CSV data or Excel spreadsheet).
There are some interesting things you can do with SQL that can convert the tags to strings that you concatenate together but that is not really what you want. Either way you will need to add a post-processing step.
With your current query you can simply do something like this:
function formatter (result) {
let set = {};
result.forEach(row => {
if (set[row.postName] === undefined) {
set[row.postName] = row;
set[row.postName].tagName = [set[row.postName].tagName];
}
else {
set[row.postName].tagName.push(row.tagName);
}
});
return Object.values(set);
}
// ...
query.then(formatter);
This shouldn't be slow as you're only looping through the results once.

Laravel Yajra Datatables Hide Null Column

Wha is the best way to hide empty or null comumns from Yajra Datatables:
var table = $('.data-table').DataTable({
processing: true,
serverSide: true,
ajax: "{{ route('any.route') }}",
columns: [
// How to Hide any of these columns if its data is empty or null?
// for exampe I want to hide mname column ?
// visible: what condition for example !!!!
{data: 'fname', name: 'fname'},
{data: 'mname', name: 'mname'},
{data: 'lname', name: 'lname'},
....
]
});
How do I hide th in table whenever any data is empty or null. for instance, using visible:, what condition should I use to test if the data: is empty or null
Can I see your controller? I have same issue but I can fix it
Try this in your controller!
public function example(){
if ($request->ajax()) { // if request ajax
$data = User::all(); // take all user table
return Datatables::of($data)
->editColumn('fname', function ($row) { //this example for edit your columns if colums is empty
$fname = !empty($row->name) ? $row->name : 'empty';
return $fname;
})
->make(true);
return view('example', compact('data'));
}}
This is the best way to hide NULL values from the data tables.
$('#leads').DataTable({
"columnDefs": [{
"defaultContent": "-",
"targets": "_all"
}]

On Meteor, get data from a collection while looping on another

I have a collection that stores a list of posts users follow, and each user have its own document for it. It is structured like this:
{"_id": "12365",
"user": "123548" //user ID
"posts" : [
{"postId": "225"},
{"postId": "688"},
{"postId": "55"},
(...)
]}
user key refers to the _id of that user, created using account package.
I am trying to, while on user profile page, list all posts that user follow, but I am not being able to, by each postId, show that postTitle instead of its Id.
The HTML to render the list is like this:
<p class="title">{{postId}}</p>
And the template helper that get that user's followed posts is like this:
Template.singleUser.helpers({
postsLibrary: function () {
var postsLibraryContent = postsLibrary.find({
user: Meteor.user();
});
},
});
How can I loop in the posts array from the collection of followed posts, but show each of the posts title that refer to each postId? Should I do it inside this helper above?
-- update --
Title is stored on a Collection of posts, like this:
{
"_id": "9845", //same as postId from the other collection
"title": "Lorem Ipsum",
"author": "Peter Parker",
(...)
}
Change your html from this
<p class="title">{{postId}}</p>
To this
{{#each posts}}
<p class="title">{{postId}}</p>
{{/each}}
This will loop over the array posts.
Ok, got it!
Template.singleUser.helpers({
postsLibrary: function () {
var data = [];
//first we find all this current user postLibrary collection
var postsLibraryContent = PostsLibrary.find({
user: this._id
}).posts;
//then for each item on `posts` array:
for (var i = 0; i < postsLibraryContent.length; i++) {
//get that array item postId value and store on a var
var postId = postsLibraryContent[i].postId;
//creates an array with the post data by searching for its _id
postData = Posts.find({
_id: postId
}).fetch();
//and then push those data to an array above created
data.push({
postTitle: postData[0].title,
postAuthor: postData[0].author
})
};
//then return data array
return data;
}
});
And to render it on HTML:
{{#each postsLibrary}}
<li>
<p class="title">{{postTitle}} - {{postAuthor}}</p>
</li>
{{/each}}

Resources