Skip to content

EasyInitHelper registration helpers

EasyInitHelper is the preferred way to register reload-sensitive Easy8 plugin and engine behavior from initializers.

Use it when initialization code touches Redmine registries, Easy registries, or hook listener classes that must keep working after Rails reloads in development. The helpers keep the registration timing explicit and avoid loading constants too early under Zeitwerk.

Helper selection

Use case Helper
Permissions and project modules EasyInitHelper.register_access_control_block
Redmine menu items EasyInitHelper.register_menu_block
Redmine::Hook::ViewListener subclasses EasyInitHelper.register_redmine_view_hooks
Registries owned by lazily loaded constants, for example EasyQuery, Redmine::Search, or Redmine::Activity EasyInitHelper.on_constant_autoload

Permissions

Register access-control entries in config/initializers/01_access_control.rb.

config/initializers/01_access_control.rb
EasyInitHelper.register_access_control_block do
  Redmine::AccessControl.map do |map|
    map.project_module :my_plugin do |project_module|
      project_module.permission :view_my_plugin_entities,
                                { my_plugin_entities: [:index] },
                                read: true
    end
  end
end

Use this instead of Rails.application.config.after_initialize for new permissions. The block is registered as Rails.application.config.to_prepare, so it runs again after development reloads.

Register menus in config/initializers/02_menu_manager.rb.

config/initializers/02_menu_manager.rb
EasyInitHelper.register_menu_block do
  Redmine::MenuManager.map :project_menu do |menu|
    menu.push :my_plugin_entities,
              { controller: "my_plugin_entities", action: "index" },
              caption: :label_my_plugin_entities,
              after: :activity,
              param: :project_id
  end
end

Menu conditions can be evaluated without a project or entity in global contexts. If a menu item uses :if, make the proc tolerate nil where that menu can be rendered globally.

View Hooks

Register Redmine view-hook listener classes by constant name in config/initializers/03_hooks.rb.

config/initializers/03_hooks.rb
EasyInitHelper.register_redmine_view_hooks("MyPlugin::Hooks")

The hook class must live in an autoloaded app/ path and follow Zeitwerk naming.

app/utils/my_plugin/hooks.rb
module MyPlugin
  class Hooks < Redmine::Hook::ViewListener
    render_on :view_issues_show_details_bottom,
              partial: "my_plugin/issues/details"
  end
end

Do not constantize hook listener classes directly in an initializer. Loading is deferred because constantizing inside a Redmine::Hook::ViewListener load path can fail before the listener constant is fully defined.

Constant-Autoload Registries

Use EasyInitHelper.on_constant_autoload when the registration belongs to a constant that should remain lazy-loaded.

config/initializers/08_others.rb
EasyInitHelper.on_constant_autoload("EasyQuery") do
  EasyQuery.map do |query|
    query.register "MyPluginEntityQuery"
  end
end

The helper delegates to Zeitwerk on_load. It also validates that the target constant is autoloadable and raises if the constant is already loaded when the block is registered. That failure means the initializer order is wrong or some earlier code touched the constant too early.

The same pattern is used for other registries:

EasyInitHelper.on_constant_autoload("Redmine::Search") do
  Redmine::Search.map do |search|
    search.register :my_plugin_entities
  end
end

EasyInitHelper.on_constant_autoload("Redmine::Activity") do
  Redmine::Activity.map do |activity|
    activity.register :my_plugin_entities,
                      class_name: %w[MyPluginEntity Journal],
                      default: false
  end
end

Avoid For New Code

Do not use these patterns for new reload-sensitive registrations:

  • Rails.application.config.after_initialize for menus, permissions, hook listeners, or EasyQuery registration
  • ActiveSupport.on_load(:easy_project_start) for new EasyQuery registration
  • direct constantization of view-hook listener classes in initializers
  • RedmineExtensions::Reloader.to_prepare, which is no longer supported

Reload Verification

The reload safety net is in spec/lib/reloader_spec.rb. It checks that hooks, menus, permissions, and menu conditions remain stable across repeated Rails.application.reloader.reload! calls.

Run it when changing registration code:

TAGS=reloader bundle exec rspec spec/lib/reloader_spec.rb

There is also a request smoke test:

TAGS=reloader bundle exec rspec spec/requests/reloader_spec.rb

Both specs are skipped unless TAGS=reloader is set and Rails reloading is enabled.