Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add alternative to fluent rule API that enables creating rules from data #363

Open
dkent600 opened this issue Oct 14, 2016 · 9 comments
Open

Comments

@dkent600
Copy link
Contributor

Use Case
Looping over a set of model properties to apply validation rules to each.

Problem
Currently, three different object classes are used in this scenario. This adds complexity to coding in TypeScript. Consider the following code:

let ruleSetter: FluentRules<any, any> | FluentRuleCustomizer<any, any> = null;

for (let property of formSchema.properties) {

    if (this.requiresValidation(property, formSchema)) {

        /**
            * ... then first we will call ensure.displayName, returning FluentRules,
            * and      second will call at least one method that will return FluentRuleCustomizer.
            * Thus each time we reach here ruleSetter will be a FluentRuleCustomizer (except the first time, when it is null),
            * whereupon we will immediately convert it into a FluentRules and then again to a FluentRuleCustomizer.
            */
        if (ruleSetter === null) {
            ruleSetter = ValidationRules
                .ensure(property.name)
                .displayName(property.description);

            // at this point, ruleSetter is a FluentRules

        } else {
            /**
                * At this point we know it is a FluentRuleCustomizer because some rule was applied to it,
                * but here it will again become a FluentRules.
                */
            ruleSetter = (ruleSetter as FluentRuleCustomizer<any, any>)
                .ensure(property.name)
                .displayName(property.description);
        }

        // these will set ruleSetter to a FluentRuleCustomizer
        ruleSetter = ruleSetter.required());

        ruleSetter = ruleSetter
            .minLength(property.minLength)
            .withMessage(`${property.description} must contain at least ${property.minLength} characters`));

            .
            .
            .
}

Request

  1. Create an interface IFluentRules (or whatever you want to name it) that includes all of the methods used here, or that might be used, including ensure, displayName, minLength, required, etc.......
  2. Instead of using ValidationRules, use a new factory method to create an empty FluentRules that implements IFluentRules.
  3. redefine all the methods in IFluentRules to return an IFluentRules

The resulting code would look like this:

let ruleSetter: IFluentRules= ValidationRules.create();

for (let property of formSchema.properties) {

    if (this.requiresValidation(property, formSchema)) {

       ruleSetter
            .ensure(property.name)
            .displayName(property.description);

       // these will set ruleSetter to a FluentRuleCustomizer
        ruleSetter = ruleSetter.required();

        ruleSetter = ruleSetter
            .minLength(property.minLength)
            .withMessage(`${property.description} must contain at least ${property.minLength} characters`));
}
@jdanyow
Copy link
Contributor

jdanyow commented Oct 15, 2016

here's a workaround- let me know if it helps:

const ruleDefinitions = [
  {
    propertyName: 'firstName',
    displayName: 'First Name',
    rules: [
      { name: 'required', args: [] },
      { name: 'minLength', args: [3] },
      { name: 'maxLength', args: [30] },
    ]
  },
  {
    propertyName: 'lastName',
    displayName: 'Last Name',
    rules: [
      { name: 'required', args: [] },
      { name: 'minLength', args: [3] },
      { name: 'maxLength', args: [30] },
    ]
  },
];

let ensureAPI: { ensure: (propertyName: string) => FluentRules<any, any> } = ValidationRules;
for (let ruleDefinition of ruleDefinitions) {
  let ruleAPI: { satisfiesRule: (name: string, ...args: any[]) => FluentRuleCustomizer<any, any> };
  ruleAPI = ensureAPI.ensure(ruleDefinition.propertyName)
    .displayName(ruleDefinition.displayName);
  for (let rule of ruleDefinition.rules) {
    ensureAPI = ruleAPI = ruleAPI.satisfiesRule(rule.name, ...rule.args);
  }
}

let rules = (<FluentEnsure<any>>ensureAPI).rules;

@jdanyow jdanyow changed the title feat(validation) add interface and factory to facilitate looping with fluent rules Add alternative to fluent rule API that enables creating rules from data Oct 15, 2016
@dkent600
Copy link
Contributor Author

Sorry, for the delay in responding....I haven't forgotten about this...

@jdanyow
Copy link
Contributor

jdanyow commented Oct 22, 2016

no worries- let me know if it helps at all

@dkent600
Copy link
Contributor Author

@jdanyow I've played around with your suggestion, above. I particularly wanted to see if my code could be any better using named rules.

It is basically a minor improvement over the code I posted at the top (it mainly gets rid of the if (ruleSetter === null) / else). But the ensureAPI = ruleAPI = are a bit messy, as are the types for ensureAPI and ruleAPI.

It is an interesting exercise, but I prefer the way I proposed.

@Beijerinc
Copy link

I had the same problem with trying to dynamically construct validation rules using the fluent API. I ended up using a 'void' rule to make sure the first call already resulted in a FluentRuleCustomizer instead of a FluentRule.

if (this.dataItem.__validationRules__) {
    rules = this.dataItem.__validationRules__.
        ensure(this.propertyMetadata.Name).
        satisfiesRule("void");
} else {
    rules = ValidationRules.
        ensure(this.propertyMetadata.Name).
        satisfiesRule("void");
}

An interface with the overlap between the FluentRule and FluentRuleCustomizer would be nice to have.

@lstarky
Copy link

lstarky commented Feb 13, 2017

I am also interested in a better way to create Validation Rules from schema data.

@jdanyow
Copy link
Contributor

jdanyow commented Mar 25, 2017

@dkent600 what's the status of this- I think you had done some prototyping but I lost track with the move, etc.

@dkent600
Copy link
Contributor Author

My proposal is here: master...dkent600:Incrementally-add-rules-for-properties-#400

It introduces a FluentRulesGenerator class

@dkent600
Copy link
Contributor Author

Regarding the proposed code enhancement I referenced just above ^^^^ , what follows is an example of what the code enables you to do that accomplishes the task of the original use case given at the top of this issue.

I will also note here that this change also, as an effortless side-effect, addresses #400, though I note that #400 was recently addressed separately. That code could be discarded.

Here is an example of how one can currently accomplish the task at hand:

let ensureApi: { ensure: (propertyName: string) => FluentRules<any, any> } = ValidationRules;

for (let property of formSchema.properties) {

    if (this.requiresValidation(property, formSchema)) {

        let ruleApi: { satisfiesRule: (name: string, ...args: any[]) => FluentRuleCustomizer<any, any> } =
            ensureApi.ensure(property.name).displayName(property.description);

        if (property.required) {
            ruleApi = ruleApi.satisfiesRule("required");
        }

        if (property.minLength !== undefined) {
            ruleApi = ruleApi.satisfiesRule("minLength", property.minLength);
        }
        ensureApi = ruleApi as FluentRuleCustomizer<any, any>;
    }
}
return ensureApi;

Here is how I am able to do it with the proposed code changes:

let ruleGenerator: FluentRulesGenerator<any> = ValidationRules.CreateFluentRulesGenerator();

for (let property of formSchema.properties) {

    if (this.requiresValidation(property, formSchema)) {

        ruleGenerator.ensure(property.name).displayName(property.description);

        if (property.required) {
            ruleGenerator.satisfiesRule("required");
        }

        if (property.minLength !== undefined) {
            ruleGenerator.satisfiesRule("minLength", property.minLength);
        }
    }
}
return ruleGenerator;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants