Patches and cross-plugin extensions¶
Use a patch to change a class owned by core, an engine or another plugin. Keep the patch in the plugin that needs it, for example in your modification_<name> plugin. Do not edit the class itself: updates overwrite it.
Do not add if Redmine::Plugin.installed?(:my_plugin) branches to the extended class. A patch in my_plugin has these advantages:
- It loads only when
my_pluginis installed and active. - The code stays in the plugin that owns the feature.
- The extended component does not depend on an optional plugin.
- Removing the plugin removes the behaviour.
Write and register a patch¶
Put the patch into patches/ of your plugin. Easy8 requires every .rb file under patches/ at boot, so the file must register the patch itself. Pass the class and the patching module as strings.
module ModificationAcme
module IssuePatch
def some_method
super
do_something_for_acme
end
end
end
EasyPatchManager.register_model_patch "Issue", "ModificationAcme::IssuePatch", prepend: true
With prepend: true the module is prepended, so its methods can call super. Without it the module is included, which suits patches that add methods or use an included(base) hook with base.class_eval.
Choose the registration method by the kind of class:
| Method | Class |
|---|---|
register_model_patch |
Models |
register_controller_patch |
Controllers |
register_helper_patch |
Helpers |
register_concern_patch |
Concerns |
register_other_patch |
Other classes, for example Redmine::FieldFormat::StringFormat |
register_patch_to_be_first |
A patch that must be applied before the others |
Options: prepend:, if: (a proc, the patch is applied only when it returns true), first:, last:, before: and after: (order against another patch). Pass an array of class names to patch several classes with one module.
What belongs in the original component¶
Keep code in the original component only when the behaviour is generic and should exist without the extending plugin. If several plugins need the same extension, add a hook or another explicit extension point to the original component and implement the plugin-specific part in the plugin.
For views, prefer view hooks for small additions. A template or partial override is acceptable for a large rewrite.
Patches in a RYS engine¶
A RYS engine registers patches with Rys::Patcher. Use this only to maintain an existing engine, see Get started with RYS.
Rys::Patcher.add("IssuesController") do
# nothing: applied after Redmine plugins are loaded
# :earlier_to_prepare: applied as soon as possible
where :earlier_to_prepare
# Apply once, for classes that are not reloaded (like String)
apply_only_once!
# Apply only if the condition is met
apply_if { CONDITION }
# Apply only if these Redmine plugins are installed
apply_if_plugins :easy_crm_cases
included do
before_action :add_flash_notice, only: [:show]
end
instance_methods do
def show
flash.now[:notice] = "Hello"
super
end
end
instance_methods(feature: "issue.show") do
# Applied only if the RYS feature is active, must call super
end
class_methods do
# Class methods
end
end
The engine's patches/ directory is registered by include ::Rys::EngineExtensions. For another directory, add it with Rys::Patcher.paths << PATH_TO_DIRECTORY. Rys patches are prepended, so they can call super. Do not mix them with alias_method_chain.