Documents with optional fields that have relations throws an error

Hello, we have recently run into an issue, in some of the schemas (ex: Logs) we have optional fields that have relations, and then when you query the document where this field is undefined, Forest shows an error “Your server encountered an error”. It was working fine in the old version, any way to make it work in the new version?

In one of the other cases we have made this workaround, but it is not ideal since the relation is lost:

	collection.addField('registrationOrPurchase', {
		columnType: 'String',
		dependencies: ['registration', 'purchase'],
		getValues: records =>
			records.map(record => {
				if (record.registration) return `registration id: ${record.registration}`;
				else if (record.purchase) return `Purchase id: ${record.purchase}`;

				return undefined;
			}),
	});

Expected behavior

Omit the optional field if undefined

Failure Logs

===== An exception was raised =====
GET /Log/64e3283163ez305d6e4055d3?{
 timezone: Europe/Stockholm
}

 Missing one of expected fields: '_id'

Context

  • Project name: PayAtt Internal Admin Portal
  • Team name: All teams
  • Environment name: DEVELOPMENT | VLADYSLAVNIKIFOROV (REWRITE)
  • Agent (forest package) name & version: “@forestadmin/agent”: “^1.16.2”, “@forestadmin/datasource-mongoose”: “^1.5.1”
  • Database type: MongoDB
  • Recent changes made on your end if any: Migrating to a new agent

Hello, Thanks for the feedback. I will try to reproduce on my side.
What is the relation type ? How do you define the relation ?
A quick fix on your side to keep the relation is to add a relation on the ‘registrationOrPurchase’ field. For example, if the relation is a oneToOne you can use the addOneToOneRelation method and use the registrationOrPurchase as foreign key.

Let me explain more in-depth. So let’s say there are 3 collections: Registrations, Purchases, Logs.
Logs collection schema has:

{
    registration: { type: mongoose.Schema.Types.ObjectId, ref: 'Registration', required: false },
    purchase: { type: mongoose.Schema.Types.ObjectId, ref: 'Purchase', required: false },
}

In this case, relations are created automatically by forest.

When registration OR purchase happens we create a new Log document.
Then if you go into Logs collection in forest, a list of documents showed as expected, but when you try to open a single document the error happens.

If I comment out registration in logs schema while opening the log with PURCHASE field like that:

{
   // registration: { type: mongoose.Schema.Types.ObjectId, ref: 'Registration', required: false },
    purchase: { type: mongoose.Schema.Types.ObjectId, ref: 'Purchase', required: false },
}

the error is gone, and vice-versa.

So we created this workaround that doesn’t throw an error but the relations are lost (basically we just get an ID of either purchase or registration in text):

collection.addField('registrationOrPurchase', {
	columnType: 'String',
	dependencies: ['registration', 'purchase'],
	getValues: records =>
		records.map(record => {
			if (record.registration) return `Registration id: ${record.registration}`;
			else if (record.purchase) return `Purchase id: ${record.purchase}`;

			return undefined;
		}),
});

collection.removeField('registration', 'purchase');

A quick fix on your side to keep the relation is to add a relation on the ‘registrationOrPurchase’ field. For example, if the relation is a oneToOne you can use the addOneToOneRelation method and use the registrationOrPurchase as primary key.

I am not sure where this relation should point to.

1 Like

Hello, Thanks for the explanation.
Unfortunately, I can’t reproduce.
I have registration, purchase and logs collections declared like this.

connection.model('Registration', new Schema({ name: { type: String } }));

connection.model('Purchase', new Schema({ name: { type: String } }));

connection.model(
  'logs',
  new Schema({
    name: { type: String },
    registration: { type: Types.ObjectId, ref: 'Registration', required: false },
    purchase: { type: Types.ObjectId, ref: 'Purchase', required: false },
  }),
);

I have created 3 records:

I can open all of them without any error. Does my scenario fit your problem? :pray:

1 Like

Managed to debug a problem :slight_smile:

On the purchase collection, we had:

export const purchaseFields = (collection: CollectionCustomizer<Schema, 'Purchase'>): void => {
	collection.addField('providerInfo', {
		columnType: { paymentProviderData: 'Json', paymentProvider: 'String' },
		dependencies: ['onslipDataObject', 'esDataObject', 'zettleDataObject'],
		getValues: records =>
			records.map(record => ({
				paymentProviderData: getPaymentData(record),
				paymentProvider: getPaymentProvider(record),
			})),
	});

	collection.use(flattenColumn, { columnName: 'providerInfo' });
};

Which were still run even though purchaseId field is not present if you open a registration log
After some debugging we realized that records is [{}], so we tried adding a check to see if its empty: Object.keys(records[0]).length which didn’t help, but then we tried switching to

export const purchaseFields = (collection: CollectionCustomizer<Schema, 'Purchase'>): void => {
	collection.addField('paymentProvider', {
		columnType: 'String',
		dependencies: ['onslipDataObject', 'esDataObject', 'zettleDataObject'],
		getValues: records => (!Object.keys(records[0]).length ? [] : records.map(record => getPaymentProvider(record))),
	});

	collection.addField('paymentProviderData', {
		columnType: 'Json',
		dependencies: ['onslipDataObject', 'esDataObject', 'zettleDataObject'],
		getValues: records => (!Object.keys(records[0]).length ? [] : records.map(record => getPaymentData(record))),
	});
};

And at this point, it started working.

2 Likes