8000 Validation Behaviour & Guidelines (3.0) (question) · Issue #378 · js-data/js-data · GitHub
[go: up one dir, main page]

Skip to content
Validation Behaviour & Guidelines (3.0) (question) #378
@pik

Description

@pik

From my understanding the default behavior of JS-Data is to validate on set. This is unusual and if possible it would be great to have some guidelines for how this should be applied (and the use cases for it - documentation on validation (3.0) is also missing at the moment).

Toy schema for reference below. Suppose as below I have defined minLength and maxLength on firstName now if the name is being sourced from user input directly something like person.firstName = 'j' will raise an error - is this tangibly useful? Is there an example somewhere of JS-Data glueing to input elements / error message handling ?

The possible use case I see is that the input element code would have to specify a try .. catch in it's setter (and display a form / input as invalid until the property has been successfully set on the object), in which case the firstName would not be written to the model until it could validate min/max length.

Also there is a particularity to how this set behaviour will work when using nested fields (nice up-side to jsonschema though that they can be validated). So emergencyContact below allows 'null' so that it may be unset, but then expects to be filled in with required fields. That means that an input which attempts to set a valid contactNumber on the person instance will fail - and the entire form needs to be synced at once.

The later might look like this --

try {
  person.emergencyContact = this.emergencyContact
} catch(e) {
  e.errors.forEach((error) => {
    this.$[error.path.split('.')[1]] && (this.$[error.path.split('.')[1]].invalid = true);
  })
  return
}

but being explicit in each field set is probably less appealing than validate() - since the later could work generically and not need to be duplicated:

save(person) { 
  try { 
    person.validate() 
  } catch(e) {
    e.errors.forEach((error) => {
      this.$[error.path.split('.')[1]] && (this.$[error.path.split('.')[1]].invalid = true);
    })
    return 
  }
  person.save()
}

An interesting point @jmdobry brought up on slack was the use of a viewModel - in case of the later you could consider an interface that looks something like:

class Ephemeral {
  constructor(obj) {
    /*  Wraps a JS-Data model and proxies it's properties */
  }
  sync() {
    /* syncs proxied properties to underlying JS-Data model */ 
  }
}

I am inclined to think that in such a case the validation behaviour would like highly similar to the try .. validate() catch {..} save() though only it would be try .. sync() catch {..} save(). Not sure over-all how many advantages there are to separating view and data-models, but I suppose a lot of the 'heaviness' (e.g. changeTracking per set rather than per 'persist') comes from the JS-Data models being used on the back-end as well?

  const EmergencyContactSchema = new Schema({
   'properties': {
      'contactName': {'type': 'string'},
      'contactNumber': {'type': 'string'},
      'relationship': {'type': 'string'}
    },
    'required': ['contactName','contactNumber','relationship'],
    'type': ['object', 'null']
  })

  const PersonSchema = new Schema({
    $schema: 'http://json-schema.org/draft-04/schema#',
    title: 'Person',
    description: 'Schema for a Person Record.',
    type: 'object',
    properties: {
      firstName: { type: 'string', default: '', minLength: 2, maxLength: 32 },
      lastName: { type: 'string', default: '' },
      emergencyContact: EmergencyContactSchema,
    }
  })

  const Person = store.defineMapper('person', {
    endpoint: 'person/',
    schema: PersonSchema
  })

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0