Domain Object

Domain object constraints are the most powerful and maintainable way to define validation rules in CBValidation. By declaring constraints directly within your model objects, you create self-validating entities that encapsulate both data structure and validation logic.

Overview

Within any domain object (model, entity, or component), you can define a public variable called this.constraints that contains validation rules for your object's properties. This approach provides:

  • Encapsulation: Validation rules live with the data they validate

  • Reusability: Objects carry their validation wherever they're used

  • Maintainability: Single source of truth for validation logic

  • IDE Support: Auto-completion and syntax highlighting for constraints

Basic Declaration

models/User.bx
// Object properties
property id;
property fname;
property lname;
property email;
property username;
property password;
property age;

// Validation constraints
this.constraints = {
    // Constraints go here
};

Complete Constraint Definitions

Here's a comprehensive example showing various constraint types and validation rules:

models/User.bx
property id;
property fname;
property lname;
property email;
property username;
property password;
property age;
property phone;
property website;
property birthDate;

// Comprehensive validation constraints
this.constraints = {
    fname = {
        required = true,
        requiredMessage = "First name is required",
        size = "2..50",
        sizeMessage = "First name must be 2-50 characters"
    },

    lname = {
        required = true,
        requiredMessage = "Last name is required",
        size = "2..50",
        sizeMessage = "Last name must be 2-50 characters"
    },

    username = {
        required = true,
        size = "6..20",
        regex = "^[a-zA-Z0-9_]+$",
        regexMessage = "Username can only contain letters, numbers, and underscores"
    },

    password = {
        required = true,
        size = "8..128",
        sizeMessage = "Password must be at least 8 characters long"
    },

    email = {
        required = true,
        type = "email",
        typeMessage = "Please enter a valid email address"
    },

    age = {
        required = true,
        type = "numeric",
        range = "13..120",
        rangeMessage = "Age must be between 13 and 120"
    },

    phone = {
        regex = "^\d{3}-\d{3}-\d{4}$",
        regexMessage = "Phone must be in format: 123-456-7890"
    },

    website = {
        type = "url",
        typeMessage = "Please enter a valid website URL"
    },

    birthDate = {
        type = "date",
        before = "#now()#",
        beforeMessage = "Birth date must be in the past"
    }
};

By default all properties are of type string and not required. Always explicitly define your constraints for clarity and maintainability.

Constraint Profiles for Targeted Validation

Use constraint profiles to validate only specific fields for different scenarios:

models/User.bx
// Properties...

this.constraints = {
    // ... constraint definitions ...
};

// Constraint profiles for different validation scenarios
this.constraintProfiles = {
    registration = "fname,lname,email,username,password",
    profileUpdate = "fname,lname,email,phone,website",
    login = "username,password",
    passwordReset = "email"
};

Usage in Your Application

Basic Validation

Once constraints are defined, you can validate objects directly:

handlers/UserHandler.bx
// Validate the entire user object
var result = validate( user );

if ( result.hasErrors() ) {
    prc.errors = result.getAllErrors();
    return event.setView( "users/register" );
}

// Save valid user
userService.save( user );

event.setView( "users/success" );

Profile-Based Validation

Use constraint profiles for specific validation scenarios:

handlers/UserHandler.bx
// Only validate profile update fields
var result = validate( target=user, profiles="profileUpdate" );

if ( result.hasErrors() ) {
    prc.errors = result.getAllErrors();
    return event.setView( "users/profile" );
}

userService.update( user );

flash.put( "notice", "Profile updated successfully" );
event.setView( "users/profile" );

Exception-Based Validation

For APIs or scenarios where you want validation failures to throw exceptions:

handlers/API/UserHandler.bx
    // Throws ValidationException if validation fails
    validateOrFail( target=user, profiles="registration" );

    var savedUser = userService.save( user );

    return event.renderData(
        statusCode=201,
        data={ "user": savedUser, "message": "User created successfully" }
    );

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

Advanced Domain Object Examples

Complex Nested Objects

Domain objects can contain complex nested structures:

models/Employee.bx
property id;
property firstName;
property lastName;
property email;
property department;
property salary;
property hireDate;
property address;
property phoneNumbers;

this.constraints = {
    firstName = { required=true, size="2..50" },
    lastName = { required=true, size="2..50" },
    email = { required=true, type="email" },
    department = { required=true, inList="Engineering,Sales,Marketing,HR" },
    salary = { required=true, type="numeric", min="0" },
    hireDate = { required=true, type="date", before="#now()#" },

    // Nested address validation
    "address.street" = { required=true, size="5..100" },
    "address.city" = { required=true, size="2..50" },
    "address.state" = { required=true, size="2" },
    "address.zip" = { required=true, regex="^\d{5}(-\d{4})?$" },

    // Array validation for phone numbers
    "phoneNumbers.*.type" = { required=true, inList="mobile,home,work" },
    "phoneNumbers.*.number" = { required=true, regex="^\d{3}-\d{3}-\d{4}$" }
};

this.constraintProfiles = {
    basic = "firstName,lastName,email",
    complete = "firstName,lastName,email,department,salary,hireDate",
    contact = "firstName,lastName,email,phoneNumbers.*"
};

Best Practices

1. Use Meaningful Constraint Names

Match constraint field names exactly to your object properties for clarity and maintainability.

2. Provide Custom Messages

Always include custom error messages that are user-friendly and actionable.

3. Leverage Constraint Profiles

Use profiles to validate only relevant fields for different business scenarios.

Organize constraints logically within your object definition for better readability.

5. Handle Nested Data

Use dot notation and asterisk wildcards for complex data structures.

6. Validate Early and Often

Validate at the domain object level to catch issues before they propagate through your application.

Domain object constraints provide the most maintainable and reusable approach to validation in CBValidation, ensuring your business objects are always self-validating and consistent across your entire application.

Last updated

Was this helpful?