[Coldbox 4.3.0] CF2016 - How to pass an error message caught from a DAO object to a view?

I am catching a duplicate insert error in a carrierDAO object as follows:

// CREATE

public string function newCarrier(required Carrier) {

// I insert a new carrier record in the database taking as argument a Carrier bean populated with the data to insert.

var insertKey = “”;

try {

var q = new Query();

q.setDatasource("#dsn.name#");
q.setSQL(“INSERT INTO tab_carriers (car_carrier_cd, car_carrier_nm, car_transport_mode, car_awb_prefix)
VALUES (:carrierCD, :carrierName, :carrierMode, :carrierAWBprefix)”);
q.addParam(name: “carrierCD”, value: “#Ucase(Trim(arguments.Carrier.getCarrierCD()))#” , cfsqltype: “CF_SQL_VARCHAR” );
q.addParam(name: “carrierName”, value: “#Ucase(Trim(arguments.Carrier.getCarrierName()))#” , cfsqltype: “CF_SQL_VARCHAR” );
q.addParam(name: “carrierMode”, value: “#Ucase(Trim(arguments.Carrier.getCarrierMode()))#” , cfsqltype: “CF_SQL_CHAR” );
q.addParam(name: “carrierAWBprefix”, value: “#Ucase(Trim(arguments.Carrier.getCarrierAWBprefix()))#” , cfsqltype: “CF_SQL_VARCHAR” );
qInsert = q.execute();

// Should any error arise in the process

} catch (any e) {

// Display an error message to the user on the [carriers.newCarrier] view

writeOutput("Error: " & e.message );

// Stop all processing at this point until the user hits the CANCEL button on the [carriers.newCarrier] view

abort;

}

// Here, we return either the numeric generatedKey value, which is an auto-generated value from mySQL
// or we simply return the string record key value already available from the bean

insertKey = arguments.Carrier.getCarrierCD();

return insertKey;

}

When I simulate the error, I do get an error message displayed for the event: [carriers.create] as defined in the corresponding handler:

function newCarrier(event,rc,prc){

// I display a form to input the details of a new carrier with the option to CREATE that record or CANCEL the process

event.setView(“carriers/newCarrier”);

}

function create(event,rc,prc){

// I process the creation of a carrier record in conjunction with the newCarrier event

var carrier = populateModel( “carrier”);
carrierSVC.create(carrier);

// validate it

var vResults = validateModel( carrier );

// Check it

if( vResults.hasErrors() ){
flash.put( “errors”, vResults.getAllErrors() );
return newCarrier(event,rc,prc);

}

else{

flash.put( “notice”, “Carrier Created!” );
setNextEvent(“carriers”);

}

}

My question is: how to get this error message displayed within the [carriers/newCarrier] view instead of on a blank page splashed by the [carriers.create] event? I guess the error message should be relayed from the DAO to the handler which in turn should pass it to the view, but how to do that?

Assumptions: focus is on best practices for Coldbox exception handling, everything else works fine otherwise.

Three options come to mind:

  1. Create a separate call to check for existing duplicates prior to the insert
  2. Design your DAO to return a struct of information including some sort of success flag that you check
  3. Throw an exception from your DAO and catch it in the handler to respond to the somewhat-expected error scenario.
    Thanks!

~Brad

ColdBox/CommandBox Developer Advocate
Ortus Solutions, Corp

E-mail: brad@coldbox.org
ColdBox Platform: http://www.coldbox.org
Blog: http://www.codersrevolution.com

Hi Brad, thanks for the feedback. I successfully implemented the third option, here is the new DAO method:

// CREATE

public string function newCarrier(required Carrier) {

// I insert a new carrier record in the database taking as argument a Carrier bean populated with the data to insert.

var insertKey = “”;

try {

var q = new Query();

q.setDatasource("#dsn.name#");
q.setSQL(“INSERT INTO tab_carriers (car_carrier_cd, car_carrier_nm, car_transport_mode, car_awb_prefix)
VALUES (:carrierCD, :carrierName, :carrierMode, :carrierAWBprefix)”);
q.addParam(name: “carrierCD”, value: “#Ucase(Trim(arguments.Carrier.getCarrierCD()))#” , cfsqltype: “CF_SQL_VARCHAR” );
q.addParam(name: “carrierName”, value: “#Ucase(Trim(arguments.Carrier.getCarrierName()))#” , cfsqltype: “CF_SQL_VARCHAR” );
q.addParam(name: “carrierMode”, value: “#Ucase(Trim(arguments.Carrier.getCarrierMode()))#” , cfsqltype: “CF_SQL_CHAR” );
q.addParam(name: “carrierAWBprefix”, value: “#Ucase(Trim(arguments.Carrier.getCarrierAWBprefix()))#” , cfsqltype: “CF_SQL_VARCHAR” );
q.execute();

// Should any error arise in the process

} catch (database e) {

// Build the error message and capture it to a variable

var errorMsg = “#e.sqlState#: #e.message# #e.queryError#”;

// Throw the exception and pass the error message variable for the controller to handle

throw(type=“DBinsertError”, message="#errorMsg#");

}

// Here we return as a String the record key value from the bean

insertKey = arguments.Carrier.getCarrierCD();

return insertKey;

}

Here is the new handler:

function newCarrier(event,rc,prc){

// I display a form to input the details of a new carrier with the option to CREATE that record or CANCEL the process

event.setView(“carriers/newCarrier”);

}

function create(event,rc,prc){

// I process the creation of a carrier record in conjunction with the newCarrier event

var carrier = populateModel( “carrier”);

try {

carrierSVC.create(carrier);

} catch(DBinsertError e) {

// Catch the message (errorMsg) thrown as exception in the carrierDAO object

prc.errorMsg = “#e.message#”;
//writeOutput("#prc.errorMsg#");

// Channel the error message to the View for user display

return newCarrier(event,rc,prc);

}

// validate it

var vResults = validateModel( carrier );

// Check it

if( vResults.hasErrors() ){
flash.put( “errors”, vResults.getAllErrors() );
return newCarrier(event,rc,prc);

}

else{

flash.put( “notice”, “Carrier Created!” );
setNextEvent(“carriers”);

}

}

and here is what I have yet to achieve, which is the pass the value of prc.errorMsg to the the bottom of the View as follows:

newCarrier view

#html.startForm(action=“carriers.create”)#

#html.textfield(label=“Carrier code”, name=“carrierCD”, wrapper=“div”, required=“true” )#
#html.textfield(label=“Carrier name”, name=“carrierName”, wrapper=“div”, required=“true” )#
#html.textfield(label=“Carrier mode”, name=“carrierMode”, wrapper=“div”, required=“true” )#
#html.textfield(label=“AWB prefix”, name=“carrierAWBprefix”, wrapper=“div” )#

#html.submitButton(value=“CREATE”)#

#html.endForm()#

#html.startForm(action=“carriers.cancel”)#
#html.submitButton(value=“CANCEL”)#
#html.endForm()#


The error message displays properly to the console above the form input when I set writeOutput() in the controller. But I get an error “errorMsg in not in PRC” when I try to share it with the View and display it value at the bottom of the View. Any further suggestions?

Your create function does not make sense. You should validate first, then insert. Not the other way around.

It should be:

  1. Populate model
  2. Validate model
  3. If no error
  4. Then save into database
  5. Else return error message to user
    Another one that I noticed, the catch type is DBinsertError. If this is a custom type, then you need to define the type. If I were you, I just use “database” to catch any database error.

`

function create(event, rc, prc) {
// I process the creation of a carrier record in conjunction with the newCarrier event
var carrier = populateModel(“carrier”);
// validate it
var vResults = validateModel(carrier);
// Check it
if (vResults.hasErrors()) {
flash.put(“errors”, vResults.getAllErrors());
return newCarrier(event, rc, prc);
} else {
try {
carrierSVC.create(carrier);
} catch (database e) {
// Catch the message (errorMsg) thrown as exception in the carrierDAO object
prc.errorMsg = e.message;
//writeOutput("#prc.errorMsg#");
// Channel the error message to the View for user display
return newCarrier(event, rc, prc);
}
flash.put(“notice”, “Carrier Created!”);
setNextEvent(“carriers”);
}
}

`

See also:
https://coldbox.ortusbooks.com/content/full/models/coding_virtual_service_layer/contacts_handler.html

Many thanks! Yes, you are right. In fact I was not sure what to do with the ValidateModel() method that came with the handler when generated by the Coldbox create handler command …

ValidateModel is to validate property value of an object. If your case, in Carrier.cfc you could define constraints to make sure these properties are filled in before saving to database:

this.constraints = { carrierCD = { required = true }, carrierNM = { required = true }, carrierTransportMode = { required = true }, carrierAWBPrefix = { required = true } };

You could also set an error message for user in your constraints. For example:

this.constraints = { carrierCD = { required = true, requiredMessage = "Carrier CD is required." } };

And, this is how you display the error.

Thank you. Very useful feedback. Problem solved. Error messages involving DDL statements now bubble up from the DAO object to the View via the handler. The handler now looks as follows:

function create(event,rc,prc){

// I process the creation of a carrier record in conjunction with the newCarrier event

var carrier = populateModel( “carrier”);

// validate it

var vResults = validateModel( carrier );

// Check it

if( vResults.hasErrors() ){
flash.put( “errors”, vResults.getAllErrors() );

return newCarrier(event,rc,prc);

}

else {

try {

carrierSVC.create(carrier);

} catch(DBinsertError e) {

// Catch the message (errorMsg) thrown as exception in the carrierDAO object

prc.errorMsg = “#e.message#”;
//writeOutput("#prc.errorMsg#");
flash.put(“message”, “#prc.errorMsg#”);

// Channel the error message to the View for user display

return newCarrier(event,rc,prc);

}

flash.put( “message”, “Carrier Created!” );
setNextEvent(“carriers”);

}

}

… and the View now looks as follows with a flash.get() method at the bottom of the screen, to get the message and display it to the user:

newCarrier view

#html.startForm(action=“carriers.create”)#

#html.textfield(label=“Carrier code”, name=“carrierCD”, wrapper=“div”, required=“true” )#
#html.textfield(label=“Carrier name”, name=“carrierName”, wrapper=“div”, required=“true” )#
#html.textfield(label=“Carrier mode”, name=“carrierMode”, wrapper=“div”, required=“true” )#
#html.textfield(label=“AWB prefix”, name=“carrierAWBprefix”, wrapper=“div” )#

#html.submitButton(value=“CREATE”)#

#html.endForm()#

#html.startForm(action=“carriers.cancel”)#
#html.submitButton(value=“CANCEL”)#
#html.endForm()#


Use of the flash storage area was essential to pass the message variable from the controller to the view, otherwise it seems impossible to pass a (prc, or rc) variable from one event to another. Now, I better understand how ValidateModel() works and also added constraints to the bean accordingly.

Thanks again!