This gem can be used for Redmine Plugin for loading customizations.
In an ideal world, all plugins would use this gem as base. Or even better: this gem comes to Redmine core itself.
If you are a Redmine developer, feel free and offer PRs with your Improvements.
- Loader for plugin customizations
- Deface support for Redmine plugins
- Extension points: classes a plugin collects from all plugins, without a list naming them
- a standard process for load patches, hooks or helper for plugins
- deface is ready to use and does not require any additional plugin (e.g. redmine_base_deface). It is easier for a plugin user, to install one plugin (instead of two). This makes also sure, that all plugins uses the same deface version.
- Redmine
>= 7.0(including upcoming) - Ruby
>= 3.2(only maintained ruby versions are supported)
Add Gem to your Gemfile:
gem 'redmine_plugin_kit'A plugin (the host) can collect classes from all plugins, one file per class, e.g. widgets of my_host:
- every plugin keeps its classes in
lib/widgets/; the host also has the modulelib/widgets.rb, which makes it the host - any other plugin declares the point in its
init.rb. Zeitwerk then never eager loads itslib/widgets/, onlyload!of the host loads it, so without the host these files need no guard against a missing base class. They stay autoloadable: nothing outsidelib/widgets/may refer to their constants, anddefined?(Widgets)is no test for the host.
loader = RedminePluginKit::Loader.new plugin_id: 'my_plugin'
loader.extends %w[widgets]
# a plugin without RedminePluginKit::Loader
RedminePluginKit::Extensions.declare __dir__, %w[widgets]classes returns only what load! loaded, so read them at runtime, not in the setup of a plugin (the setups run in plugin order); RedminePluginKit::Extensions.loaded? 'widgets' tells whether load! ran.
An undeclared lib/widgets/ stops the boot as long as the host is installed. Without it, a convention test of the plugin finds such a directory:
assert_empty RedminePluginKit::Extensions.undeclared_dirs(plugin_dir, standalone: %w[searches])standalone names the directories in lib/ with classes but no module that belong to the plugin itself; a name without such a directory is an error.
The host loads and lists them:
module Widgets
class Base
extend RedminePluginKit::Extensions::Abstract
abstract!
end
end
# in the setup of the host, which runs at boot and after every code reload
RedminePluginKit::Extensions.load! 'widgets'
# the loaded subclasses without abstract ones; with a key, a subclass with the
# same key replaces its superclass and two unrelated classes are an error
RedminePluginKit::Extensions.classes Widgets::Base, key: :widget_nameA file name used by several plugins stops the boot, as Zeitwerk would load only one of them.