3
\$\begingroup\$

Outline

I have a form that needs validation. The problem is that the software requires custom validation that depends on multiple parameters.

I decided to pass the object into a function which will dynamically give the object the functions it needs to be validated. Then once the validation is completed, I remove those functions from the object.

I'm adding the functions so I can chain them together, which I believe will provide me with great composition.


Usage

This is how I add the functions to the object:

let q = this.validator.validate(this.question)

I can then chain the added functions like this:

q.validateString().cleanUp();

That way my functions are composable and reusable.


Implementation

Now for the actual code to be reviewed:

export class Validator {
    validate(question) {
        let q = Object.assign(
            {},
            question,
            validationOptions
        );

        return q;
    }
}

let validationOptions = {
    validateString,
    cleanUp
};

function validateString() {
    return this;
}

function cleanUp() {
    Object.keys(validationOptions).map((i) => delete this[i]);
    return this;
}

I'm forced to use the class because I need Aurelia Dependency Injection. I compose my functions outside of that class Validator because they only need to work with the this binding.

This code works perfectly, and it executes as expected; however, I do have a few concerns:

  • Is there a downside to the way I add these functions?
  • The way I remove these functions works well, but are there better options? I can't use Symbol because I return the q object to another class.
\$\endgroup\$
  • \$\begingroup\$ I just need some clarification. So you want to be able to chain your validation functions, but you want to keep them separate from the object, so you can more easily assign and chain them on other objects? \$\endgroup\$ – Joshua Dawson Jun 4 '16 at 14:22
  • \$\begingroup\$ Yes they have to be assigned to many objects. The objects are send back to the server in the end so it would be a waste to have those functions inside that object a few hundred times I'd guess. \$\endgroup\$ – Randy Jun 4 '16 at 14:23
2
\$\begingroup\$

Mild issue with assigning functions

It's possible that you could overwrite properties of a question if they have the same name as one of your validation functions. For example:

let question = {
  foo: "bar"
};

let validationOptions = {
    foo,
    cleanUp
};

When you execute Object.assign the later object's properties override earlier ones in the arguments array.

Alternative to deleting the functions

I think a better way to "delete" the added functions would be to just keep track of the original question object, and then return it from cleanUp:

export class Validator {
    validate(question) {
        let q = Object.assign(
            {},
            question,
            validationOptions
        );

        q.cleanUp = () => question;    

        return q;
    }
}

let validationOptions = {
    validateString
};

function validateString() {
    // validate
    return this;
}

That way the initial object is unchanged, and you don't have to concern yourself with deleting the validation functions from the question when your done.

Wrapper Class

I think you also ought to consider making the validator a wrapper class for your question. That way your validation functions stay out of your data, you can still chain your methods, and you don't have to assign the validation functions for each object:

export class Validator {
    constructor(question) {
      this.data = question;
    }
    validateString() {
      return this;
    }
    cleanUp() {
      return this.data;
    }
}

If you want the validation functions to be free from an object, classes have prototypes that you can assign the methods to:

function validateString() {
  return this;
}

export class Validator {
    constructor(question) {
      data = question;
    }
}
Validator.prototype.validateString = validateString;

If you have a lot of functions you could do:

let validateOptions = {
  validateString,
  validateDate,
  validateAge
};

Object.keys(validateOptions).forEach((property) => {
  Validator.prototype[property] = validateOptions[property];
});
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.