This would be an awesome addition. Arrays of structs have better collection methods with each(), map(), reduce(), etc. I’ve been told arrays of structs are faster. They definitely interact with Javascript better on the frontend.

In the meantime, let’s solve this using Aspect Oriented Programming (AOP).

What is Aspect Oriented Programming (AOP)?

As with most programming topics, there is a bit of jargon that comes with it. Don’t let this jargon scare you away. A list of definitions is available on the WireBox site, but we’re going to try and define it very simply here.

What is an aspect?

One of the dictionary definitions of “aspect” is a “part; feature; phase”. In AOP, an aspect is similar — it is a feature of your application. What makes an aspect different from any other object is that an aspect can spans multiple objects. It is more concerned with its job or feature than what it is. So while your normal beans model real world “things” like BankAccount, User, or even Transaction, an aspect will model a feature or job with names like MethodLogger, DBTransaction, or (in our case) QueryToArrayOfStructsConverter.

When would I use this?

If you find yourself writing the same code or method over and over (especially in different components), an aspect might be the way to go.
One other place an aspect shines is adding a feature to existing code without touching the existing code.

Examples can include:

Logging every method that is called

Wrapping all query methods in a transaction

Implementing a security system around certain handler methods

Transforming a result in some way <– this is our example

Note: Whenever you are injecting code in to your components, you risk running in to tough to debug problems. Use your best judgement.

Our Setup

I’m going to show this using ColdBox and WireBox. (I’m sure you can do the same thing with FW/1 and AOP/1).
You can recreate the same skeleton by running coldbox create app query-converter-aspect in CommandBox.

Next, we’ll hijack the index method of the Main handler.

We’re injecting the DAO directly just for this example. When we hit this route we will dump out the data that we get back from the database.

Here’s a look at the DAO.

Very simple. Just querying a test database I set up for this example. Note that we are returning a query.

That’s all of the setup. Let’s move on to the AOP portion of this example.

Building the Aspect

Aspects need to implement coldbox.system.aop.MethodInterceptor (or wirebox.system.aop.MethodInterceptor for standalone). There is only one required method, invokeMethod.

Gotcha!

When implementing the interface, don’t forget to include all of the method definition, including the output=false metadata.

Here is QueryToArrayOfStructsConverter:

Let’s deconstruct this example:

Line 7: This is our implementation of the required method from coldbox.system.aop.MethodInterceptor.

Line 8: By calling invocation.proceed(), we call the matched method. It will return any value it would normally return. In our case, it should return our query object we defined in TestDAO.cfc (once we match it in our WireBox.cfc later).

Line 9: We transform the query in to an array of structs and return it.

Our aspect is ready to go. Now we need to tell WireBox when to inject it.

Registering the Aspect

Gotcha!

Don’t forget to activate the AOP listener! If you can’t figure out why your aspect isn’t running, even after setting your matcher to .any(), you probably forget to add the coldbox.system.aop.Mixer to your listeners array in WireBox.cfc.

You can register aspects in WireBox.cfc, the same place you register your other bindings. Here are the bindings I added:

Now, this is overkill. I probably should pick just one bindAspect, but I wanted to show options. Let’s go over this in detail:

Line 18: First, we bind our aspect to a named key.

By Filename

Line 22: This binding matches components that end in “DAO” for every method and runs our aspect. This is the binding that will match our original TestDAO.cfc file.

By Method Annotation

Line 29: This binding matches all components for methods that have the annotation convertToArrayOfStructs and runs our aspect.

The annotatedWith will match both inline annotations and doc block annotations. Here are those examples:

By Component Annotation

Line 36: This binding matches all components that have the annotation convertToArrayOfStructs for all methods and runs our aspect.

The annotatedWith will match both inline annotations and doc block annotations. Here are those examples:

Gotcha!

You may think you can bind your aspects on returns('query'), but then you have to return a query. Instead, use a different AOP matcher and don’t type hint the methods you want to transform in this way.

Note: this may be a bug. I haven’t really dived in to this use case yet.

Wrap Up

Running this code before, we saw this query:

Running the code with our aspect in place, we see this array:

Now, thanks to AOP, we have an easy way to bring back our queries as arrays of structs every time.

To learn more you can refer to the WireBox docs here or the AOP/1 docs here.

Bonus! — ColdBox Module

This kind of use case bundles real well into a module.

There’s a little magic in this one to activate the AOP listener if it hasn’t been activated and then add the aspects in automatically. Since it is automatic, I’ve chosen to go with component and method annotation bindings only.