[framework-one] Save, Validate & Redirect

238 views
Skip to first unread message

Dan Vega

unread,
Apr 30, 2010, 9:57:38 AM4/30/10
to framework-one
So I am working on some examples for validating data. Lets say we have
a dealer add/edit form. When you click the submit button it actually
posts to the 'save' action. For argument sake lets just say all of the
values in populate() were required fields and the user filled all of
them out except the name field. When we validate the data we will find
out that there were errors. So we get the errors and its easy enough
to redirect them back to the edit screen and display the errors. My
question is how can we still display everything they have already
filled out? At this point we would basically be loading a half baked
entity (domain object) so I am really curious to hear how others are
doing it.

public void function save(){
var dealer = variables.dealerService.load(rc.id);

// populate the dealer entity

variables.fw.populate(dealer,"name,account,division,region,isdeleted,pricebook");

//validate the dealer
var result = variables.validator.validate(dealer);

if( result.hasErrors() ){
// if validation does have errors return to form and display errors
rc.errors = result.getErrors();
// ****** WHAT TO DO HERE ****


} else {
// if validation has no errors save and return to list
tx = ormGetSession().beginTransaction();
try {
variables.dealerService.save(dealer);
tx.commit();
} catch(any e){
tx.rollback();
rethrow;
}

//redirect to list
rc.message = dealer.getName() & " was created successfully.";
variables.fw.redirect("dealer.index","message");
}

}

--
You received this message because you are subscribed to the Google Groups "framework-one" group.
To post to this group, send email to framew...@googlegroups.com.
To unsubscribe from this group, send email to framework-on...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/framework-one?hl=en.

Dan Vega

unread,
Apr 30, 2010, 10:42:20 AM4/30/10
to framework-one
Ok so I figured out a way to handle this. Baiscally your persisting
the dealer back to the edit method and I needed to make a slight mod
to the edit method. What does everyone think of this approach?

public void function edit(){
if( !structKeyExists(rc,"dealer") ) {
rc.dealer = variables.dealerService.load(rc.id);
if( isNull(rc.user)){
rc.error = "Dealer not found";
}
}
}

public void function save(){
rc.dealer = variables.dealerService.load(rc.id);

// populate the dealer entity

variables.fw.populate(rc.dealer,"name,account,division,region,isdeleted,pricebook");

//validate the dealer
var result = variables.validator.validate(rc.dealer);

if( result.hasErrors() ){
// if validation does have errors return to form and display errors
rc.errors = result.getErrors();
variables.fw.redirect("dealer.edit","dealer,id,errors");
} else {
// if validation has no errors save and return to list
tx = ormGetSession().beginTransaction();
try {
variables.dealerService.save(rc.dealer);
tx.commit();
} catch(any e){
tx.rollback();
rethrow;
}
//redirect to list
rc.message = rc.dealer.getName() & " was created successfully.";
variables.fw.redirect("dealer.index");
}

}



On Apr 30, 9:57 am, Dan Vega <danv...@gmail.com> wrote:
> So I am working on some examples for validating data. Lets say we have
> a dealer add/edit form. When you click the submit button it actually
> posts to the 'save' action. For argument sake lets just say all of the
> values in populate() were required fields and the user filled all of
> them out except the name field. When we validate the data we will find
> out that there were errors. So we get the errors and its easy enough
> to redirect them back to the edit screen and display the errors. My
> question is how can we still display everything they have already
> filled out? At this point we would basically be loading a half baked
> entity (domain object) so I am really curious to hear how others are
> doing it.
>
>         public void function save(){
>                 var dealer = variables.dealerService.load(rc.id);
>
>                 // populate the dealer entity
>
> variables.fw.populate(dealer,"name,account,division,region,isdeleted,priceb ook");

Dave Anderson

unread,
Apr 30, 2010, 4:04:14 PM4/30/10
to framework-one
That's pretty much what I do. Don't you want the rest of the supplied
data in the edit view with the errors, or is the field list edited for
brevity? Is there anything keeping you from doing:
redirect("dealer.edit","#structKeyList(form)#,errors") ?

As a result of your example, I found your Hyrule project. That looks
pretty sweet. Definitely seems like it would knock over the major
hurdle preventing me from implementing the new Hibernate functionality
in CF9.

Dave

Dan Vega

unread,
Apr 30, 2010, 4:09:44 PM4/30/10
to framework-one
Dave, there is no real need to do this - >
redirect("dealer.edit","#structKeyList(form)#,errors"

The dealer entity is already loaded so I just pass it back
variables.fw.redirect("dealer.edit","dealer,id,errors");

and in my edit i have no reason load it again if its already there

> > public void function edit(){
> >         if( !structKeyExists(rc,"dealer") ) {
> >                 rc.dealer = variables.dealerService.load(rc.id);
> >                 if( isNull(rc.user)){
> >                         rc.error = "Dealer not found";
> >                 }
> >         }
>
> > }

Thanks for the comments on Hyrule. It is coming along quite nicely! I
am going to put some example of using it with fw/1 out there next
week. I hope you will give it a look and send me any and _ALL_
feedback you have on it!

Thanks!


On Apr 30, 4:04 pm, Dave Anderson <d...@metaldog.net> wrote:
> That's pretty much what I do.  Don't you want the rest of the supplied
> data in the edit view with the errors, or is the field list edited for
> brevity?  Is there anything keeping you from doing:
> redirect("dealer.edit","#structKeyList(form)#,errors") ?
>
> As a result of your example, I found your Hyrule project.  That looks
> pretty sweet.  Definitely seems like it would knock over the major
> hurdle preventing me from implementing the new Hibernate functionality
> in CF9.
>
> Dave
>
> On Apr 30, 9:42 am, Dan Vega <danv...@gmail.com> wrote:
>
>
>
>
>
> > Ok so I figured out a way to handle this. Baiscally your persisting
> > the dealer back to the edit method and I needed to make a slight mod
> > to the edit method. What does everyone think of this approach?
>
> > public void function edit(){
> >         if( !structKeyExists(rc,"dealer") ) {
> >                 rc.dealer = variables.dealerService.load(rc.id);
> >                 if( isNull(rc.user)){
> >                         rc.error = "Dealer not found";
> >                 }
> >         }
>
> > }
>
> > public void function save(){
> >         rc.dealer = variables.dealerService.load(rc.id);
>
> >         // populate the dealer entity
>
> > variables.fw.populate(rc.dealer,"name,account,division,region,isdeleted,pri cebook");

Shane Pitts

unread,
Apr 30, 2010, 10:43:57 PM4/30/10
to framew...@googlegroups.com
(/services/dealerService.cfc )

public function createDealer( id ){
/* I create and return a domain object */
return factory.createDealerRecord(id=arguments.id);
}

public function saveDealer( rc ){
var dealer = 0;
var results = 0;
dealer = createDealer(id=arguments.id);
/* dealer domain object can populate from
a supplied structure */
dealer.populate( arguments );
/* dealer domain object can check itself
for errors */
dealer.validate();
if (! dealer.hasErrors() ){
/* dealer can save itself too!*/
dealer.save();
return dealer;
}

public function editDealer( rc ){
var errors = 0;
var dealer = 0;
if ( structKeyExists( arguments, 'dealer' ) && isObject(arguments.dealer) ){
     /* We most likely got here as the result of a form submit and redirect.
         pass the object along again */
dealer = arguments.dealer;
} else if ( structKeyExists( arguments, 'dealer_id')
     /* We're here from a selection somewhere else, and are editing a
         dealer record for the first time. instantiate the object to pass on. */
        dealer = createDealer( id=arguments.dealer_id );
    } else {
     /* We're most likely here to create a new dealer record.  Instantiate an
         empty object.  The save() function in the object knows how to insert
            a new record when the id=0 */
dealer = createDealer( id=0 );
}
return dealer;
}




(/Controllers/dealer.cfc)

public function endSaveDealer( rc ){
var message = 'Your data was saved successfully';
var persistList = 'message';
var dealer = 0;
if ( structKeyExists( arguments.rc, 'data' )){
dealer = rc.data;
persistList = 'all';
if ( dealer.isDirty() && dealer.hasErrors() ){
message = 'not saved, there were errors in your data...';
}
/* place the correct message in the rc scope */
arguments.rc.message = message;
arguments.rc.dealer = dealer;
} else {
arguments.rc.message = 'Something happened and your data was lost.';
}
/* return to the edit item with one of three options.
1. the data was saved, and a message will be passed on 
stating so. the saved, clean dealer object will also
exists in the rc scope.
2. the data was not saved, and it still contains the dirty
data that the user needs to edit.  Pass it back in to
the rc scope, along with a message.
3. Somehow we got here without the rc.data variable.. while
that shouldn't have happened, there isn't much we can do
about it.. so send them back to the form with a message.
a new dealer object will be created in the editDealer
service function
*/
structDelete(arguments.rc, data);
variables.fw.redirect( 'dealer.editDealer', persistList );
}



( dealer/editDealer.cfm view )

<cfscript>
if ( isDefined('rc.data') && isObject('rc.data') ){
local.dealer = rc.data;
} else {
/* means we didn't get the service layer dealer object we should have.
     This is how I like to handle all the outputs below without having
        to validate them all in place.  If I don't have an object (even an empty
        one) then throw an error. */
raiseException( type="FW1.serviceExecutionComplete", message="dealer domain object not found.",
detail="In order to populate an edit form, I expect a dealer domain object to be sent in from the service layer as rc.data" );
}
</cfscript> 
<cfif isDefined('rc.message')>#rc.message#</cfif>
<form action='index.cfm?action=dealer.saveDealer'>
<input type='hidden' name='id' id='id' value='#local.dealer.getDealer_id()#' />
<input type='text' name='dealer_id' id='dealer_id' value='#local.dealer.getDealer_id()#' />
<input type='text' name='address' id='address' value='#local.dealer.getAddress()#' />
<input type='text' name='city' id='city' value='#local.dealer.getCity()#' />
</form>


So, here's my usual logic... because FW1 will automatically call a matching service item, I don't usually have to create any controller functions, other than the endItem functions that usually occur after some type of form submit.  Since I dont' create views for those functions, I want to return them to the appropriate place.  That's usually right back to the page they came from, so they can either continue editing stuff, or fix the errors.

When someone first comes to the dealer edit form, they are either here to edit, or create a record. If they clicked a link that passed an id, that record is instantiated and passed in to the view to populate the field values.  If the id was 0 or not there, it creates the record for the first time and sends them in to the form to add the data.

When they post to the saveitem, FW1 appends the RC scope to the argumentsCollection when it invokes the service.  There is no need for a controller, we already know we're going to try to save some data.. so just expect that the matching service function will handle the saving of the record.  Once the services are complete.  FW1 calls the endXxX() function.  So, I create that function to handle what to do "next" depending on what happened during the save.

If the object that was passed has errors, we redirect to the editForm with that "same" object and it doesn't need to get instantiated again in the service layer..  it just renames it and passes it back on again..  The message of the error was appended to the rc scope in the endXxX() controller and it will exists in the view.

If the object did not have errors, it still renames it and sends it back along in the RC scope because I typically go back to the form after a data save, so they can decide what they want to do next, or make more changes.

If the object did "not" exist in the endXxX() function (for some odd reason), we redirect back through to the edit form but we don't pass in our object, and it knows that it needs to create one.. and the RC scope message is there for the user.

I typically try and leverage my domain objects to know how to save themselves, validate themselves, etc.. that way I don't have to create totally custom code for them..

This is how I've been using FW1.. and it's worked really really well..  And if I'm doing everything right, you can thank Sean, I've spent many sleepless nights picking his brain.. and never once has he told me to go to hell :)  If everything above is completely wrong and makes no sense at all.. you can blame Sean, it would naturally be his fault that I didn't learn anything on all those sleepless nights!

(forgive any typos, or syntax errors..  I'm typing, not coding/compiling )

Shane

Dave Anderson

unread,
May 2, 2010, 10:50:46 AM5/2/10
to framework-one
Oh-ho-ho! I feel really dumb now. I didn't realize 'dealer' was an
object - or that you could pass objects through a redirect. Makes
sense though, since the variables preserved are stored in the session
scope, right? That was eye-opening -- thanks! (But why is the id
being passed along with the dealer object? Isn't the id a property of
the dealer?)

I'll definitely be checking out hyrule. I'd really like to use
Hibernate, but can't imagine having to write custom validation for
every ORM object.

Dave

On Apr 30, 3:09 pm, Dan Vega <danv...@gmail.com> wrote:
> Dave, there is no real need to do this - >
> redirect("dealer.edit","#structKeyList(form)#,errors"
>
> The dealer entity is already loaded so I just pass it back
> variables.fw.redirect("dealer.edit","dealer,id,errors");
>
> and in my edit i have no reason load it again if its already there
>
> > > public void function edit(){
> > >         if( !structKeyExists(rc,"dealer") ) {
> > >                 rc.dealer = variables.dealerService.load(rc.id);
> > >                 if( isNull(rc.user)){
> > >                         rc.error = "Dealer not found";
> > >                 }
> > >         }
>
> > > }
>
> Thanks for the comments on Hyrule. It is coming along quite nicely! I
> am going to put some example of using it with fw/1 out there next
> week. I hope you will give it a look and send me any and _ALL_
> feedback you have on it!
>
> Thanks!

Sean Corfield

unread,
May 2, 2010, 1:17:00 PM5/2/10
to framew...@googlegroups.com
On Sun, May 2, 2010 at 7:50 AM, Dave Anderson <da...@metaldog.net> wrote:
> Oh-ho-ho!  I feel really dumb now.  I didn't realize 'dealer' was an
> object - or that you could pass objects through a redirect.  Makes
> sense though, since the variables preserved are stored in the session
> scope, right?

Yup. The entire request context or any part thereof can be preserved,
objects, arrays, structs and simple values alike.
--
Sean A Corfield -- (904) 302-SEAN
Railo Technologies, Inc. -- http://getrailo.com/
An Architect's View -- http://corfield.org/

"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood

Bob Silverberg

unread,
May 2, 2010, 2:33:43 PM5/2/10
to framew...@googlegroups.com
One thing to be aware of if you're using the session scope to pass
data between requests is that any ORM objects will end up being
detatched.

Sent from my iPhone

Dan Vega

unread,
May 2, 2010, 3:24:25 PM5/2/10
to framew...@googlegroups.com
That is a great point Bob.. in this case it was just some simple properties but I could def see running into a problem here. In that case I am not sure what the correct answer is, I will have to do some more playing around. 
On Sun, May 2, 2010 at 2:33 PM, Bob Silverberg <bob.sil...@gmail.com> wrote:
One thing to be aware of if you're using the session scope to pass data between requests is that any ORM objects will end up being detatched.

Sent from my iPhone


On 2010-05-02, at 1:17 PM, Sean Corfield <seanco...@gmail.com> wrote:

On Sun, May 2, 2010 at 7:50 AM, Dave Anderson <da...@metaldog.net> wrote:
Oh-ho-ho!  I feel really dumb now.  I didn't realize 'dealer' was an
object - or that you could pass objects through a redirect.  Makes
sense though, since the variables preserved are stored in the session
scope, right?

Yup. The entire request context or any part thereof can be preserved,
objects, arrays, structs and simple values alike.
--
Sean A Corfield -- (904) 302-SEAN
Railo Technologies, Inc. -- http://getrailo.com/
An Architect's View -- http://corfield.org/

"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood

--
You received this message because you are subscribed to the Google Groups "framework-one" group.
To post to this group, send email to framew...@googlegroups.com.
To unsubscribe from this group, send email to framework-on...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/framework-one?hl=en.


--
You received this message because you are subscribed to the Google Groups "framework-one" group.
To post to this group, send email to framew...@googlegroups.com.
To unsubscribe from this group, send email to framework-on...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/framework-one?hl=en.

Bob Silverberg

unread,
May 2, 2010, 3:55:30 PM5/2/10
to framew...@googlegroups.com
I haven't actually used FW/1 since I took a first look at it several months ago, so maybe I'm missing something about the FW/1 paradigm, but I normally wouldn't redirect after a validation failure - I'd simply redisplay the edit view in the same request.  That way I don't need to worry about my object being detached.

Cheers,
Bob 


On Sun, May 2, 2010 at 3:24 PM, Dan Vega <dan...@gmail.com> wrote:
That is a great point Bob.. in this case it was just some simple properties but I could def see running into a problem here. In that case I am not sure what the correct answer is, I will have to do some more playing around. 

 
--
Bob Silverberg
www.silverwareconsulting.com

Hands-on ColdFusion ORM Training
www.ColdFusionOrmTraining.com


Dan Vega

unread,
May 2, 2010, 4:00:32 PM5/2/10
to framew...@googlegroups.com
Sean, instead of redirecting can I just show the edit view again? 

Shane Pitts

unread,
May 2, 2010, 5:42:17 PM5/2/10
to framew...@googlegroups.com
Theoretically, you could just create the view file for the submit action, and have it call the view for the edit form, but that feels dirty to me.. I can only assume that all form field values will be simple values, so when you get to the endItem in the controller, just redirect back to the form view item passing them all back along.. then recreate the object at that point in the service layer, populating it with what came back  in the arguments.

shane

Sean Corfield

unread,
May 2, 2010, 6:37:12 PM5/2/10
to framew...@googlegroups.com
On Sun, May 2, 2010 at 12:55 PM, Bob Silverberg
<bob.sil...@gmail.com> wrote:
> I haven't actually used FW/1 since I took a first look at it several months
> ago, so maybe I'm missing something about the FW/1 paradigm, but I normally
> wouldn't redirect after a validation failure - I'd simply redisplay the edit
> view in the same request.

Right now you have to redirect (unless you submit back to the
object.edit action).

I'm *considering* adding a way to specify a different view for 1.2
(although that really goes against the convention paradigm IMO).
--
Sean A Corfield -- (904) 302-SEAN
Railo Technologies, Inc. -- http://getrailo.com/
An Architect's View -- http://corfield.org/

"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood

Qasim Rasheed

unread,
May 2, 2010, 8:19:40 PM5/2/10
to framew...@googlegroups.com
I would love to see the ability to call a different view. 

Thanks

Dan Vega

unread,
May 2, 2010, 8:34:37 PM5/2/10
to framew...@googlegroups.com
I agree.. When I click save user i expect to hit the save method, posting back to the edit method just to preserve data feels wrong to me. 

AJ Mercer

unread,
May 2, 2010, 10:17:36 PM5/2/10
to framew...@googlegroups.com
is it possible mid way through a request life cycle to do something sneaky like switching REQUEST.item?

So REQUEST.item starts of as 'save'
but then changed - REQUEST.item = 'edit'
for the view part of the life cycle


I have not put too much thought into this - so just disregard if way off track.
--

AJ Mercer
http://webonix.net
http://twitter.com/webonix

Sean Corfield

unread,
May 3, 2010, 1:42:20 PM5/3/10
to framew...@googlegroups.com
With the recent changes to support skinning, this would actually work
but... well... it's a bit of a hack :)

It does indicate that adding variables.fw.setView( fqaction ); would
be relatively easy tho' so I may do it for 1.1 now...

http://github.com/seancorfield/fw1/issues/issue/1

I'm still migrating content and issues from RIAforge to github so bear
with me over the next two weeks (I actually have Getting Started /
Developing Applications migrated - I'm working on Reference Manual
then Using Subsystems and I need to migrate the open issues from
RIAforge to github). Another evening should see it all done so it
should happen this week.

Sean

On Sun, May 2, 2010 at 7:17 PM, AJ Mercer <ajme...@gmail.com> wrote:
> is it possible mid way through a request life cycle to do something sneaky
> like switching REQUEST.item?
>
> So REQUEST.item starts of as 'save'
> but then changed - REQUEST.item = 'edit'
> for the view part of the life cycle

Shane Pitts

unread,
May 3, 2010, 3:42:30 PM5/3/10
to framew...@googlegroups.com
Hey Bob,  I'd like to have an expanded conversation on this if we can..  I'm just curious that when you say "will end up being detached".. do you mean, that they will the first time you try and do it? or just.. eventually.

When I first read this the other day, I thought that maybe I just didn't understand what you mean, or that what I was doing wasn't what you were describing.. but after looking through what I'm doing in my code, and how FW1 handles saving flashContext etc, I am actually doing just this, and it works just fine..

I create Reactor objects in my service layer, populate them, validate them, do whatever I need to with them, then in my endItem controller, I receive in the RC scope argument that FW1 passes in, which has my object as RC.data.  I check the custom rc.data.hasErrors() function that I created in the reactor object and decide what to do next.

If there was a problem, I do:

rc.returnObject = rc.data;
variables.fw.redirect('section.edititem', 'returnObject');

the redirect function does indeed roll up simpleTypes and append them to the urlstring, but first it saves everything you told it to in a session struct, passing the key to that in with the cflocation.

I guess my question/concern is..  "should" this not work for some reason with ORM type objects, or only with "certain" ORM implementations..

I can tell you first hand, it works beautifully with Reactor.. and after the redirect back in to my edit service layer function.. I check to see if arguments.returnObject exists, and if it does, I can call returnObject.getName() etc just fine.. it doesn't lose anything.

Shane



On Sun, May 2, 2010 at 12:33 PM, Bob Silverberg <bob.sil...@gmail.com> wrote:
One thing to be aware of if you're using the session scope to pass data between requests is that any ORM objects will end up being detatched.



Dan Vega

unread,
May 3, 2010, 3:49:55 PM5/3/10
to framew...@googlegroups.com
@Shane - detached objects have nothing to do with fw/1 

I really can't speak to what reactor does but Mark has a pretty good post on how this works in hibernate

Bob Silverberg

unread,
May 3, 2010, 3:51:23 PM5/3/10
to framew...@googlegroups.com
It will only be a problem with ColdFusion's built-in ORM objects, which are actually Hibernate-backed objects.  There is no concept of a detached object with Reactor or Transfer, although you can get into trouble with Transfer's cache when you place Transfer objects in the session scope.  I don't imagine that would be an issue with this scenario, however.

So if you're using Reactor I don't think you have anything to worry about.  When I said "ORM objects" I actually meant "ColdFusion ORM" objects.

Cheers,
Bob

On Mon, May 3, 2010 at 3:42 PM, Shane Pitts <spit...@gmail.com> wrote:

--
Bob Silverberg
www.silverwareconsulting.com

Hands-on ColdFusion ORM Training
www.ColdFusionOrmTraining.com


Sean Corfield

unread,
May 3, 2010, 4:17:34 PM5/3/10
to framew...@googlegroups.com
On Mon, May 3, 2010 at 12:51 PM, Bob Silverberg
<bob.sil...@gmail.com> wrote:
> It will only be a problem with ColdFusion's built-in ORM objects, which are
> actually Hibernate-backed objects.  There is no concept of a detached object
> with Reactor or Transfer, although you can get into trouble with Transfer's
> cache when you place Transfer objects in the session scope.  I don't imagine
> that would be an issue with this scenario, however.

It's technically possible to run into the problem with Transfer and
FW/1's "flash scope" but it is exceedingly unlikely :)

Certainly holding Transfer objects in session scope that could be
modified in the central cache can cause problems. I ran into some
nasty problems with that both at Adobe (after we switched from Reactor
to Transfer on one of my projects) and again at Broadchoice (made more
complicated by running Transfer on a cluster where we had multiple
caches to worry about!).

> So if you're using Reactor I don't think you have anything to worry about.

Reactor does no caching so you are pretty safe.
--
Sean A Corfield -- (904) 302-SEAN
Railo Technologies, Inc. -- http://getrailo.com/
An Architect's View -- http://corfield.org/

"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood

Shane Pitts

unread,
May 3, 2010, 4:39:43 PM5/3/10
to framew...@googlegroups.com
sweet, thanks for the feedback..  you had me nervous that I was making a huge error with future doom looming :)

shane
Reply all
Reply to author
Forward
0 new messages