Photo from Chile

ValidateThis! - Let's Talk Metadata

In this installment of my series about object oriented validations with ColdFusion I'm going to discuss the metadata that drives all of the client and server-side validations, as well as how a developer can describe that metadata to the framework.

We'll start by looking at a sample xml file, the one that is used to drive the demo in fact. I want to make it clear, however, that the framework is in no way dependent on this xml schema. One can create an xml document based on any schema, as long as it includes the necessary metadata. As well, one can load metadata into the framework programmatically, eliminating the need for xml altogether.

view plain print about
2    <properties>
3        <property name="UserName" desc="Email Address">
4            <rules>
5                <rule type="required" contexts="Register,Update" />
6                <rule type="email" contexts="Register,Update"
7                    failureMessage="Hey, buddy, you call that an Email Address?" />

8            </rules>
9        </property>
10        <property name="Nickname">
11            <rules>
12                <rule type="custom" contexts="Register,Update">
13                    <params>
14                        <param methodname="CheckDupNickname" />
15                    </params>
16                </rule>
17            </rules>
18        </property>
19        <property name="UserPass" desc="Password">
20            <rules>
21                <rule type="required" contexts="Register,Update" />
22                <rule type="rangelength" contexts="Register,Update">
23                    <params>
24                        <param minlength="5" />
25                        <param maxlength="10" />
26                    </params>
27                </rule>
28                <rule type="equalTo" contexts="Register,Update">
29                    <params>
30                        <param ComparePropertyName="VerifyPassword" />
31                    </params>
32                </rule>
33            </rules>
34        </property>
35        <property name="VerifyPassword" desc="Verify Password">
36            <rules>
37                <rule type="required" contexts="Register,Update" />
38            </rules>
39        </property>
40        <property name="FirstName" desc="First Name">
41            <rules>
42                <rule type="required" contexts="Update" />
43            </rules>            
44        </property>
45        <property name="LastName" desc="Last Name">
46            <rules>
47                <rule type="required" contexts="Update" />
48                <rule type="required" contexts="Register">
49                    <params>
50                        <param DependentPropertyName="FirstName" />
51                    </params>
52                </rule>
53            </rules>            
54        </property>
55        <property name="LikeOther" desc="What do you like?">
56            <rules>
57                <rule type="required" contexts="Register,Update"
58                    failureMessage="If you don't like Cheese and you don't like Chocolate, you must like something!">

59                    <params>
60                        <param ServerCondition="getLikeCheese() EQ 0 AND getLikeChocolate() EQ 0" />
61                        <param ClientCondition="$(&quot;[name='LikeCheese']&quot;).getValue() == 0 &amp;&amp; $(&quot;[name='LikeChocolate']&quot;).getValue() == 0;" />
62                    </params>
63                </rule>
64            </rules>            
65        </property>
66        <property name="HowMuch" desc="How much money would you like?">
67            <rules>
68                <rule type="numeric" contexts="Register,Update" />
69            </rules>            
70        </property>
71        <property name="AllowCommunication" desc="Allow Communication">
72        </property>
73        <property name="CommunicationMethod" desc="Communication Method">
74            <rules>
75                <rule type="required" contexts="Register,Update"
76                    failureMessage="If you are allowing communication, you must choose a communication method.">

77                    <params>
78                        <param DependentPropertyName="AllowCommunication" />
79                        <param DependentPropertyValue="1" />
80                    </params>
81                </rule>
82            </rules>            
83        </property>
84    </properties>

Let's walk through the xml document, identifying the metadata as we go. First off you'll notice that we have properties, which correspond to the properties of your Business Object (things like UserName, FirstName, LastName, etc.). Each property has a name attribute, which again corresponds to the name of the property in the Business Object, and a desc attribute, which provides a "friendly" description. The desc is used to generate validation failure messages.

Within each property we define rules. These are the actual validation rules that the framework requires. Each rule must have a type attribute, which describes the validation type (e.g., required, email, custom, etc.). The following attributes are optional for a rule:

  • contexts - If a validation rule only applies to the Business Object within a particular context (e.g., if a User is being registered) a list of contexts may be specified.
  • failureMessage - A failure message may be specified which will override the default failure message generated by the framework.

Within each rule there is an optional params element, which contain param elements which describe parameters for the rule in question. Some rule types require parameters while others do not. The xml document above shows some examples:

  • The custom rule type requires a methodname parameter which contains the name of the method in the Business Object that must be invoked in order to perform the validation.
  • The rangelength rule type requires both a minlength and a maxlength parameter which are used to validate the length of the property's value.
  • The equalTo rule type requires a ComparePropertyName parameter which is used to determine whether the property in question is equal to another property.

The required rule type has a number of optional parameters, which can be used to make the required rule conditional:

  • Specifying only a DependentPropertyName parameter indicates that the property in question is only required if another property has been populated. In the example above, the LastName property is only required if the FirstName property has been populated.
  • Specifying a DependentPropertyName parameter in conjunction with a DependentPropertyValue parameter indicates that the property in question is only required if another property has a specific value. In the example above, the CommunicationMethod property is only required if the AllowCommunication property has a value of 1.
  • Specifying a ServerCondition parameter indicates that the property in question is only required if the condition specified evaluates to true. In the example above, the LikeOther property is only required if both the LikeCheese and the LikeChocolate properties have a value of 0. This only applies to server-side validations.
  • Specifying a ClientCondition parameter also indicates that the property in question is only required if the condition specified evaluates to true. In the example above, we are testing the same condition, that is "the LikeOther property is only required if both the LikeCheese and the LikeChocolate properties have a value of 0". The difference here is that we define the actual JavaScript statement that will be executed to determine if this is true. Obviously this only applies to client-side validations.

And that is all of the metadata that is currently used by the framework. Note that the description of possible params above is not exhaustive. Any new validation type can add any number of required and/or optional parameters.

If one chooses to define these rules in an xml file, which is certainly the way I do it, one can format them in any way, as long as all of the required metadata is included. Of course if you create a new schema you'll have to create a new XMLFileReader object, which will be used to replace the default XMLFileReader object included with the framework, but that's dead easy.

As I mentioned above, for those of you who harbour a dislike for xml (Matt), there are two methods available to you which will allow you to define all of the validation rules programmatically:

  • The addRule method allows you to add a rule, passing in parameters for propertyName, valType, propertyDesc (optional), contexts (optional), failureMessage (optional), and params (optional).
  • The addPropertyRules method allows you to add multiple rules for a single property, passing in parameters for propertyName, propertyDesc (optional), and an array of structs that represents the data for the rules.

And that's where things stand as of today. What do you think? Have I missed any important metadata that would be required for you to define validation rules?

I have been following this project with great interest because I wanted to do something like it not to long ago. Keep up the great work!
# Posted By Dan Vega | 10/17/08 5:48 AM