Meteor-Community-Packages / meteor-collection2

A Meteor package that extends Mongo.Collection to provide support for specifying a schema and then validating against that schema when inserting and updating.
https://packosphere.com/aldeed/collection2
MIT License
1.02k stars 108 forks source link

Dynamic Schema with Validation #415

Closed reedbird8 closed 4 years ago

reedbird8 commented 4 years ago

I'd like to have a Dynamic Schema with validation. I've tried using selectors AND merging collections so they have the same fields, but every now and then I get an error where it says I am passing an empty object after it cleans the object, as if it removed all fields from the object.

I'm wondering what might the best way to handle this be, if you've got some advice. I have a couple other ideas I may try, but was curious to ask here before I spent too much time with them.

harryadel commented 4 years ago

Could you please provide us with sample code illustrating the empty object issue you run into?

reedbird8 commented 4 years ago

So, I'm not terribly concerned about the error at this moment. I'm more trying to see if there might be a way dynamic schema with validation was intended to happen that I'm not getting from the documentation.

But since you asked, as for the error, it happens when I try to insert items on startup (which I desperately want to do when I launch this update to our app so they do not all have to be entered manually). However, if I make a minor change to any file and have the app reload in development, it works. The oddest part about this is I am passing "validate: false" as an option, yet it still clears the object.

var success = Document.insert(data, {selector: {type: projectID}, validate: false});

Essentially I have collections file where I am declaring the default schema for a document type. However, custom fields can be added to this document type based on the project the document belongs to. So when I go to add a document it checks for all the custom fields that have been added to that project and extends the schema like so:

CustomSchema = new SimpleSchema(extendSchema); Document.attachSchema(CustomSchema, {selector: {type: projectID}});

coagmano commented 4 years ago

This looks like something where you should be checking the input against the schema directly, instead of attaching these to the global shared collection with selectors

reedbird8 commented 4 years ago

This looks like something where you should be checking the input against the schema directly, instead of attaching these to the global shared collection with selectors

I'm not quite sure what you mean. I need to validate the document to be inserted both with the common schema as well as the fields specific to the project the document belongs to. My goal is to receive one error message that would include all invalid fields in both the common schema as well as the fields specific to the project.

harryadel commented 4 years ago

Either keep attaching new schemas to the collection with a different selector like you've been doing or just like @coagmano recommended handle this issue at schema level without getting the collections involved. And as I understand from your previous comment you had already dabbled with the first solution but it's not working for some reason. This is why I'd always prefer a simple repository where we can tinker with solutions having a concrete problem to solve.

Anyway, deciding which solution to go for depends on your use case. If you already know what new fields you need to validate and there're not many of them (i.e. not many projects with their own specific fields) then you can simply attach multiple schemas at the collection initialization and you'd be good to go. But if you actually somehow dynamically decide not only the fields to validate but rather what those fields are then stick with schema validation level.

Collections solution:

// collection.js

const Documents = new Mongo.Collection('documents');

const basicDocumentSchema = new SimpleSchema({
      id: String,
      name: String,
      documentType: String, // will be used later to pass to different docuemnts
    });

const defaultDocumentSchema = basicDocumentSchema.extend({
      defaultDocumentField: String,
    });

const specialDocumentSchema = basicDocumentSchema.extend({
    specialDocumentField: String,
})

Documents.attachSchema(defaultDocumentSchema, {selector: {type: 'default'}});
Documents.attachSchema(specialDocumentSchema, {selector: {type: 'special'}});

// method.js
...

Document.insert(doc, { selector: { type: doc.documentType }})

Schema solution:

// method.js

const basicDocumentSchema = new SimpleSchema({
      id: String,
      name: String,
      documentType: String, // will be used later to pass to different docuemnts
    });

const defaultDocumentSchema = basicDocumentSchema.extend({
      defaultDocumentField: String,
    });

const specialDocumentSchema = basicDocumentSchema.extend({
    specialDocumentField: String,
})

try {
  if (doc.type === 'default') {
    // Keep in mind since you are controlling the schema above you can mutate them however you'd like
  defaultDocumentSchema.validate(doc);
  Document.insert(doc)
  }
} catch (e) {

}
reedbird8 commented 4 years ago

@harryadel - Quick question about the Schema solution...the line defaultDocumentField.validate(doc), shouldn't that be defaultDocumentSchema instead?

harryadel commented 4 years ago

Ah, yes. it's typo :sweat_smile:

reedbird8 commented 4 years ago

I should add that neither of these really solves the dynamic issue of adding new required fields while the app is running...though the second one might be able to be modified to do it better since those variables are only valid within the current run of the method call.

coagmano commented 4 years ago

Yeah, with the second method, you can create the schema in the method call, adding any dynamic properties before validating

reedbird8 commented 4 years ago

So, just messed around with the second method validating solely against the schema. It handles errors sent to the client very differently than the first method, which is actually a bit disappointing. It only sends the first error back to the client, rather than looking for all errors in the document.

EDIT: Just saw how to transform the error so Meteor can handle it on the client. Also helps to clean the doc before validating.

Thanks all!