Handlebars: Access has been denied to resolve the property "name" because it is not an "own property" of its parent - node.js

I got the issue with handlebars 4.7.3. I already checked the solution from this ask,
Handlebars: Access has been denied to resolve the property "from" because it is not an "own property" of its parent
but it was no solution for my code so I hope someone can help me.
Controller.js
submitPosts: (req, res) => {
// Check the attributs from create.handlebars for success or error message
const newPost = new Post( {
surname: req.body.surname,
name: req.body.name,
biography: req.body.biography,
profilpicture: req.body.profilpicture,
paintings: req.body.paintings,
});
// Safe new posts
newPost.save().then(post => {
console.log(post);
flash('success-message', 'new post!')
res.redirect('/admin/posts');
});
},
postModel.js
const
mongoose = require('mongoose');
const Schema = mongoose.Schema; // get props
const PostSchema = new Schema({
// define props --> required for a post
surname: {
type: String,
default: ''
},
name: {
type: String,
default: ''
},
biography: {
type: String,
default: ''
},
profilpicture: {
type: Object,
},
paintings : {
type: Object,
}
});
module.exports = mongoose.model('post', PostSchema);
index.handlebars
{{#each post}}
<tr>
<td>{{ surname }}</td>
<td>{{ name }}</td>
<td><img style="width: 100px; height:100px;" src="{{ profilpicture }}"></td>
<td>{{ biography }}</td>
<td><img style="width: 100px; height:100px;" src="{{ paintings }}"></td>
</tr>
{{/each}}
Already tried every possibility from the other ask on stack overflow, other handlebars version, change router code, ... NOTHING WORKS :(

Just had the same issue that wrecked my nerves.
Tried additional packages etc but in the end a simple command solve this.
Its the Command ".lean()"
Found more here: link to mongoose docs for lean()
my code example:
// show message page Route
router.get("/", (req, res) => {
//read all Message entries from db
Message.find()
//sort them descending by date property
.sort({ date: "desc" })
.lean()
//take these and call the rendered page and pass them as object to the page
.then(messages => {
res.render("messages/show_messages", { messages: messages });
});
});

Related

CouchDB many-to-many fetching with dynamic sorting

I have a CouchDB index where documents are music files with fields artist, album, title, etc. I also want to store playlists of tracks and be able to fetch all of a playlist's tracks and optionally sort the results by arbitrary fields.
I've read through the documentation on joins and the blog post/comment example is basically the same architecture as my playlist/track one, so I already have the ability to fetch a playlist's tracks and sort them by some predetermined key.
Is there any way to combine a view (like in those docs) with post-hoc sorting by some other arbitrary field (artist, title, etc.)?
I don't mind using multiple queries, but collecting all of the returned document IDs and using them in a subsequent /_find query like {"_id": {"$in": [...]}} query is extremely slow. Sorting them client side is always an option, but then I'd need to implement sorting logic twice in my app: a server-side version for when I'm using /_find on all tracks and a client-side version for tracks returned from a playlist view.
(This would be trivially easy to accomplish with an RDBMS but my use case involves syncing data between mobile and desktop versions of the same app (which might be offline), which is what led me to CouchDB+PouchDB.)
This belongs in a comment but would be an ugly mess.
When not to use map/reduce is a must read blog post by Nolan Lawson. There the point made is to leverage document _id's as much as possible, and demonstrates that denormalization isn't always the answer.
I believe if you are able to refactor your id's and documents, it will be evident how a secondary index (or two!) will fill in the gaps.
I won't go further because it would be plagiarism - it can't be explained in simpler terms. Read that article, toy with this code.
const g_view_result = 'view_result';
const getAllArtists = async() => {
const options = {
include_docs: true,
startkey: 'artist_',
endkey: 'artist_\uffff'
};
allDocs(["name"], options);
}
const getAllAlbums = async() => {
const options = {
include_docs: true,
startkey: 'album_',
endkey: 'album_\uffff'
};
allDocs(["title"], options);
}
const getAllBowieAlbums = async() => {
const options = {
include_docs: true,
startkey: 'album_bowie_',
endkey: 'album_bowie_\uffff'
};
allDocs(["title"], options);
}
const allDocs = async(fields, options) => {
let html = [];
const results = getEl('results');
results.innerText = '';
let docs = await db.allDocs(options);
docs.rows.forEach(row => {
html.push(fields.map(f => row.doc[f]).join(", "));
})
results.innerText = html.join('\n');
}
// canned test documents
const getDocsToInstall = () => {
return [
/*---- artist ----*/
{
_id: 'artist_bowie',
type: 'artist',
name: 'David Bowie',
age: 67
},
{
_id: 'artist_dylan',
type: 'artist',
name: 'Bob Dylan',
age: 72
},
{
_id: 'artist_joni',
type: 'artist',
name: 'Joni Mitchell',
age: 70
},
/*---- albums ----*/
{
_id: 'album_bowie_1971_hunky_dory',
artist: 'artist_bowie',
title: 'Hunky Dory',
type: 'album',
year: 1971
},
{
_id: 'album_bowie_1972_ziggy_stardust',
artist: 'artist_bowie',
title: 'The Rise and Fall of Ziggy Stardust and the Spiders from Mars',
type: 'album',
year: 1972
},
{
_id: 'album_dylan_1964_times_they_are_changin',
artist: 'artist_dylan',
title: 'The Times They Are a-Changin\'',
type: 'album',
year: 1964
},
{
_id: 'album_dylan_1965_highway_61',
artist: 'artist_dylan',
title: 'Highway 61 Revisited',
type: 'album',
year: 1965
},
{
_id: 'album_dylan_1969_nashville_skyline',
artist: 'artist_dylan',
title: 'Nashville Skyline',
type: 'album',
year: 1969
},
{
_id: 'album_joni_1974_court_and_spark',
artist: 'artist_joni',
title: 'Court and Spark',
type: 'album',
year: 1974
}
]
}
//
// boilerplate code
//
let db;
// init example db instance
const initDb = async() => {
db = new PouchDB('test', {
adapter: 'memory'
});
await db.bulkDocs(getDocsToInstall());
}
initDb().then(() => {
document.querySelectorAll('.hide').forEach(el => el.classList.remove('hide'));
});
const getEl = id => document.getElementById(id);
.hide {
display: none
}
.label {
text-align: right;
margin-right: 1em;
}
.hints {
font-size: smaller;
}
<script src="https://cdn.jsdelivr.net/npm/pouchdb#7.1.1/dist/pouchdb.min.js"></script>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.memory.min.js"></script>
<table id='actions' class='hide'>
<tr>
<td>
<button onclick='getAllArtists()'>All artists</button>
</td>
<td>
<button onclick='getAllAlbums()'>All albums</button>
</td>
<td>
<button onclick='getAllBowieAlbums()'>All albums by David Bowie</button>
</td>
</tr>
</table>
<div style='margin-top:2em'></div>
<pre id='results'></pre>

Accessing child methods in parent for mongoose succeeds in array and fails with single child

UPDATE : Solution is at bottom of question
I have an express site using mongoose.
I'll greatly simplify to say that I have adults, kids, and house models. When I create methods on kids, I can call them from within methods on adults and get a result. I can also call them from my .ejs views. However, when I create methods on house, I can only get a result from my .ejs views and get undefined when called from within methods on adults. Example code follows.
adult.js
const mongoose = require('mongoose');
const adultSchema = mongoose.Schema({
name: { type: String },
size: {type: String},
kids: [{type: mongoose.Schema.Types.ObjectId, ref: 'Kid', required: true}]
house:{type: mongoose.Schema.Types.ObjectId, ref: 'House', required: true}
});
adultSchema.method({
getKidsDescription: function() {
if (this.kids.length < 1) {
return 'No kids yet';
} else {
let ev = 'Kids, aged: ';
let kds = this.kids;
kds.forEach(function(k){
ev = ev + 'k.getAge()' // works
})
return ev;
}
},
getHouseDescription: function(){
return 'A fabulous house on '+this.house.getFullStreet(); // does not work
}
})
module.exports = mongoose.model('Adult', adultSchema);
kid.js
const mongoose = require('mongoose');
const kidSchema = mongoose.Schema({
name: { type: String },
size: {type: String},
birthdate: {type:Date}
});
kidSchema.method({
getAge: function() {
return (Math.floor(new Date() - this.birthdate)/(1000*60*60*24*365))
},
})
module.exports = mongoose.model('Kid', kidSchema);
house.js
const mongoose = require('mongoose');
const houseSchema = mongoose.Schema({
name: { type: String },
city: {type: String},
street: {type:String}
});
houseSchema.method({
getFullStreet: function() {
return this.street + ' Road';
},
})
module.exports = mongoose.model('House', houseSchema);
When I make a query for theAdult, it looks like this:
controller.js
exports.main = async (req, res, next) => {
if (req.theAdult) {
try {
const found = await db.fetchAdult(req.theAdult._id)
res.render('/main', {
//theHouse: found.house //below I show this working
});
} catch(e) {
throw new Error(e.message)
}
} else {
res.redirect('/');
}
}
db.js
exports.fetchAdult = (id) => {
return Adult.findById(id)
.populate({ path: 'kids'})
.populate({ path: 'house'})
.exec()
.then(doc => {
return doc;
});
}
Assuming house is passed to view as an object when rendered (commented out above), this works
view.ejs
<p> <%= theHouse.getFullStreet() %></p>
Assuming house populated on the call to load the Adult, this returns undefined.
view.ejs
<p> <%= theAdult.house.getFullStreet() %></p>
At the same time, both of these work
view.ejs
<ul> <% theAdult.kids.forEach(function(k) { %>
<li><%= k.getAge() %> </li>
<% }); %>
</ul>
<p> <% theAdult.getKidsDescription() %> </p>
What I am not understanding is how the method calls work for objects in array and work in the view but do not work for objects on in an array. This is a single child error (for me). If it did not work in the view, I would assume that the method getFullStreet() was the problem, but it works in the view. If the array methods could not be called within the parent, I would assume the issue was with trying to access getFullStreet() in the parent.
What am I missing?
SOLUTION
I was fetching theAdult in my call to show view.ejs, but I was then actually relying on currentAdult which referred to req.adult and did not have the fields populated. My solution was to add a pre hook to the adult schema that always populates house on find.
in adult.js
adultSchema.pre('find', function() {
this.populate('house')
})
Have you tried passing a hydrated theAdult? It might only see the ObjectID, without any other data or methods.

How to do soft delete with mongodb using nodejs

I'm able to delete data from the view , but at the sametime its getting deleted from mongodb which shouldn't happen.
I tried mongoose-soft-delete plugin to perform soft delete, but it isn't working
//schema
var mongoose= require('mongoose');
let softDelete = require('mongoosejs-soft-delete');
var Schema=mongoose.Schema;
var newblogSchema=new Schema({
user_id:Number,
title:String,
description:String,
summary:String,
hashtag:String
})
var newblogs=mongoose.model('NewBlog',newblogSchema);
newblogSchema.plugin(softDelete);
module.exports=newblogs;
//html template
<table>
<tr>
<th>Title</th>
<th>Description</th>
<th>Summary</th>
<th>HashTags</th>
</tr>
<tr *ngFor="let blog of blogs;">
<td >{{blog.title}}</td>
<td [innerHtml]="blog.description| safeHtml">{{blog.description}}</td>
<td>{{blog.summary}}</td>
<td>{{blog.hashtag}}</td>
<td> <a routerLink="/blog"><button type="button"
(click)="editblog(blog._id,blog.title,blog.description,blog.summary,blog.hashtag)">
Edit</button></a>
<td><button type="button" (click)="deleteblog(blog._id)">Delete</button>
</tr>
</table>
//ts file
deleteblog(blogid) {
var result = confirm('Want to delete?');
if (result === true) {
this.blogservice.deleteblog(blogid).subscribe(response => {this.blogs = response; });
}
//service
deleteblog(blogid):Observable<any>{
return Observable.create(observer=>{
this.http.post('http://localhost:4000/api/deleteblog', {_id: blogid}, {headers: new HttpHeaders({'Content-Type':'application/json'})}
)
.subscribe((response:Response)=>{
observer.next(response);
observer.complete();
});
});
}
//api.js
router.post('/deleteblog',(req,res)=>{
var body=req.body;
newblog.findByIdAndRemove({_id:body._id},(error,newblog)=>{if(error){
console.log(error);
}
else{
return res.json({message:'deleted',data:newblog});
}
});
});
Now the data is getting deleted from view as well as mongodb.
Expected result is to delete data only from the view and not from mongodb
we can implement soft delete with plugin, middleware and $isDeleted document method
soft delete plugin code:
import mongoose from 'mongoose';
export type TWithSoftDeleted = {
isDeleted: boolean;
deletedAt: Date | null;
}
type TDocument = TWithSoftDeleted & mongoose.Document;
const softDeletePlugin = (schema: mongoose.Schema) => {
schema.add({
isDeleted: {
type: Boolean,
required: true,
default: false,
},
deletedAt: {
type: Date,
default: null,
},
});
const typesFindQueryMiddleware = [
'count',
'find',
'findOne',
'findOneAndDelete',
'findOneAndRemove',
'findOneAndUpdate',
'update',
'updateOne',
'updateMany',
];
const setDocumentIsDeleted = async (doc: TDocument) => {
doc.isDeleted = true;
doc.deletedAt = new Date();
doc.$isDeleted(true);
await doc.save();
};
const excludeInFindQueriesIsDeleted = async function (
this: mongoose.Query<TDocument>,
next: mongoose.HookNextFunction
) {
this.where({ isDeleted: false });
next();
};
const excludeInDeletedInAggregateMiddleware = async function (
this: mongoose.Aggregate<any>,
next: mongoose.HookNextFunction
) {
this.pipeline().unshift({ $match: { isDeleted: false } });
next();
};
schema.pre('remove', async function (
this: TDocument,
next: mongoose.HookNextFunction
) {
await setDocumentIsDeleted(this);
next();
});
typesFindQueryMiddleware.forEach((type) => {
schema.pre(type, excludeInFindQueriesIsDeleted);
});
schema.pre('aggregate', excludeInDeletedInAggregateMiddleware);
};
export {
softDeletePlugin,
};
you can use as global for all schemas
mongoose.plugin(softDeletePlugin);
or for concrete schema
For Soft delete, you should maintain an active flag column that should only contain values as 0 and 1.
This way, you could analyse whether a record is deleted or not.
While displaying, add another clause for displaying only the records that have flag value 1. And while deleting, just update that flag's value to 0.
This would do the job.
For Example, here user 2 is deleted. with activeFlag as 0.
ID memberID userStatus groupCode activeFlag
1 user1 1 4455 1
2 user2 1 4220 0
3 user3 2 4220 1
Try to use https://www.npmjs.com/package/soft-delete-mongoose-plugin
A simple and friendly soft delete plugin for mongoose.

how to associate data schema in mongoose

I'm trying to associate 2 schema on nodejs/mongoose app.
my first model:
var mongoose = require("mongoose");
var projectSchema = new Schema({
pname: String,
pnumber: String,
plocation: String,
pclient: String,
clientabn: String,
contname: String,
contnumber: String,
mobile: String,
address: String,
city: String,
country: String,
boqs: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Boq",
}]
});
module.exports = mongoose.model("Project", projectSchema);
second model:
var mongoose = require("mongoose");
var boqSchema = new Schema({
boqDesc: String,
boqUnit: String,
boqQty: String,
boqRate: String,
boqPrice: String,
});
module.exports = mongoose.model("Boq", boqSchema);
And this is my post rout:
app.post("/myprojects/:id/boq", function(req, res) {
Project.findById(req.params.id, function(err, project) {
if (err) {
console.log(err);
} else {
Boq.create(req.body.boq, function(err, boq) {
if (err) {
console.log(err);
} else {
project.boqs.push(boq);
project.save();
res.redirect("/myprojects/" + project._id + "/boq");
console.log(boq);
}
});
}
});
});
When is posting, only an id of boq will be saved on database not any data from boq's form. anyone knows what is wrong with this code?
output of console.log:
console.log output
console.log(project)
Below is the post form:
<form action="/myprojects/<%= project._id %>/boq" method="POST">
<table class="table" id="toptab">
<tbody>
<tr>
<td class="td6"><b>No.</b></td>
<td class="td7"><b>Item Description/Scope of Work</b></td>
<td class="td8"><b>Unit</b></td>
<td class="td9"><b>Quantity</b></td>
<td class="td10"><b>Rate</b></td>
<td class="td11"><b>Price</b></td>
</tr>
<tr>
<td class="td6"></td>
<td class="td7" contenteditable="true" name="boq[boqDesc]"></td>
<td class="td8" contenteditable="true" name="boq[boqUnit]"></td>
<td class="td9" contenteditable="true" name="boq[boqQty]"></td>
<td class="td10" contenteditable="true" name="boq[boqRate]">$</td>
<td class="td11" contenteditable="true" name="boq[boqPrice]"></td>
</tr>
</tbody>
</table>
<button type="submit" name="submit" class="btn btn-primary">Save</button>
</form>
you're calling Boq.create(undefined, callback);
this creates a boq document in db with no fields (except the _id).
it passes validation because all boq's fields are not required.
go to your client, and make sure you're adding boq data to http request's body correctly.
add <input> elements in between <td></td> tags, each named after one of the fields in boq's schema. for instance <td><input type="text" name="boqDesc"></td>

Create search articles feature in MEAN stack

First of all, let me tell you that I'm a novice in the world of javascript and node.js. I have been searching for help in trying to do what i want but haven't found yet.
I am using the MEAN stack(http://mean.io/) and I am trying to implement a search feature in the included articles model. The search would look for articles with a specific tag and would be implemented in the index page. Follow me and see if you can find what I am missing please.
In the backend:
app/models/
/**
* Article Schema
*/
var ArticleSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true
},
content: {
type: String,
default: '',
trim: true
},
tag: {
type: String,
default: '',
trim: true
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
app/controllers/
exports.searcharticle = function(req, res) {
Article.find({'tag': req.params.tag}).sort('-created').populate('user', 'name username').exec(function(err, articles) {
if (err) {
res.render('error', {
status: 500
});
} else {
res.jsonp(articles);
}
});
};
Added the route for the search in app/routes/articles.js
app.get('/articles/search/:tag', articles.searcharticle);
In the frontend:
Created the view for the search wich will display the search results - public/views/articles/search.html
<section data-ng-controller="ArticlesController" data-ng-init="searchart()">
<ul class="articles unstyled">
<li data-ng-repeat="article in articles">
<span>{{article.created | date:'medium'}}</span> /
<span>{{article.user.name}}</span>
<h2><a data-ng-href="#!/articles/{{article._id}}">{{article.name}}</a></h2>
<div>{{article.tag}}</div>
</li>
</ul>
<h1 data-ng-hide="!articles || articles.length">Your search hasn't returned any results. <br> Why don't you Create One?</h1>
</section>
The view for the index.html, where the searchbox will be implemented
<section data-ng-controller="ArticlesController">
<form role="form" data-ng-submit="searchart()">
<div>
<div>
<input type="text" id="tag" ng-model="selected" class="form-control" placeholder="Tag">
</div>
<div>
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
</form>
Added the route to the config.js
when('/articles/search/:tag', {
templateUrl: 'views/articles/search.html'
}).
And added the search function to the articles controller
$scope.searchart = function() {
Articles.query(function(articles) {
$scope.articles = articles;
});
};
Right now, with this code implemented, when I click in the submit button in the index page, nothing happens.
Can you find what am I missing?
Thanks in advance!
In order to use a URL in your client Article Service, you should define the URL Parameter in the articles service at: packages/articles/public/services/article.js, like the articleId parameter already defined in there like this:
angular.module('mean.articles').factory('Articles', ['$resource',
function($resource) {
return $resource('articles/:articleId', {
articleId: '#_id'
}, {
update: {
method: 'PUT'
}
});
}
]);
Then you need to pass it in your angular controller search method, like the function that gets one by id, like this:
$scope.findOne = function() {
Articles.get({
articleId: $stateParams.articleId
}, function(article) {
$scope.article = article;
});
};
Personally I don't know how to add another parameter to the $resource object in addition to the existing one (articleId), you may have to create another $resource service with the new parameter (:tag) and use it in your search method in your angular controller.
Another way that sounds more simple and flexible to me is to just pass the search parameters in the query method, like this:
$scope.searchart = function() {
Articles.query({tag:$scope.selectedTag}, function(articles) {
$scope.articles = articles;
});
};
and then at the server side controller, read your query parameters like this:
exports.searcharticle = function(req, res) {
Article.find(req.query).sort('-created').populate('user', 'name username').exec(function(err, articles) {
if (err) {
res.render('error', {
status: 500
});
} else {
res.jsonp(articles);
}
});
};

Resources