In a RESTful ColdBox app, how can I override the `...
# box-products
r
In a RESTful ColdBox app, how can I override the
event.getResponse().setData()
to allow me to drop the pagination element and possibly other parts?
It seems to be automagically pulling in the
setDataWithPagination()
method?
b
@richard.herbert Create your own response CFC that extends the core one and add it manually to
prc.response
in a
preProcess
interceptor
Then you can make it behave however you like
r
Okay.... Where would someone store a custom response CFC? I've never overridden a core ColdBox feature. The
preProcess
interceptor of my handler (I assume that's what you meant)?
b
You can store it wherever you like. I usually put CFCs in the
models
folder šŸ™‚
The
preProcess
interceptor of my handler (I assume that's what you meant)?
No, that doesn't really make sense. ColdBox interceptors are unrelated to handlers
r
I usually put CFCs in the
models
folder
In a HMVC app would that go in a
models
folder in the app root or closer to the code, say
modules_app/api/modules_app/v1/models
location?
No, that doesn't really make sense
Okay, be gentle with me, this is a new area for me. So I need to look into interceptors?
b
In a HMVC app would that go in a
models
folder in the app root or closer to the code, say
modules_app/api/modules_app/v1/models
location?
Again, up to you. If you have your API broken out into a module then it would make good sense to put that CFC inside the API module. If you want to re-use the same response for all versions of your API, you could put it under
modules_app/models
but if you think every version of your API may have a different response object, then you could stick it in the v1 module.
The base rest handler doesn't care-- it's just going to look for
prc.response
and so long as you put your custom version there on each request, ColdBox won't care.
So I need to look into interceptors?
Apparently šŸ™‚ The shortest path from A to B is probably just to stick a
preProcess
method in your module's
ModuleConfig.cfc
since module configs are automatically registered as interceptors. Just keep in mind, interceptors are global and will run on every request unless you implement an
eventPattern
annotation to restrict it only to API requests https://coldbox.ortusbooks.com/the-basics/interceptors/restricting-execution
r
it would make good sense to put that CFC inside the API module
Okay, yes, I can see the logic in that.
just to stick a
preProcess
method in your module's
ModuleConfig.cfc
Okay, I'll look into that too. Thank you for the steer, I'll do some more reading now.
b
Luis also has an example here in the docs that uses a proper interceptor https://coldbox.ortusbooks.com/digging-deeper/rest-handler#extending-the-response-object
I would still recommend putting the interceptor inside the module and using the event pattern though, even if you follow his instructions.
Which means you would register it in your api modules
ModuleConfig.cfc
not the
coldbox.cfc
file as his docs show
r
Okay, thanks again, I'll take a look...
Okay, so I took a look at this over the weekend and seem to have a working solution and was wondering if you won't mind marking my homework? I added this to my
/modules_app/api/ModuleConfig.cfc
Copy code
interceptors = [ {
	class: 'api.interceptors.RestResponseInterceptor',
		name: 'RestResponseInterceptor',
		properties: {}
} ];
and this to to the new interceptor
/modules_app/api/interceptors/RestResponseInterceptor.cfc
Copy code
component {
	void function configure() {}

	function preProcess( event, interceptData, buffer, rc, prc )
		eventPattern='^v1:scim\.'
	{
		prc.response = wirebox.getInstance( 'modules_app.api.models.RestResponseObject' );
	}
}
and then I when on to writing my new response object. I was hoping to be able to add new methods like
setDataSimple()
and
setDataNoPagination()
that I could use with
event.getResponse()
in my handler but that didn't seem to be the way to go. It seemed that I needed to override the
setDataPacket()
method with the response I want, so I added this to
/modules_app/api/models/RestResponseObject.cfc
Copy code
component extends=coldbox.system.web.context.Response {
	/**
	 * Returns a custom response formatted data packet using the information in the response
	 *
	 * @reset Reset the 'data' element of the original data packet
	 */
	struct function getDataPacket( boolean reset = false ) {
		if( isSimpleValue( getData() ) ) {
			var packet = {
				'data': getData()
			};
		} else {
			var packet = getData();
		}

		// Are we reseting the data packet
		if ( arguments.reset ) {
			packet.data = {};
		}

		return packet;
	}
}
which works for the one case I currently have. My question now is, do I need replicate this approach for every variation of a response object or is there a better, more flexible way to meet my needs?
b
@richard.herbert Looks good to me
Not sure what you mean by "every variation of a response object" though.
Generally, when we build a REST API, all resources use the same format of data coming back. (Forgebox for example). The same predictable JSON body is in every resource, with the difference being the actual data
If you have different needs in different parts of your API, you can use a different response object, or build enough dynamic-ness into the existing one to cover all your needs.
r
Thanks for the confirmation. Most appreciated. I have to format my response in a particular way to satisfy the system making the request. Hence this very simple response packet. In another situation I've had criticism for returning a "pagination" node for a simple, single record response to a
GET
request. I assume I'd have to build my dynamic-ness into my
getDataPacket()
method? I was hoping to have something like
setDataNoPagination()
so I could influence the packet when populating it with my data.
b
I mean, it's just code... you can do whatever you want šŸ™‚
You put whatever code in that method to return whatever response you need
And any custom methods you add can be called in your handlers along with all the other chainable methods on the response object allowing you to fully customize how you build responses
r
Humm, so if I wanted to have this in my hander...
Copy code
event.getResponse()
	.setDataNoPagination( response )
	.setStatus( 201 );
I would have to add a
setDataNoPagination()
method in my custom response object? How would I get
getDataPacket()
to return the modified packet? I'm not sure I understand how this is all wired up to allow a dynamic response object?
b
How would I get
getDataPacket()
to return the modified packet?
You would put that code in your overridden
getDataPacket()
method, no?
r
How could I let it know that when I populated my data with
setDataNoPagination()
I want the response packet to be in one format and when I populated my data with
setDataSimple()
I want the response packet to be in a different format?
b
The base rest handler simply calls the getDataPacket() method here https://github.com/ColdBox/coldbox-platform/blob/development/system/RestHandler.cfc#L124 you get to return whatever the heck you want based on whatever flags or methods you design.
You write an if statement-- honestly I'm not understanding the question
If you want to track a specific flag, then add a property of your own design to the CFC along with accessors to manage it.
Then call those methods as you see fit
And influence the output of
getDataPacket
as you desire using CFML!
There's really not much special about the response object. It's just a CFC that captures all the info needed to power the renderData() call and needs to have a bare minimum of methods that the rest handler expects to exist
Luis could have used an interface here, but I suspect he just wanted to keep it simple
r
honestly I'm not understanding the question
And there's me thinking English was my first language šŸ™‚ I'll leave you be as I can see I'm frustrating you with my lack of insight into how ColdBox wires these things up šŸ™‚
b
No, I'm not frustrated at all šŸ™‚ Just trying to figure out what you need to know
I think maybe if you read through the coldbox rest handler code, it will make more sense
Specifically, the
aroundHandler()
method
I don't think it's as complicated as you may think
Really, at the end of the day this is what happens • before executing the rest handler, the
prc.response
CFC instance is created (unless you'e already put one there) • Your handler code calls whatever methods it wants to in order to "build up" the information that needs to be returned to the client (status code, content type, the data to return in the HTTP response body) • The second half of the aroundhandler method takes whatever info is in that reponse object to pass into the
event.renderData()
call for you, and whatever the
response.getDataPacket
method returns is what gets passed in as the
data
for render data
That's basically it. The response object is just a helper to put the info that ultimately gets passed into event.renderData() at the end of the request
r
Thank you again. I'll take myself off into a darkened room and consider your helpful words šŸ™‚
šŸ˜‰ 1