DEV Community

loading...

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

abourass profile image Antonio B. ・2 min read

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

Discussion (3)

pic
Editor guide
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
whiteman1989 profile image
whiteman1989

How to get id instead of _id?