I have to admit, this title seems a little weird. How can I change some behaviour in a module without changing the code? And why do I want to change this behaviour?
Let me start with the why. I am using a lot of box modules, but sometimes there are some pieces missing, or am I not happy with some default behaviour. Many modules are very adaptable, for example using configuration settings or some interceptors. But sometimes this is not enough.
In a Free and Open Source Software world we just clone a repo, modify some code and send some pull request the the authors. But what if they don’t want your changes? I could fork the project, and create my own module, but from this moment on I am the maintainer of my own module. And sometimes other modules are depending on the module I want to fork, which is often not what I want. But there are other ways to change a module, and they work best for smaller changes. Let me explain.

Recently this question came up on some slack channel. Someone wanted to change some cbwagger behaviour, and I remembered I did this some time ago. But the method is applicable to any coldbox module. Let’s look at the necessary steps:

  • Read the module code of YourFavouriteModule, and find out what method(s) in which model you want to change, for example SomeModel.someMethod()
  • create your own module YourModule and create a model map. In this model map you create YourModel which extends from SomeModel. Override someMethod()with your own code.
  • In the ModuleConfig of YourModule you add YourFavouriteModel as a dependency. You can do this by adding this.dependencies = [ "YourFavouriteModel" ]; to your module config.
  • The final step is: replacing SomeModel in YourFavouriteModule with YourModel from YourModule. You can do so by adding the following code to the setup method in your module config.
binder
  .map( 
    alias = "someModel@yourfavouritemodule", 
    force = true 
  )
  .to( "yourmodule.models.yourmodel" )
  .asSingleton();

In some case you need to have a better look at the mappings in your original module, but this code effectively replaces the model with your own model. That’s all…

But this might be a bit abstract so let me give some real world examples. We wanted to modify cbswagger so the listing of routes was security aware. So we found the routesParser model and the filterDesignatedRoutes method which is the base method necessary for displaying all these routes with documentation. We liked the original method, we only wanted to exclude some of the routes. The actual code is not that important, it’s all about the concept: replace some methods with your own, and keep all the good stuff.

public any function filterDesignatedRoutes() {
  return super
	.filterDesignatedRoutes()
	.reduce( ( acc, itemkey, item ) => {
	// first check if any  of the actions is allowed			 
      if ( isStruct( item.action ) ) {
		var action = 
          item.action.filter( ( key, value ) => {
            //return true or false for some permission
			return variables.cbSecurity
			  .getAuthService()
			  .getUser()
			  .hasPermission("#item.handler#.#value#" );
		  } )
		  if ( !structIsEmpty( action ) ) {
			item.action    = action;
			acc[ itemkey ] = item;
		  }
		}
		return acc;
  }, {} )
}

The possibilities are endless. Let’s say some Ortus guy writes a generic module ipdetect to detect our visitor IP’s. This module is very successful, so it will be used in the sentry module, in cbsecurity and more. Although the getRealIp() method is very smart, my loadbalancers are not so smart, so I have to write my own logic to detect these client IP’s. I could ask the Ortus guy to add my logic to the module, or.. I could just create my own module, override this method and replace the mapping, instantly fixing ip detection in all dependant modules. Of course it would be more elegant to to open up the ipdetect module and add a configuration setting, so I can replace the default getRealIP method. This is something which is done in the cbvalidation module for example, where you can replace the Validation manager with your own version and/or provide your own validators.
There are many possibilities to customize modules. As said, it only makes sense if you can not contribute code to the module and if your changes are small. If you want to rewrite half of the module it might make more sense to create your own version.