Validatable Delegate

Using the Validatable delegate for object validation

The Validatable@cbValidation delegate (ColdBox 7+) allows you to add validation capabilities directly to any object using ColdBox's delegation pattern. This provides a cleaner, more object-oriented approach to validation.

Overview

Instead of injecting the ValidationManager into every class, you can delegate validation methods directly to your objects. This is available in ColdBox 7 and later.

Basic Setup

Shorthand Syntax

The simplest way to make an object validatable:

User.bx
class delegates="Validatable@cbValidation" {

    property name="id" type="numeric";
    property name="name" type="string";
    property name="email" type="string";

    this.constraints = {
        name: { required: true, size: "2..100" },
        email: { required: true, type: "email" }
    };
}

Selective Method Delegation

Delegate only specific validation methods:

class delegates="Validatable@cbValidation=validate,validateOrFail" {
    property name="sku" type="string";
    property name="price" type="numeric";
}
product.bx
// Only add validate and validateOrFail methods
component delegates="Validatable@cbValidation=validate,validateOrFail" {
    property name="sku" type="string";
    property name="price" type="numeric";
}

Long Syntax with Property Injection

For more control, use explicit property delegation:

order.bx
class {
    property name="validatable"
        inject="Validatable@cbValidation"
        delegate="validate,validateOrFail";

    property name="orderNumber" type="string";
    property name="items" type="array";

    this.constraints = {
        orderNumber: { required: true },
        items: { required: true, arrayItem: {...} }
    };
}

Available Delegated Methods

When using the Validatable delegate, the following methods are available:

  • validate() - Validate and return result object

  • validateOrFail() - Validate or throw exception

  • isValid() - Quick boolean check (stores results)

  • getValidationResults() - Get stored validation results

  • validateHasValue() - Check if a value exists

  • validateIsNullOrEmpty() - Check if value is null/empty

  • assert() - Assert a condition is true

  • getValidationManager() - Access the ValidationManager

Using Delegated Validation Methods

Validate Method

handler.bx
function saveUser(event, rc, prc) {
    var user = entityNew("User");
    populateModel(user);

    // Call validate() directly on the object
    var results = user.validate();

    if (results.hasErrors()) {
        prc.errors = results.getAllErrors();
        return event.setView("user/form");
    }

    entitySave(user);
    return event.setNextRoute("user.view");
}

ValidateOrFail Method

For API endpoints with exception handling:

api.bx
function apiCreateUser(event, rc, prc) {
    var user = entityNew("User");
    populateModel(user);

    try {
        // validateOrFail throws exception if validation fails
        user.validateOrFail();

        entitySave(user);
        return event.renderData(data: user);
    } catch(ValidationException e) {
        return event.renderData(
            statusCode: 422,
            data: { errors: deserializeJSON(e.extendedInfo) }
        );
    }
}

IsValid Method

Quick validation with stored results:

validation-check.bx
function processUser(event, rc, prc) {
    var user = entityNew("User");
    populateModel(user);

    // isValid() stores results and returns boolean
    if (user.isValid()) {
        // User is valid, proceed
        userService.create(user);
    } else {
        // Access stored results
        var results = user.getValidationResults();
        prc.errors = results.getAllErrors();
    }
}

Advanced Usage

Validation with Profiles

Pass profiles for targeted validation:

profile-validation.bx
component delegates="Validatable@cbValidation" {
    property name="name" type="string";
    property name="email" type="string";
    property name="password" type="string";

    this.constraints = {
        name: { required: true },
        email: { required: true, type: "email" },
        password: { required: true, size: "8..50" }
    };

    this.constraintProfiles = {
        registration: "name,email,password",
        update: "name,email",
        passwordChange: "password"
    };
}

// In handler:
function updateUser(event, rc, prc) {
    var user = userService.getById(rc.userId);
    populateModel(user);

    // Validate only update profile fields
    if (user.isValid(profiles: "update")) {
        userService.update(user);
    }
}

Custom Validation with Callbacks

Combine validation with business logic:

callback-validation.bx
component delegates="Validatable@cbValidation" {
    property name="sku" type="string";
    property name="price" type="numeric";

    this.constraints = {
        sku: { required: true, regex: "^[A-Z0-9]+$" },
        price: { required: true, min: 0.01 }
    };

    // Custom validation method
    public void function validateUniqueSku() {
        if (productService.skuExists(this.sku)) {
            throw("ValidationException", "SKU '#sku#' already exists");
        }
    }
}

// In handler:
function createProduct(event, rc, prc) {
    var product = entityNew("Product");
    populateModel(product);

    try {
        product.validateOrFail();
        product.validateUniqueSku();  // Custom validation

        productService.create(product);
        return event.setNextRoute("product.view");
    } catch(ValidationException e) {
        prc.errors = [e.message];
    }
}

Best Practices

1. Use Shorthand for Full Delegation

For simple validation needs, use the shorthand syntax:

component delegates="Validatable@cbValidation" {
    // All validation methods available
}

2. Use Selective Delegation for Lightweight Objects

For objects that only need specific methods:

component delegates="Validatable@cbValidation=validate,isValid" {
    // Only these methods available
}

3. Always Define Constraints

Ensure your object has constraints defined:

component delegates="Validatable@cbValidation" {
    this.constraints = {
        // Define all constraints here
    };
}

4. Use isValid() for Simple Checks

For quick validation without error details:

if (user.isValid()) {
    processUser(user);
}

5. Use validateOrFail() for APIs

For REST endpoints where you need exception handling:

try {
    user.validateOrFail();
    // Process user
} catch(ValidationException e) {
    return errorResponse(e);
}

6. Combine with Custom Methods

Add custom validation logic to your objects:

public boolean function isEligible() {
    return this.age >= 18 && this.isValid(profiles="eligibility");
}

Supported ColdBox Versions

The Validatable delegate requires:

  • ColdBox 7.0+

  • cbValidation 4.1.0+

For earlier ColdBox versions, use the mixin methods via validate() and validateOrFail().

See Also

Last updated

Was this helpful?