Project

General

Profile

Actions

Defect #36245

open

ActiveSupport::Reloader.to_prepare not working in trunk 21287

Added by Alexander Meindl almost 2 years ago. Updated over 1 year ago.

Status:
Resolved
Priority:
Normal
Assignee:
-
Category:
Plugin API
Target version:
-
Start date:
Due date:
% Done:

0%

Estimated time:
Resolution:
Affected version:

Description

With r21283 in trunk ActiveSupport::Reloader.to_prepare and Rails.configuration.to_prepare is not fired anymore.

ActiveSupport::Reloader.to_prepare is required for plugins (as an example), if some code should loaded after all plugins (other plugins) are loaded.

Maybe there is another possibility with Rails 6 or Zeitwerk for doing this, but imho ActiveSupport::Reloader.to_prepare should work within plugins, too. See https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#autoloading-when-the-application-boots


Related issues

Related to Redmine - Patch #34072: Hook after plugins were loadedClosedGo MAEDA

Actions
Related to Redmine - Feature #32938: Rails 6: Zeitwerk supportClosedMarius BALTEANU

Actions
Actions #1

Updated by Alexander Meindl almost 2 years ago

The defect is for the usage of ActiveSupport::Reloader.to_prepare in a plugin - not in Redmine itself (just to make it clear).

I did not find a solution to fix this behavior until now.

Here is an example for a plugin init.rb:

Redmine::Plugin.register :my_plugin do
  name 'My plugin'
  version '0.01'
end

ActiveSupport::Reloader.to_prepare do
  raise 'this is never called'
end

Rails.configuration.to_prepare do
  raise 'this is never called, too'
end

Actions #2

Updated by Takashi Kato almost 2 years ago

I apologize for the delay in responding.
Before the introduction of zeitwerk, the autoloader loads Redmine plugins on initializing.

After the introduction of zeitwerk, to make the Redmine::Plugin class manageable for zeitwerk, Redmine::PluginLoader runs the "init.rb" for all plugins inside the "Rails.configuration.to_prepare" block (and run on every reload).

https://www.redmine.org/projects/redmine/repository/entry/trunk/lib/redmine/plugin_loader.rb#L108

Now processing written inside the "Rails.configuration.to_prepare" block in "init.rb" can be written directly in "init.rb".
Let me know if there are any problems in creating plugins.

Actions #3

Updated by Takashi Kato almost 2 years ago

Alexander
It may not be a plugin you want to fix, but I found your plugin on GitHub and made it compatible with zeitwerk.

https://github.com/tohosaku/redmine_emojibutton/commits/zeitwerk

Actions #4

Updated by Alexander Meindl almost 2 years ago

Hi Takashi,

thanks for your answer. The problem is, you cannot use Classes from Plugin B with Plugin A - because Plugin B is not initialized at this moment. Because of this, till now without zeitwerk, the solution was to use this Classes after all plugins are initialized (with Rails.configuration.to_prepare).

If Redmine::PluginLoader loads all plugins in "Rails.configuration.to_prepare" block, it is not possible to call an "Rails.configuration.to_prepare" block in a plugin again. This would be a "Rails.configuration.to_prepare" block in a "Rails.configuration.to_prepare" block - and this does not work - as it looks at the moment.

Here are some examples: https://github.com/AlphaNodes/additional_tags/blob/master/init.rb or https://github.com/AlphaNodes/redmine_saml/blob/master/init.rb or https://github.com/AlphaNodes/redmine_sudo/blob/master/init.rb (we build an plugin loader for that)

I am not sure, if you get me right. I try to explain, that if you have dependencies between plugins (which we have a lot), there is no way (or I do not know it), how we can run code from a plugin, after all plugins are initialized.

An example: Plugin B requires Plugin C. You cannot use Plugin C code in Plugin B, till it is initialized - and this worked perfectly with Rails.configuration.to_prepare before zeitwerk. Maybe to provide a hook after initializing all plugins could be a solution.

Actions #5

Updated by Ko Nagase almost 2 years ago

Hi Alexander,

I still haven't tried Redmine trunk yet, but I encountered the similar situation which needs to control plugins load orders in Redmine 4.2-stable branch.
https://github.com/gtt-project/redmine_gtt/pull/130

From glance of Takashi's comment,

https://www.redmine.org/projects/redmine/repository/entry/trunk/lib/redmine/plugin_loader.rb#L108

I noticed that there seems to be after_plugins_loaded hook which seems to be called when all plugins are loaded, so I guess that we can try to use this way as a workaround.
https://www.redmine.org/issues/20263

Actions #6

Updated by Alexander Meindl almost 2 years ago

Hi Ko,

indeed I found after_plugins_loaded hook some hours ago. But the problem with that is, you cannot use a patched method in Redmine::Plugin.register block (e.g. to add a link to menu for special conditions, which requires a patch, which is applied later with after_plugins_loaded hook).

Maybe the way with after_plugins_loaded hook is the right direction. Not sure, if there are more problems with that.
But dispense with Rails.configuration.to_prepare means a lot of rework/adjustments in plugins.

Actions #7

Updated by Alexander Meindl almost 2 years ago

  • Status changed from New to Resolved

after_plugins_loaded hook works for me as a replacement for Rails.configuration.to_prepare

Actions #8

Updated by Go MAEDA almost 2 years ago

  • Related to Patch #34072: Hook after plugins were loaded added
Actions #9

Updated by Go MAEDA almost 2 years ago

Actions #10

Updated by Takashi Kato almost 2 years ago

Hi Alexander,

Glad that this is solved!
Thank you for your very meaningful report as it was a case that I hadn't really anticipated.

Actions #11

Updated by Ko Nagase over 1 year ago

Sorry for the very late reply.

I tried to check Zeitwerk plugin load sequence by "puts" debug on the latest 4.2-stable and master (trunk) branches with using ruby 2.7.4, and the difference was as follows:
4.2-stable master (trunk)
code
puts 'MyPlugin - init.rb'

Redmine::Plugin.register :my_plugin do
  puts 'MyPlugin - Redmine::Plugin.register'
  name 'My Plugin plugin'
  author 'Author name'
  description 'This is a plugin for Redmine'
  version '0.0.1'
  url 'http://example.com/path/to/plugin'
  author_url 'http://example.com/about'
end

ActiveSupport::Reloader.to_prepare do
  puts 'MyPlugin - ActiveSupport::Reloader.to_prepare'
end

Rails.configuration.to_prepare do
  puts 'MyPlugin - Rails.configuration.to_prepare'
end

Rails.application.config.to_prepare do
  puts 'MyPlugin - Rails.application.config.to_prepare'
end

Rails.application.reloader.to_prepare do
  puts 'MyPlugin - Rails.application.reloader.to_prepare'
end

Rails.application.config.after_initialize do
  puts 'MyPlugin - Rails.application.config.after_initialize'
end

class AfterPluginsLoadedHook < Redmine::Hook::Listener
  def after_plugins_loaded(context = {})
    puts 'MyPlugin - after_plugins_loaded hook'
  end
end
puts 'MyPlugin - init.rb'

Redmine::Plugin.register :my_plugin do
  puts 'MyPlugin - Redmine::Plugin.register'
  name 'My Plugin plugin'
  author 'Author name'
  description 'This is a plugin for Redmine'
  version '0.0.1'
  url 'http://example.com/path/to/plugin'
  author_url 'http://example.com/about'
end

ActiveSupport::Reloader.to_prepare do
  puts 'MyPlugin - ActiveSupport::Reloader.to_prepare'
end

Rails.configuration.to_prepare do
  puts 'MyPlugin - Rails.configuration.to_prepare'
end

Rails.application.config.to_prepare do
  puts 'MyPlugin - Rails.application.config.to_prepare'
end

Rails.application.reloader.to_prepare do
  puts 'MyPlugin - Rails.application.reloader.to_prepare'
end

Rails.application.config.after_initialize do
  puts 'MyPlugin - Rails.application.config.after_initialize'
end

#class AfterPluginsLoadedHook < Redmine::Hook::Listener
Class.new(Redmine::Hook::ViewListener) do |c|
  def after_plugins_loaded(context = {})
    puts 'MyPlugin - after_plugins_loaded hook'
  end
end
result
# Init server
MyPlugin - init.rb
MyPlugin - Redmine::Plugin.register
MyPlugin - after_plugins_loaded hook
MyPlugin - ActiveSupport::Reloader.to_prepare
MyPlugin - Rails.application.reloader.to_prepare
MyPlugin - Rails.configuration.to_prepare
MyPlugin - Rails.application.config.to_prepare
MyPlugin - Rails.application.config.after_initialize

# Reload 1st after editing "plugins/my_plugin/config/locales/en.yml" 
MyPlugin - ActiveSupport::Reloader.to_prepare
MyPlugin - Rails.application.reloader.to_prepare
MyPlugin - Rails.configuration.to_prepare
MyPlugin - Rails.application.config.to_prepare

# Reload 2nd after editing "plugins/my_plugin/config/locales/en.yml", again
MyPlugin - ActiveSupport::Reloader.to_prepare
MyPlugin - Rails.application.reloader.to_prepare
MyPlugin - Rails.configuration.to_prepare
MyPlugin - Rails.application.config.to_prepare
# Init server
MyPlugin - init.rb
MyPlugin - Redmine::Plugin.register
MyPlugin - after_plugins_loaded hook
MyPlugin - Rails.application.config.after_initialize

# Reload 1st after editing "plugins/my_plugin/config/locales/en.yml" 
MyPlugin - init.rb
MyPlugin - Redmine::Plugin.register
MyPlugin - Rails.application.config.after_initialize
MyPlugin - after_plugins_loaded hook
MyPlugin - ActiveSupport::Reloader.to_prepare
MyPlugin - Rails.application.reloader.to_prepare

# Reload 2nd after editing "plugins/my_plugin/config/locales/en.yml", again
MyPlugin - init.rb
MyPlugin - Redmine::Plugin.register
MyPlugin - Rails.application.config.after_initialize
MyPlugin - after_plugins_loaded hook
MyPlugin - ActiveSupport::Reloader.to_prepare
MyPlugin - Rails.application.reloader.to_prepare
MyPlugin - ActiveSupport::Reloader.to_prepare
MyPlugin - Rails.application.reloader.to_prepare

In master (trunk) branch, I had to change the Hook class definition, because of the following error when reloading.
(Thanks tohosaku and @matobaa for the @redmine_ld_rize plugin's commit!)

TypeError (superclass mismatch for class AfterPluginsLoadedHook):

plugins/my_plugin/init.rb:29:in `<top (required)>'
lib/redmine/plugin_loader.rb:31:in `load'
lib/redmine/plugin_loader.rb:31:in `run_initializer'
lib/redmine/plugin_loader.rb:108:in `each'
lib/redmine/plugin_loader.rb:108:in `block in load'

In master (trunk) branch,
  • "ActiveSupport::Reloader.to_prepare" and "Rails.application.reloader.to_prepare" are actually called when reloading, but not called at initialization.
  • "ActiveSupport::Reloader.to_prepare" and "Rails.application.reloader.to_prepare" seem to be called multiple times after 2nd reloading, and I think that this behavior needs to be fixed.
  • "after_plugins_loaded" hook called timing is different between 4.2-stable branch (only once) and master (trunk) (every load/reload), and I think that same called timing is preferable (especially when supporting both Redmine 4.2 and 5.0 in the plugin).

2022-04-04: Added "Rails.application.config.after_initialize" in above table

Actions #12

Updated by Ko Nagase over 1 year ago

"after_plugins_loaded" hook called timing is different between 4.2-stable branch (only once) and master (trunk) (every load/reload), and I think that same called timing is preferable (especially when supporting both Redmine 4.2 and 5.0 in the plugin).

Well, about this, just separating the event handler to "Rails.application.config.after_initialize" seems to be enough. Show

Also, in current master (trunk), "Rails.application.config.after_initialize" event handler (in the plugin's "init.rb") seems to be called at every load/reload timing without duplicate call, so this can be also used instead of other ".to_prepare" functions of Redmine <= 4.2. (But I am not sure whether this is expected result...) Show

Here is the combination result from above diffs. Show


FYI

Actions #13

Updated by Ko Nagase over 1 year ago

"ActiveSupport::Reloader.to_prepare" and "Rails.application.reloader.to_prepare" seem to be called multiple times after 2nd reloading, and I think that this behavior needs to be fixed.

About this, just adding once execution guard by class variable may be enough. Show

With this change, the result becomes as follows, and I think that this is the most similar with past (Redmine <= 4.2) sequence. Show

Actions #14

Updated by Ko Nagase over 1 year ago

Sorry, above note-13 comment seemed to be completely wrong...

Now, I am using "Rails.application.config.after_initialize" with current master (trunk) branch, and it seems to be no problem.
https://github.com/gtt-project/redmine_custom_fields_groups/pull/14

Actions

Also available in: Atom PDF