A coldspring-based and context aware validation rule

17 views
Skip to first unread message

Benoit HEDIARD

unread,
Mar 17, 2011, 6:49:06 AM3/17/11
to hyrule-v...@googlegroups.com
Here is the approach we have taken to implement coldspring-based and context aware validation rule in our app.
There is perpaps a better way to do it (and better naming), it is only some thoughts on the Hyrule 0.2 evolution...

So we made the following improvements in Hyrule :
1. ICustomValidator and IContextValidator  - Two new type of rules which can take a context (a string) and the full DTO (cfc)
2. Validator.cfc - New beanFactory property added
3. Validator.cfc - New validator types implemented: @custom, @customList, @bean and @beanList.

------------------------------
ICustomValidator.cfc
interface  {
public boolean function isValid(Struct prop, Any dto);
}
------------------------------
IContextValidator.cfc
interface  {
public boolean function isValid(Struct prop, String context, Any dto);
}
------------------------------
Validator.cfc
...
property Any beanFactory;
...
public ValidationResult function validate(required any dto,Struct dtoMD,ValidationResult vr,String context="*"){
...
case "CUSTOM" : {
try {
validator = createObject("component","#prop.custom#");
if( !validator.isValid(prop, arguments.context, arguments.dto) ) {
addErrorMessage(componentName=dtoMetaData.name, property=prop, validationResult=validationResult, validationType=custom);
}
} catch(any e) {
throw(type="ValidatorError", message="Custom validation component #prop.custom# not found");
}
break;
}
case "CUSTOMLIST" : {
for (var custom in listToArray(prop.customList)) {
try {
validator = createObject("component","#custom#");
if( !validator.isValid(prop, arguments.context, arguments.dto) ) {
addErrorMessage(componentName=dtoMetaData.name, property=prop, validationResult=validationResult, validationType=custom);
}
} catch(any e) {
throw(type="ValidatorError", message="Custom validation component #custom# not found");
}
}
break;
}
case "BEAN" : {
if (isNull(getBeanFactory())) {
throw(type="ValidatorError", message="Bean Factory is not injected in Validator");
}
validator = getBeanFactory().getBean(prop.bean);
break;
}
case "BEANLIST" : {
if (isNull(getBeanFactory())) {
throw(type="ValidatorError", message="Bean Factory is not injected in Validator");
}
for (var beanName in listToArray(prop.beanList)) {
validator = getBeanFactory().getBean(beanName);
if( !validator.isValid(prop, arguments.context, arguments.dto) ) {
addErrorMessage(componentName=dtoMetaData.name, property=prop, validationResult=validationResult, validationType=beanName);
}
}
break;
}


------------------------------------
EXAMPLE OF USE
------------------------------------

Here is a validation rule based on IContextValidator in our app.
The User model has an email property.
This email property has unicity constraints which must be context aware :
- during the creation, the email must not exists in the DB,
- during the update, the email should not exists in the DB if it is changed.
Context is not really required in this example (because we could check the user.getId() value), this only for the proof of concept.

------------------------------
User.cfc
...
/**
 * @display Email
 * @email
 * @bean userUniqueEmail
 */
property name="email" type="String" index="ix_user_email" notnull="true";
...
------------------------------
UserDAO.cfc
...
public myapp.model.user.User function getUserByEmail(required String email) {
var user = entityLoad("User", {email = arguments.email}, true);
if (isNull(user)) {
user = new myapp.model.user.User();
}
return user;
}

public Boolean function validateUniqueEmail(required String email, Numeric id = 0) {
var valid = false;
if (arguments.email != "") {
var user = getUserByEmail(email=arguments.email);
if (isNull(user) || user.getId() == arguments.id) {
valid = true;
}
}
return valid;
}
...
------------------------------
UserUniqueEmail.cfc
component accessors="true" implements="hyrule.rules.IContextValidator" {
property name="userDAO" type="mycompany.model.user.UserDAO";
public Boolean function isValid(Struct prop, String context, Any dto){
// Example of context aware validation
if (arguments.context == "create") {
return getUserDAO().validateUniqueEmail(email=arguments.prop.value);
} else {
return getUserDAO().validateUniqueEmail(email=arguments.prop.value, id=arguments.dto.getId());
}
}

}
------------------------------
UserService.cfc
...
property name="validator" type="hyrule.Validator";
...
public mycompany.model.user.User function create( ... ) {
...
// Validate var validation = getValidator().validate(context="create", dto=user); if (validation.hasErrors()) { request.context.errors = validation.getErrors(); } else { // Save getUserDAO().saveUser(user); ... } return user;
}
...
public mycompany.model.user.User function create( ... ) {
...
// Validate var validation = getValidator().validate(context="update", dto=user); if (validation.hasErrors()) { request.context.errors = validation.getErrors(); } else { // Save getUserDAO().saveUser(user); ... } return user;
}
...


UserDAO, UserService, UserUniqueEmail and Validator are loaded into a ColdSpring factory (autowired by name).
model-config.xml
....
<bean id="userDAO" class="mycompany.model.user.UserDAO" />
<bean id="userService" class="mycompany.services.UserService" />
<bean id="userUniqueEmail" class="mycompany.model.user.rules.UserUniqueEmail" />
<bean id="validator" class="hyrule.Validator" />
...

Bean factory is loaded into the Validator instance during app initialization.

Again, there is perhaps better way to do this.
This is only food for thoughts.


-- 
Benoit HEDIARD

Reply all
Reply to author
Forward
0 new messages