The other day I was writing some patches for a custom product (see
Developers: patch it yourself). One of the requirements is that the product code needs to be supported at 4, 4.1 and 4.1.1. The change is to add a new dynamic variable to the beginning of
map_view.int!do_render()
.
Originally, I thought I would have to make three different versions of this patch file (one for each CST version that the product supports). That would seem to make sense because at each of the versions, the contents of the method could have changed slightly. As it turns out, the contents had changed between two of the versions.
Having three version-specific patches for a single method ends up being an administrative nightmare because each time a new TSB is released, I need to review all the changes in the TSB for conflicts with my patch code. In some cases this might be necessary because the patch makes significant changes in the middle of the method. But in many cases, the changes we want to put in are at the top or the bottom of the method. In those cases, I discovered that I could use method
:define_method_synonym()
to wrap my new code around the core code without touching any of the functionality in the core code...
# use define_method_synonym() to wrap some of our behavior
# around the core method without redefining the core behavior.
_if map_view.method(:original!int!do_render|()|) _is _unset
_then
map_view.define_method_synonym(:original!int!do_render|()|,:int!do_render|()|)
_endif
$
_pragma(classify_level=restricted)
_method map_view.int!do_render( _optional post_render_set )
##
## Draws the current visible set in the render thread.
##
_dynamic !ve_connector_current_map_view! << _self
_return _self.original!int!do_render(post_render_set)
_endmethod
$
And if you use the
_gather/_scatter
keywords, your wrapper does not even need to worry about whether the arguments for a method change from version to version.
This technique is also useful for putting debug code into unshipped source code. Let's say I want to get a traceback every time I get a user_error dialog with the word "coordinate" in the dialog message. The dialog is actually activated somewhere in
basic_window.show_alert()
. This method code, however, is unshipped so your test code might look like...
_if basic_window.method(:original!show_alert|()|) _is _unset
_then
basic_window.define_method_synonym(:original!show_alert|()|,:show_alert|()|)
_endif
$
_pragma(classify_level=restricted, usage={redefinable})
_method basic_window.show_alert(message, _optional yes_message, no_message, default, mode)
_if message.canonical.index_of_seq("coordinate") _isnt _unset
_then
!traceback!()
_endif
_return _self.original!show_alert(message,yes_message,no_message,default,mode)
_endmethod
$
NOTE: the method comments on
:define_method_synonym()
say that it is "severely deprecated." But until the time that this method is removed from the core code, I find it a very useful tool.
NOTE NOTE: While this technique does minimize the touch points of your code with the core code,
you still need to be aware of the classifications (basic,advanced,restricted) of a method to understand how/if you should modify the method.