DEV Community

Antonio B.
Antonio B.

Posted on

How to solve the "own property" issue in Handlebars with Mongoose

With the recent patch to handlebars, pages began returning errors anywhere I had referenced a mongoose document.

Handlebars: Access has been denied to resolve the property “email” because it is not an “own property” of its parent

So, looking up the error, I get this page, which seems more focused on how to go around the patch rather than complying with it. So, I check stack-overflow to see what my fellow dev's are doing. I found (mostly) just people explaining how to get around it. A few astute comments pointed out we just need a new object, were hasOwnProperty is true for the values being referenced.

It occurred to me that I'd seen .toObject() in mongoose when I'd created virtuals for a few of my models. For those unfamiliar, when creating virtuals it's generally advised you pass a configuration object with the schema that looks like the following:

{
  toObject: {
    virtuals: true,
  },
  toJSON: {
    virtuals: true,
  },
}

This means that a user schema might look like this:

const UserSchema = new mongoose.Schema({
  birthday: {
    type: Date,
    format: 'MMMM Do YYYY',
  },
  secondLanguage: {
    type: String,
    default: 'None',
  },
  email: {
    type: String,
  },
}, {
  toObject: {
    virtuals: true,
  },
  toJSON: {
    virtuals: true,
  },
});

With that in place, we have access to the .toObject() method on documents.
So, in our routes (using Express in this example but the idea remains the same regardless):

router.get('/dashboard', async(req, res) => {
try {
  const user = await User.findOne({email: req.body.email.toLowerCase()});
  res.render('index/dashboard', {
    user: user.toObject(),
  })
} catch (err) {
    console.error(err);
  } 
}

Places where you did .find() you simply forEach and run .toObject on each document. I ended up making some wrapping functions:

const multipleMongooseToObj = (arrayOfMongooseDocuments) => {
  const tempArray = [];
  if (arrayOfMongooseDocuments.length !== 0){
    arrayOfMongooseDocuments.forEach(doc => tempArray.push(doc.toObject()));
  }
  return tempArray;
};

const mongooseToObj = (doc) => { if (doc == null){ return null; } return doc.toObject(); };

module.exports = {
  mongooseToObj,
  multipleMongooseToObj,
};

These can be used as follows:

const user = mongooseToObj(await User.findOne({email: req.body.email.toLowerCase()})); // Returns the same as user.toObject()
const users = multipleMongooseToObj(await User.find()); // Return arrays where .toObject() was called on each document

Top comments (4)

Collapse
 
andyfoster profile image
Andy

Oh my god, thank you so much for this! I couldn't even get the workarounds to work for my setup.

In the end I didn't even have to add the toObject() to my Mongoose Schema. I just added that method to the req.locals in my server.js file.

app.use(function (req, res, next) {
  if (req.user) {
    res.locals.user = req.user.toObject();
  }
  next();
});
Collapse
 
kdiaz489 profile image
kdiaz489

Just what I needed! Thank you so much!

Collapse
 
precival profile image
Precival Alves

I had this problem.
I decided to change from yarn to npm, and it worked for my project.

Collapse
 
whiteman1989 profile image
whiteman1989

How to get id instead of _id?