Home > Enterprise >  Modify object in Mongoose pre-save hook
Modify object in Mongoose pre-save hook

Time:05-20

In a GeoJSON Polygon (or more strictly: LinearRing), the last set of coordinates needs to evaluate to the same value as the first one:

[[0,0], [0,1], [1,1], [1,0]] // bad
[[0,0], [0,1], [1,1], [1,0], [0,0]] // good

I would like to do some lenient validation (i.e. fixing the object, instead of rejecting it) when saving a GeoJSON Polygon to my MongoDB instance with Mongoose.

I do this:

export type Coordinates = number[]

// model object
export class Polygon { 
  type: string;
  coordinates?: Coordinates[];

  constructor(coordinates?: Coordinates[]) {
    this.type = "Polygon";
    this.coordinates = coordinates;
  }
}

// schema object
export const PolygonSchema = { 
  type: String,
  coordinates: [[Number]],
};

// model-schema binding
const polygonSchema = new mongoose.Schema(PolygonSchema, { typeKey: '$type' }).loadClass(Polygon);

// pre-save hook
polygonSchema.pre('save', async (next, opts) => {
  // @ts-ignore
  const pol: Polygon = this; // I find this assignment everywhere in documentation, but it seems to fail
  if (pol?.coordinates?.length > 1 && pol.coordinates[0] !== pol.coordinates[pol.coordinates.length - 1]) {
    pol.coordinates.push(pol.coordinates[0]);
  }
  next();
});

tsc complains that this will always be undefined, and the pol object is indeed undefined when debugging. However, this is not, according to the Visual Studio debugger.

Conflicting evidence in the debugger

Is it possible to modify the object during the pre-save hook, or do I need to do this beforehand ?

A note, in case it matters: a Polygon will always be a subdocument in my MongoDB instance: it is saved as the shape of another object.

CodePudding user response:

The pre('save', ...) function won't give you access to the document if you pass it an arrow function instead of a traditional function. Arrow functions do not have this bound, and instead use the parent scope.

From mozilla dev docs

Arrow functions don't have their own bindings to this, arguments or super, and should not be used as methods.

Change your arrow to a traditional function definition

From

polygonSchema.pre('save', {document: true, query: true}, async (next, opts) => {...}

To

polygonSchema.pre('save', {document: true, query: true}, async function (next, opts) {...}

CodePudding user response:

This turned out to be a problem that I did not expect. Apparently, you have no access to the this object in anonymous (arrow) functions ( source: https://youtu.be/DZBGEVgL2eE?t=1495 ). I don't know if this means in JavaScript in general or Mongoose, specifically, but the problem was fixed by doing this:

const polygonSchema = new mongoose.Schema(PolygonSchema, { typeKey: '$type' }).loadClass(Polygon);

async function preSave() {
  // @ts-ignore
  const pol: Polygon = this;
  if (pol?.coordinates?.length && pol.coordinates[0] !== pol.coordinates[pol.coordinates.length - 1]) {
    pol.coordinates.push(pol.coordinates[0]);
  }
}

polygonSchema.pre('save', preSave);
  • Related