# Validatable Delegate

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:

{% tabs %}
{% tab title="BoxLang" %}
{% code title="User.bx" overflow="wrap" lineNumbers="true" %}

```javascript
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" }
    };
}
```

{% endcode %}
{% endtab %}

{% tab title="CFML" %}
{% code title="User.cfc" overflow="wrap" lineNumbers="true" %}

```javascript
component 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" }
    };
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Selective Method Delegation

Delegate only specific validation methods:

{% tabs %}
{% tab title="BoxLang" %}

```javascript
class delegates="Validatable@cbValidation=validate,validateOrFail" {
    property name="sku" type="string";
    property name="price" type="numeric";
}
```

{% code title="product.bx" overflow="wrap" lineNumbers="true" %}

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

{% endcode %}
{% endtab %}

{% tab title="CFML" %}
{% code title="product.cfc" overflow="wrap" lineNumbers="true" %}

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

{% endcode %}
{% endtab %}
{% endtabs %}

### Long Syntax with Property Injection

For more control, use explicit property delegation:

{% tabs %}
{% tab title="BoxLang" %}
{% code title="order.bx" overflow="wrap" lineNumbers="true" %}

```javascript
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: {...} }
    };
}
```

{% endcode %}
{% endtab %}

{% tab title="CFML" %}
{% code title="order.cfc" overflow="wrap" lineNumbers="true" %}

```javascript
component {
    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 = {...} }
    };
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

## 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

{% tabs %}
{% tab title="BoxLang" %}
{% code title="handler.bx" overflow="wrap" lineNumbers="true" %}

```javascript
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");
}
```

{% endcode %}
{% endtab %}

{% tab title="CFML" %}
{% code title="handler.cfc" overflow="wrap" lineNumbers="true" %}

```javascript
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");
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### ValidateOrFail Method

For API endpoints with exception handling:

{% tabs %}
{% tab title="BoxLang" %}
{% code title="api.bx" overflow="wrap" lineNumbers="true" %}

```javascript
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) }
        );
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="CFML" %}
{% code title="api.cfc" overflow="wrap" lineNumbers="true" %}

```javascript
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) }
        );
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### IsValid Method

Quick validation with stored results:

{% tabs %}
{% tab title="BoxLang" %}
{% code title="validation-check.bx" overflow="wrap" lineNumbers="true" %}

```javascript
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();
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="CFML" %}
{% code title="validation-check.cfc" overflow="wrap" lineNumbers="true" %}

```javascript
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();
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Advanced Usage

### Validation with Profiles

Pass profiles for targeted validation:

{% tabs %}
{% tab title="BoxLang" %}
{% code title="profile-validation.bx" overflow="wrap" lineNumbers="true" %}

```javascript
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);
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="CFML" %}
{% code title="profile-validation.cfc" overflow="wrap" lineNumbers="true" %}

```javascript
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);
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Custom Validation with Callbacks

Combine validation with business logic:

{% tabs %}
{% tab title="BoxLang" %}
{% code title="callback-validation.bx" overflow="wrap" lineNumbers="true" %}

```javascript
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];
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="CFML" %}
{% code title="callback-validation.cfc" overflow="wrap" lineNumbers="true" %}

```javascript
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];
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Best Practices

### 1. Use Shorthand for Full Delegation

For simple validation needs, use the shorthand syntax:

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

### 2. Use Selective Delegation for Lightweight Objects

For objects that only need specific methods:

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

### 3. Always Define Constraints

Ensure your object has constraints defined:

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

### 4. Use isValid() for Simple Checks

For quick validation without error details:

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

### 5. Use validateOrFail() for APIs

For REST endpoints where you need exception handling:

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

### 6. Combine with Custom Methods

Add custom validation logic to your objects:

```javascript
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

* [Installation & Mixins](https://github.com/ortus-docs/cbvalidation-docs/blob/v4.x/installation.md) - Mixin methods for handlers, views, interceptors
* [Validation Results](https://github.com/ortus-docs/cbvalidation-docs/blob/v4.x/overview/README.md) - Understanding validation result objects
* [Custom Validators](/advanced/advanced-custom-validators.md) - Creating custom validation logic


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://coldbox-validation.ortusbooks.com/advanced/validatable-delegate.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
