Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
borderStyledashed
titleStep 1

import groovytools.builder.*

MetaBuilder mb = new MetaBuilder(getClass().getClassLoader())

...

Code Block
borderStyledashed
titleStep 2

class Customer {
    def name
    def dateOfBirth
    def ssn
    def phone
}

mb.define {
    customer(factory: Customer) {
        properties {
            name()
            dateOfBirth()
            ssn()
        }
    }
}

...

Code Block
borderStyledashed
titleStep 3

def aCustomer = mb.build {
    customer {
        name = 'J. Doe'
        dateOfBirth  = '1/1/1900'
        ssn  = '555-55-5555'
    }
}

// this is equivalent
aCustomer = mb.build {
    customer ( name: 'J. Doe', dateOfBirth: '1/1/1900', ssn: '555-55-5555')
}

// you can even mix up the styles:
aCustomer = mb.build {
    customer ( name: 'J. Doe', dateOfBirth: '1/1/1900') {
        ssn = '555-55-5555'
    }
}

...

Code Block
borderStyledashed
titleCatching Errors

aCustomer = mb.build {
    customer {
        name = 'J. Doe'
        dob  = '1/1/1900'      // should have been 'dataOfBirth'
        ssn  = '555-55-5555'
        phone = '1-555-555-5555' // not allowed
    }
}

...

Despite the fact that phone is a an actual property of your class, MetaBuilder only goes by what's in your the schema. That kind of checking is nice to have and can protect private or sensitive properties. Read on to see how MetaBuilder can do even more with some additional information.

...

This next example demonstrates how one might use closure to create
Customer objects:

Code Block
borderStyledashed
titleSpecifying a Factory using a Closure

mb.define {
    customer2(factory: { new Customer() } )) {
        properties {
            name()
            dateOfBirth()
            ssn()
        }
    }
}

def aCustomer2 = mb.build {
    customer2 {
        name = 'J. Doe'
        dateOfBirth  = '1/1/1900'
        ssn  = '555-55-5555'
    }
}

...

  • def: the default value or a Closure to produce the default value, as needed
  • req: if true, a value must be specfied (it could be null though)
  • property: an alternative property name or Closure to use to set the value
  • min: the minimum length for a property value
  • max: the maximum length for a property value
  • check: causes an exception if the value fails the following test:
    Code Block
    borderStyledashed
    
    switch(value) {
        case check: return true
    }
    return false
    
    Tip
    titleTip

    The check attribute accepts the following types of objects which makes enforcing constraints easy:

    • Closures
    • Patterns
    • Classes
    • Numbers
    • Strings
    • Ranges
    • Collections
    Here's an example showing combinations of some of the above:
    Code Block
    borderStyledashed
    titleMetaBuilder Property Attributes in Action
    
    mb.define {
    customer3(factory: Customer) {
        properties {
            name(req: true, min: 1)
            dob(property: 'dateOfBirth')
            ssn(check: ~/\d{3}-\d{2}-\d{4}/)
        }
    }
    
    def aCustomer3 = mb.build {
        customer4 {
            name = 'J. Doe'
            dob = '1/1/1900'
            ssn  = '555-55-5555'
        }
    }
    

...

Code Block
borderStyledashed
titleAdding a Phone Class and Schema

class Phone {
    def type
    def number
}

mb.define {
    phone (factory: Phone) {
        properties {
            type(check: ['home','cell','work'], def: 'home')
            number(req: true, check: ~/\d{3}-\d{3}-\d{4}/)
        }
    }
}

...

Code Block
borderStyledashed
titleUsing the Phone Schema

mb.define {
    customer4(factory: Customer) {
        properties {
            name(req: true, min: 1)
            dob(property: 'dateOfBirth')
            ssn(check: ~/\d{3}-\d{2}-\d{4}/)
            phone(schema: 'phone', req: true)
        }
    }
}

def aCustomer4 = mb.build {
    customer4 {
        name = 'J. Doe'
        dob  = '1/1/1900'
        ssn  = '555-55-5555'
        phone {
            type = 'home'
            number = '123-456-7890'
        }
    }
}

...

Code Block
borderStyledashed
titleDefining Schema with Indexed and Non-Indexed Collections

class Customer2 extends Customer {
    def phoneList = []
    def addresses = [:]
}

mb.define {
    customer5(factory: Customer2) {
        properties {
            name(req: true, min: 1)
            dob(property: 'dateOfBirth')
            ssn(check: ~/\d{3}-\d{2}-\d{4}/)
            phone(schema: 'phone', req: true)
        }
        collections {
            phoneList(min: 1) {
                phone(schema: phone)
            }
            addresses (key: 'type', min: 1, max:2) {
                address() {
                    properties {
                        type(check: ['billto', 'shipto'], def: 'billto')
                        street()
                        city()
                        state()
                        zip()
                    }
                }
            }
        }
    }
}

...

Another thing to note is the use of the key attribute in the addresses collection. Presenece Presence of the key attribute tells MetaBuilder that the parent-child relationship is indexed. In the following example, you can see that customer5 has both a list of phone and map of addresses using the address's type
and the key.

Code Block
borderStyledashed
titleBuilding Objects with Indexed and Non-Indexed Collections

def aCustomer5 = mb.build {
    customer5 {
        name = 'J. Doe'
        dob  = '1/1/1900'
        ssn  = '555-55-5555'
        phone {
            type = 'home'
            number = '123-456-7890'
        }
        phoneList {
            phone(type: 'work', number: '111-222-3333')
            phone(type: 'cell', number: '444-555-6666')
        }
        address {
            type   = 'billto'
            street = '1234 Some St.'
            city   = 'Some City'
            zip    = '12345'
        }
        address {
            type   = 'shipto'
            street = '1234 Some Other St.'
            city   = 'Some Other City'
            zip    = '12345'
        }
    }
}

...