Patches and Cross-plugin Extensions
When my_plugin extends behavior owned by core, an engine, or another plugin, keep that extension in my_plugin.
The preferred solution is to define a patch in the extending component instead of adding if my_plugin_active? or if Redmine::Plugin.installed?(:my_plugin) branches to the component being extended.
Why this is preferred
- The feature is loaded only when the extending plugin is installed and active.
- Ownership stays clear. Code for
my_pluginremains inmy_plugin. - The extended component stays isolated from optional dependencies.
- Removing or disabling the plugin removes the behavior cleanly.
Preferred approach
If my_plugin needs to extend Issue, ApplicationController, a service object, or another plugin's class, create a patch under my_plugin and register or include it from my_plugin during boot.
Typical locations are under patches/ or another plugin-local namespace dedicated to patches.
| plugins/my_plugin/patches/redmine/models/issue_patch.rb | |
|---|---|
Avoid this pattern
Do not keep my_plugin behavior inside the extended component behind a condition.
| bad example | |
|---|---|
This couples the base component to an optional plugin and spreads my_plugin logic outside of my_plugin.
What belongs in the original component
Keep code in the original component only when the behavior is genuinely generic and should exist independently of the extending plugin.
If you need a safer extension point for multiple consumers, prefer adding a hook or another explicit extension point to the original component, then implement the plugin-specific behavior in the plugin.
For views, follow the existing rule from code style:
- for small additions, prefer hooks
- for large rewrites, a template or partial override can be acceptable
Summary rule
If a feature exists only because my_plugin is present, the implementation should live in my_plugin, usually as a patch.