Validation Problem

0 views
Skip to first unread message

Jamie Jackson

unread,
Mar 27, 2007, 4:20:46 PM3/27/07
to transfer-dev
I've gone the route of having a validate function in my decorator.

I just ran into a problem with this approach, and am hoping that
there's a simple solution:

I have an object with dates and numerics, and if I input, say, strings
in those form fields, the Transfer Object chokes due to bad data
types.*

Does anyone have any solutions?

Thanks,
Jamie

*The argument PERIODENDDATE passed to function setperiodEndDate() is
not of type date.

Mark Mandel

unread,
Mar 27, 2007, 7:25:11 PM3/27/07
to transf...@googlegroups.com
Jamie,

TransferObjects have typed getters and setters, to avoid having bad
data inside them.

There are two ways in which you can handle this:

1) Write decorator methods that overwrite the original setters to take
strings, and if a string is not allowed, then validate() should return
false.
2) Do a pre-validation check for type values on the setters you are
calling (we do this, and it is automated by comparing function meta
data types against the types of the arguments in a setter we are
dynamically invoking)

How's that?

Mark


--
E: mark....@gmail.com
W: www.compoundtheory.com

Jamie Jackson

unread,
Mar 27, 2007, 9:06:52 PM3/27/07
to transfer-dev
On Mar 27, 7:25 pm, "Mark Mandel" <mark.man...@gmail.com> wrote:
> Jamie,
>
> TransferObjects have typed getters and setters, to avoid having bad
> data inside them.
>
> There are two ways in which you can handle this:
>
> 1) Write decorator methods that overwrite the original setters to take
> strings, and if a string is not allowed, then validate() should return
> false.

I can override the setter but I can't call the super function
getTransferObject().setMyProperty(arguments.myProperty), because it
violates the super function. I can make the setter set an instance
var, then override the getter, but the native transfer functions don't
seem to use accessors, so, for instance, a myObj.getMemento() doesn't
pull in my overwritten value.

Anyway, the main problem is I chose a bad tack: I tried to use a TO as
a form bean: I'm populating a TO from a form submission, then I'm
passing that same TO bean back to the form for redisplay of form
fields when there are validation errors, or for editing/viewing of
existing records.

This strategy seemed to work alright in the beginning, but it's
falling apart. I'm going to have to stick to my guns and do some hacks
to get it to work, but I think I'd have rather gone with a form bean
with associated validation.

> 2) Do a pre-validation check for type values on the setters you are
> calling (we do this, and it is automated by comparing function meta
> data types against the types of the arguments in a setter we are
> dynamically invoking)

I'd be interested in seeing a snippet, if you have one, since I think
this might ease the hack I'm going to have to do. (I'm not suggesting
that your implementation is a hack, but the way I'd have to slap it on
would be.)

> How's that?

Pretty good, thanks. ;-)

However, I would be interested in knowing where I went wrong according
to the folks who use myTO.validate() functions. I know some folks are
using it, but wonder how they work around this issue.

> Mark


>
> On 3/28/07, Jamie Jackson <mySpa...@gmail.com> wrote:
>
>
>
>
>
> > I've gone the route of having a validate function in my decorator.
>
> > I just ran into a problem with this approach, and am hoping that
> > there's a simple solution:
>
> > I have an object with dates and numerics, and if I input, say, strings
> > in those form fields, the Transfer Object chokes due to bad data
> > types.*
>
> > Does anyone have any solutions?
>
> > Thanks,
> > Jamie
>
> > *The argument PERIODENDDATE passed to function setperiodEndDate() is
> > not of type date.
>
> --

> E: mark.man...@gmail.com
> W:www.compoundtheory.com

Nando

unread,
Mar 27, 2007, 9:32:39 PM3/27/07
to transf...@googlegroups.com
Jamie,

Not sure what Mark is going to show you, but i simply overwrote the getter and setter in my decorator, like this:

<cffunction name="setDateLastFeatured" output="false" returntype="void">
        <cfargument name="dateLastFeatured" type="any" required="true">
       
        <cfif Len(arguments.dateLastFeatured) EQ 0>
            <cfset setDateLastFeaturedNull() />
            <cfset setIsDateLastFeaturedValid(true) />
        <cfelseif isDate(arguments.dateLastFeatured)>
            <cfset getTransferObject().setDateLastFeatured(arguments.dateLastFeatured) />
            <cfset setIsDateLastFeaturedValid(true) />
        <cfelse>
            <!--- leave value unchanged, but set valid flag to false
                     to be picked up by validator --->
            <cfset setIsDateLastFeaturedValid(false) />
        </cfif>
       
    </cffunction>
   
    <cffunction name="getDateLastFeatured" access="public" output="false" returntype="any">
       
        <cfif getDateLastFeaturedIsNull()>
            <cfreturn "" />
        <cfelse>
            <cfreturn getTransferObject().getdateLastFeatured() />
        </cfif>
       
    </cffunction>

Ummm, if you're using the get and setMemento(), Mark told me that they were internal to the functioning of the Transfer object and shouldn't be used. Maybe you're running into the reason why? Not sure

I'm using this instead inside my decorator (the architecture could be better as Mark pointed out, but this is from a simple test app)

<cffunction name="setInstanceFromStruct">
        <cfargument name="data" required="true" type="struct"
                    hint="The struct containing the data to set." />
        <cfloop index="i" list="#variables.fieldList#">
            <cfif StructKeyExists(arguments.data ,i)>
                <cfinvoke method="set#i#">
                    <cfinvokeargument name="#i#" value="#arguments.data[i]#" />
                </cfinvoke>
            </cfif>
        </cfloop>
    </cffunction>

On 3/28/07, Jamie Jackson <mySp...@gmail.com> wrote:

Jamie Jackson

unread,
Mar 27, 2007, 10:18:47 PM3/27/07
to transfer-dev
Thanks a lot, Nando.

I'm coming to a clearer conclusion after the answers here and on
#transfer:

My requirement: If a user enters a bad date, like "foo 8 1979" (or a
string instead of a numeric for that matter), I want to redisplay "foo
8 1979" along with a validation error above it, so they can make they
appropriate changes.

My original approach: I thought I'd be able to use the Transfer (TM)
Object for everything, including populating it and sending it back to
the form. The form would get data from the Transfer (TM) Object like
#program.getAddress().getState().getName()# (in the case of a deep-ish
nested object).

My conclusion: The above approach is a bad idea. A bad date can't make
it into the Transfer (TM) Object, so it can't be validated in a
myTransferObject.validate() function. Also, the Transfer (TM) Object
can't be sent back to the form with that bad data, because it can't
have gotten into the Transfer (TM) Object in the first place.

The solution: I think I should have created either a Transfer Object
(not as in Transfer-ORM, but the traditional TO pattern) or a simple
Form-modeling Bean, which wouldn't need specific data types. The TO or
Form Bean would either have a validate() function, or would call some
external Validator.

I'd appreciate your comments, either refuting or supporting my
findings...

Thanks,
Jamie

Jaime Metcher

unread,
Mar 27, 2007, 10:56:05 PM3/27/07
to transf...@googlegroups.com
Jamie,

A lot of people want all validation in the model and for the controller to just pass raw form data to the model.  This is what Nando's approach achieves.

Personally I'm quite happy to expect my UI (controller + view) layer to be a bit disciplined in what it passes on to the model  Given that the controller has to know the model's API anyway, I don't mind making it a typed API.  Therefore the controller has to keep its own working storage for the raw form data, and do a bit of prevalidation.  This is your form bean idea and that's pretty much what I do.

The validate methods in my decorators then just have to worry about the semantics of the model, not the syntax of the inputs, which to me is a UI issue.

Jaime Metcher

On 3/28/07, Jamie Jackson <mySp...@gmail.com> wrote:
> > getTransferObject().setMyProperty( arguments.myProperty), because it

Jamie Jackson

unread,
Mar 28, 2007, 10:02:33 AM3/28/07
to transfer-dev
Thanks for the feedback.

Out of curiosity, what do your forms want?
*a nested bean (needing chained gets sometimes)?
*a flat bean?
*items straight from the viewstate/event?
or
*items from a struct in the viewstate/event?

I have a complex form, with compositions, etc., and I don't have a
great handle on the pros and cons of these different approaches.
(Though I've pretty much figured out the cons of passing the form a
Transfer (TM) Object.) :-O

Thanks,
Jamie

On Mar 27, 10:56 pm, "Jaime Metcher" <jmetc...@gmail.com> wrote:
> Jamie,
>
> A lot of people want all validation in the model and for the controller to
> just pass raw form data to the model. This is what Nando's approach
> achieves.
>
> Personally I'm quite happy to expect my UI (controller + view) layer to be a
> bit disciplined in what it passes on to the model Given that the controller
> has to know the model's API anyway, I don't mind making it a typed API.
> Therefore the controller has to keep its own working storage for the raw
> form data, and do a bit of prevalidation. This is your form bean idea and
> that's pretty much what I do.
>
> The validate methods in my decorators then just have to worry about the
> semantics of the model, not the syntax of the inputs, which to me is a UI
> issue.
>
> Jaime Metcher
>

> > > > getTransferObject().setMyProperty(arguments.myProperty), because it

Jaime Metcher

unread,
Mar 28, 2007, 4:26:10 PM3/28/07
to transf...@googlegroups.com
Jamie,

This probably doesn't help, but I would use any of these approaches depending on the situation.  I try to avoid complex forms, but where I do have one I tend to use a composite form object with a composite controller, so each subcontroller to subform interaction is "flat" with respect to the form bean.

It sounds like you're using ModelGlue (?) which is something I haven't got into yet, and maybe requires a different approach.  From what I've seen the viewstate/event basically constitutes another global scope, so I'd definitely be using beans and/or structs in there just to manage that namespace.

Jaime

Jamie Jackson

unread,
Mar 28, 2007, 4:50:12 PM3/28/07
to transfer-dev
Well, I mentioned that I didn't have time to switch to dedicated
FormBeans, and that I'd have to find a workaround.

Note, I'm not necessarily advocating using Transfer (TM) Objects for
user input, but I managed to stuff the bad data into my Transfer (TM)
Objects using separate instance variables in the decorator.

The following handles and redisplays empty strings (for nulls) as well
as stuffs bad data into the object (which I wanted):

=========== TRANSFER DECORATOR ==============
<cfcomponent displayname="Program" hint="I model a program."
extends="transfer.com.TransferDecorator" output="false">

<cffunction access="public" output="false" name="configure">
<cfscript>
setPeriodStartDateNull();
setPeriodEndDateNull();
</cfscript>
</cffunction>

<cffunction name="setperiodStartDate" access="public"
returntype="void" output="false">
<cfargument name="periodStartDate" type="string" />
<cfset setTransferObjectVariable("periodStartDate",
arguments.periodStartDate, true, true) />
</cffunction>
<cffunction name="getperiodStartDate" access="public" output="false">
<cfreturn getTransferObjectVariable("periodStartDate") />
</cffunction>

<cffunction name="setperiodEndDate" access="public" returntype="void"
output="false">
<cfargument name="periodEndDate" type="string" />
<cfset setTransferObjectVariable("periodEndDate",
arguments.periodEndDate, true, true) />
</cffunction>
<cffunction name="getperiodEndDate" access="public" output="false">
<cfreturn getTransferObjectVariable("periodEndDate") />
</cffunction>

<cffunction name="validate" access="public" returntype="boolean"
output="false">
<!--- snip --->
</cffunction>

<!--- Pull in mix-ins --->
<cfinclude template="../mixins/TransferObjectVariableAccessors.cfm">

</cfcomponent>

=========== MIXIN ============

<!--- TODO: factor these methods out --->
<cffunction name="setTransferObjectVariable" access="private"
returntype="void" output="false">
<cfargument name="propertyName" type="string" required="true" />
<cfargument name="propertyValue" type="string" required="true" />
<cfargument name="setNullIfEmpty" type="boolean" default="false" />
<cfargument name="doCheckType" type="boolean" default="false" />
<cfset var local = structNew() />
<!--- get the original transfer object (the one we're extending...
kinda like "super") --->
<cfset local.transferObject = getTransferObject() />
<!--- this will be used in our local data type validation. init it
to false --->
<cfset local.isValid = true />
<!--- init our substitute instance variable to an empty string --->
<cfset variables.instance["#arguments.propertyName#String"] = "" />

<cfif arguments.setNullIfEmpty and trim(arguments.propertyValue) is
"" ><!--- passed value was an empty string --->
<!--- set property to null --->
<cfinvoke component="#local.transferObject#"
method="set#arguments.propertyName#Null" />
<cfelse><!--- passed value was not an empty string --->
<cfif arguments.doCheckType >
<!--- get target data type via reflection --->
<cfset local.dataType =
getMetaData(local.transferObject["set#arguments.propertyName#"]).parameters[1].type /
>
<!--- check if the passed value is valid, according to the
accepted data type --->
<!--- <cfset local.isValid =
evaluate("is#local.dataType#(#arguments.propertyValue#)") /> --->
<cfswitch expression="#local.dataType#">
<cfcase value="numeric">
<cfset local.isValid = isNumeric(arguments.propertyValue) />
</cfcase>
<cfcase value="date">
<cfset local.isValid = isDate(arguments.propertyValue) />
</cfcase>
</cfswitch>
</cfif>
<cfif arguments.doCheckType and local.isValid ><!--- value is of
the correct datatype --->
<!--- set the super setter --->
<cfinvoke component="#local.transferObject#"
method="set#arguments.propertyName#">
<cfinvokeargument name="#arguments.propertyName#"
value="#arguments.propertyValue#" />
</cfinvoke>
<cfelse>
<!--- value wasn't the correct datatype --->
<!--- set our substitute instance variable --->
<cfset variables.instance["#arguments.propertyName#String"] =
arguments.propertyValue />
</cfif>
</cfif>
</cffunction>

<cffunction name="getTransferObjectVariable" access="private"
output="false">
<cfargument name="propertyName" required="true" />
<cfset var local = structNew() />
<!--- init the substitute instance variable, if it doesn't exist ---
>
<cfparam name="variables.instance.#arguments.propertyName#String"
default="" />

<cfif variables.instance["#arguments.propertyName#String"] is
""><!--- substitute instance variable is blank --->
<!--- check if property is null --->
<cfinvoke component="#getTransferObject()#"
method="get#arguments.propertyName#IsNull"
returnvariable="local.isPropertyNull" />
<cfif local.isPropertyNull><!--- property is null --->
<cfreturn "" /><!--- return empty string --->
<cfelse><!--- property is not null --->
<!--- return real variable value --->
<cfinvoke component="#getTransferObject()#"
method="get#arguments.propertyName#"
returnVariable="local.propertyValue" />
<cfreturn local.propertyValue />
</cfif>
<cfelse><!--- substitute instance variable is not blank --->
<cfreturn variables.instance["#arguments.propertyName#String"] />
</cfif>
</cffunction>

Thanks,
Jamie

Jamie Jackson

unread,
Mar 28, 2007, 4:57:09 PM3/28/07
to transfer-dev
On Mar 28, 4:50 pm, "Jamie Jackson" <mySpa...@gmail.com> wrote:
> Well, I mentioned that I didn't have time to switch to dedicated
> FormBeans, and that I'd have to find a workaround.
>
> Note, I'm not necessarily advocating using Transfer (TM) Objects for
> user input, but I managed to stuff the bad data into my Transfer (TM)
> Objects using separate instance variables in the decorator.
>
> The following handles and redisplays empty strings (for nulls) as well
> as stuffs bad data into the object (which I wanted):

That wasn't very legible. Here it is again: http://www.pasteserver.net/740

Reply all
Reply to author
Forward
0 new messages