https://www.redmine.org/https://www.redmine.org/favicon.ico?16793021292021-11-25T08:20:23ZRedmineRedmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1047282021-11-25T08:20:23ZAlexander Meindl
<ul></ul><p>The defect is for the usage of ActiveSupport::Reloader.to_prepare in a plugin - not in Redmine itself (just to make it clear).</p>
<p>I did not find a solution to fix this behavior until now.</p>
<p>Here is an example for a plugin init.rb:</p>
<pre><code class="ruby syntaxhl"><span class="no">Redmine</span><span class="o">::</span><span class="no">Plugin</span><span class="p">.</span><span class="nf">register</span> <span class="ss">:my_plugin</span> <span class="k">do</span>
<span class="nb">name</span> <span class="s1">'My plugin'</span>
<span class="n">version</span> <span class="s1">'0.01'</span>
<span class="k">end</span>
<span class="no">ActiveSupport</span><span class="o">::</span><span class="no">Reloader</span><span class="p">.</span><span class="nf">to_prepare</span> <span class="k">do</span>
<span class="k">raise</span> <span class="s1">'this is never called'</span>
<span class="k">end</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">configuration</span><span class="p">.</span><span class="nf">to_prepare</span> <span class="k">do</span>
<span class="k">raise</span> <span class="s1">'this is never called, too'</span>
<span class="k">end</span>
</pre></code></pre> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1047582021-12-01T12:53:54ZTakashi Kato
<ul></ul><p>I apologize for the delay in responding.<br />Before the introduction of zeitwerk, the autoloader loads Redmine plugins on initializing.</p>
<p>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).</p>
<p><a class="external" href="https://www.redmine.org/projects/redmine/repository/entry/trunk/lib/redmine/plugin_loader.rb#L108">https://www.redmine.org/projects/redmine/repository/entry/trunk/lib/redmine/plugin_loader.rb#L108</a></p>
<p>Now processing written inside the "Rails.configuration.to_prepare" block in "init.rb" can be written directly in "init.rb".<br />Let me know if there are any problems in creating plugins.</p> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1047602021-12-01T13:52:01ZTakashi Kato
<ul></ul><p>Alexander<br />It may not be a plugin you want to fix, but I found your plugin on GitHub and made it compatible with zeitwerk.</p>
<p><a class="external" href="https://github.com/tohosaku/redmine_emojibutton/commits/zeitwerk">https://github.com/tohosaku/redmine_emojibutton/commits/zeitwerk</a></p> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1047612021-12-01T15:49:25ZAlexander Meindl
<ul></ul><p>Hi Takashi,</p>
<p>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).</p>
<p>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.</p>
<p>Here are some examples: <a class="external" href="https://github.com/AlphaNodes/additional_tags/blob/master/init.rb">https://github.com/AlphaNodes/additional_tags/blob/master/init.rb</a> or <a class="external" href="https://github.com/AlphaNodes/redmine_saml/blob/master/init.rb">https://github.com/AlphaNodes/redmine_saml/blob/master/init.rb</a> or <a class="external" href="https://github.com/AlphaNodes/redmine_sudo/blob/master/init.rb">https://github.com/AlphaNodes/redmine_sudo/blob/master/init.rb</a> (we build an <a href="https://github.com/AlphaNodes/additionals/blob/master/app/models/additionals_loader.rb#L37" class="external">plugin loader</a> for that)</p>
<p>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.</p>
<p>An example: <em>Plugin B</em> requires <em>Plugin C</em>. You cannot use <em>Plugin C</em> code in <em>Plugin B</em>, 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.</p> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1047622021-12-01T16:09:16ZKo Nagase
<ul></ul><p>Hi Alexander,</p>
<p>I still haven't tried Redmine trunk yet, but I encountered the similar situation which needs to control plugins load orders in Redmine <code>4.2-stable</code> branch.<br /><a class="external" href="https://github.com/gtt-project/redmine_gtt/pull/130">https://github.com/gtt-project/redmine_gtt/pull/130</a></p>
<p>From glance of Takashi's comment,</p>
<blockquote>
<p><a class="external" href="https://www.redmine.org/projects/redmine/repository/entry/trunk/lib/redmine/plugin_loader.rb#L108">https://www.redmine.org/projects/redmine/repository/entry/trunk/lib/redmine/plugin_loader.rb#L108</a></p>
</blockquote>
<p>I noticed that there seems to be <code>after_plugins_loaded</code> 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.<br /><a class="external" href="https://www.redmine.org/issues/20263">https://www.redmine.org/issues/20263</a></p> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1047632021-12-01T16:32:53ZAlexander Meindl
<ul></ul><p>Hi Ko,</p>
<p>indeed I found <em>after_plugins_loaded</em> 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 <em>after_plugins_loaded</em> hook).</p>
<p>Maybe the way with <em>after_plugins_loaded</em> hook is the right direction. Not sure, if there are more problems with that.<br />But dispense with Rails.configuration.to_prepare means a lot of rework/adjustments in plugins.</p> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1047652021-12-02T06:43:39ZAlexander Meindl
<ul><li><strong>Status</strong> changed from <i>New</i> to <i>Resolved</i></li></ul><p><em>after_plugins_loaded</em> hook works for me as a replacement for Rails.configuration.to_prepare</p> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1047662021-12-02T08:22:34ZGo MAEDA
<ul><li><strong>Related to</strong> <i><a class="issue tracker-3 status-5 priority-4 priority-default closed" href="/issues/34072">Patch #34072</a>: Hook after plugins were loaded</i> added</li></ul> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1047682021-12-02T08:23:03ZGo MAEDA
<ul><li><strong>Related to</strong> <i><a class="issue tracker-2 status-5 priority-4 priority-default closed" href="/issues/32938">Feature #32938</a>: Rails 6: Zeitwerk support</i> added</li></ul> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1047722021-12-02T13:16:41ZTakashi Kato
<ul></ul><p>Hi Alexander,</p>
<p>Glad that this is solved!<br />Thank you for your very meaningful report as it was a case that I hadn't really anticipated.</p> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1050992022-01-06T05:32:08ZKo Nagase
<ul></ul><p>Sorry for the very late reply.</p>
I tried to check Zeitwerk plugin load sequence by "puts" debug on the latest <code>4.2-stable</code> and <code>master (trunk)</code> branches with using ruby 2.7.4, and the difference was as follows:
<table>
<tr>
<th></th>
<th>4.2-stable </th>
<th>master (trunk) </th>
</tr>
<tr>
<td> code </td>
<td> <pre><code class="ruby syntaxhl"><span class="nb">puts</span> <span class="s1">'MyPlugin - init.rb'</span>
<span class="no">Redmine</span><span class="o">::</span><span class="no">Plugin</span><span class="p">.</span><span class="nf">register</span> <span class="ss">:my_plugin</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - Redmine::Plugin.register'</span>
<span class="nb">name</span> <span class="s1">'My Plugin plugin'</span>
<span class="n">author</span> <span class="s1">'Author name'</span>
<span class="n">description</span> <span class="s1">'This is a plugin for Redmine'</span>
<span class="n">version</span> <span class="s1">'0.0.1'</span>
<span class="n">url</span> <span class="s1">'http://example.com/path/to/plugin'</span>
<span class="n">author_url</span> <span class="s1">'http://example.com/about'</span>
<span class="k">end</span>
<span class="no">ActiveSupport</span><span class="o">::</span><span class="no">Reloader</span><span class="p">.</span><span class="nf">to_prepare</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - ActiveSupport::Reloader.to_prepare'</span>
<span class="k">end</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">configuration</span><span class="p">.</span><span class="nf">to_prepare</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - Rails.configuration.to_prepare'</span>
<span class="k">end</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">to_prepare</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - Rails.application.config.to_prepare'</span>
<span class="k">end</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">reloader</span><span class="p">.</span><span class="nf">to_prepare</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - Rails.application.reloader.to_prepare'</span>
<span class="k">end</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">after_initialize</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - Rails.application.config.after_initialize'</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">AfterPluginsLoadedHook</span> <span class="o"><</span> <span class="no">Redmine</span><span class="o">::</span><span class="no">Hook</span><span class="o">::</span><span class="no">Listener</span>
<span class="k">def</span> <span class="nf">after_plugins_loaded</span><span class="p">(</span><span class="n">context</span> <span class="o">=</span> <span class="p">{})</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - after_plugins_loaded hook'</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre> </td>
<td> <pre><code class="ruby syntaxhl"><span class="nb">puts</span> <span class="s1">'MyPlugin - init.rb'</span>
<span class="no">Redmine</span><span class="o">::</span><span class="no">Plugin</span><span class="p">.</span><span class="nf">register</span> <span class="ss">:my_plugin</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - Redmine::Plugin.register'</span>
<span class="nb">name</span> <span class="s1">'My Plugin plugin'</span>
<span class="n">author</span> <span class="s1">'Author name'</span>
<span class="n">description</span> <span class="s1">'This is a plugin for Redmine'</span>
<span class="n">version</span> <span class="s1">'0.0.1'</span>
<span class="n">url</span> <span class="s1">'http://example.com/path/to/plugin'</span>
<span class="n">author_url</span> <span class="s1">'http://example.com/about'</span>
<span class="k">end</span>
<span class="no">ActiveSupport</span><span class="o">::</span><span class="no">Reloader</span><span class="p">.</span><span class="nf">to_prepare</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - ActiveSupport::Reloader.to_prepare'</span>
<span class="k">end</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">configuration</span><span class="p">.</span><span class="nf">to_prepare</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - Rails.configuration.to_prepare'</span>
<span class="k">end</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">to_prepare</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - Rails.application.config.to_prepare'</span>
<span class="k">end</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">reloader</span><span class="p">.</span><span class="nf">to_prepare</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - Rails.application.reloader.to_prepare'</span>
<span class="k">end</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">after_initialize</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - Rails.application.config.after_initialize'</span>
<span class="k">end</span>
<span class="c1">#class AfterPluginsLoadedHook < Redmine::Hook::Listener</span>
<span class="no">Class</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="no">Redmine</span><span class="o">::</span><span class="no">Hook</span><span class="o">::</span><span class="no">ViewListener</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">c</span><span class="o">|</span>
<span class="k">def</span> <span class="nf">after_plugins_loaded</span><span class="p">(</span><span class="n">context</span> <span class="o">=</span> <span class="p">{})</span>
<span class="nb">puts</span> <span class="s1">'MyPlugin - after_plugins_loaded hook'</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre> </td>
</tr>
<tr>
<td> result </td>
<td> <pre>
# 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
</pre> </td>
<td> <pre>
# 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
</pre> </td>
</tr>
</table>
<p>In <code>master (trunk)</code> branch, I had to change the Hook class definition, because of the following error when reloading.<br />(Thanks <code>tohosaku and @matobaa for the @redmine_ld_rize</code> plugin's commit!)<br /><pre>
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'
</pre></p>
In <code>master (trunk)</code> branch,
<ul>
<li>"ActiveSupport::Reloader.to_prepare" and "Rails.application.reloader.to_prepare" are actually called when reloading, but not called at initialization.</li>
<li>"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.</li>
<li>"after_plugins_loaded" hook called timing is different between <code>4.2-stable</code> branch (only once) and <code>master (trunk)</code> (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).</li>
</ul>
<p><strong>2022-04-04: Added "Rails.application.config.after_initialize" in above table</strong></p> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1051062022-01-06T09:14:55ZKo Nagase
<ul></ul><blockquote>
<p>"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).</p>
</blockquote>
<p>Well, about this, just separating the event handler to "Rails.application.config.after_initialize" seems to be enough.
<a href="#" onclick="$('#collapse-e87d5e0b-show, #collapse-e87d5e0b-hide').toggle(); $('#collapse-e87d5e0b').fadeToggle(150);; return false;" id="collapse-e87d5e0b-show" class="icon icon-collapsed collapsible">Show</a><a href="#" onclick="$('#collapse-e87d5e0b-show, #collapse-e87d5e0b-hide').toggle(); $('#collapse-e87d5e0b').fadeToggle(150);; return false;" id="collapse-e87d5e0b-hide" class="icon icon-expended collapsible" style="display:none;">Hide</a><div id="collapse-e87d5e0b" class="collapsed-text" style="display:none;"><pre><code class="diff syntaxhl"><span class="gd">--- a/lib/redmine/plugin_loader.rb
</span><span class="gi">+++ b/lib/redmine/plugin_loader.rb
</span><span class="p">@@ -106,7 +106,9 @@</span> module Redmine
Rails.application.config.to_prepare do
PluginLoader.directories.each(&:run_initializer)
<span class="gi">+ end
</span>
+ Rails.application.config.after_initialize do
Redmine::Hook.call_hook :after_plugins_loaded
end
end
</code></pre></div></p>
<p>Also, in current <code>master (trunk)</code>, "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...)
<a href="#" onclick="$('#collapse-127ae342-show, #collapse-127ae342-hide').toggle(); $('#collapse-127ae342').fadeToggle(150);; return false;" id="collapse-127ae342-show" class="icon icon-collapsed collapsible">Show</a><a href="#" onclick="$('#collapse-127ae342-show, #collapse-127ae342-hide').toggle(); $('#collapse-127ae342').fadeToggle(150);; return false;" id="collapse-127ae342-hide" class="icon icon-expended collapsible" style="display:none;">Hide</a><div id="collapse-127ae342" class="collapsed-text" style="display:none;"><pre><code class="diff syntaxhl"><span class="gi">+ Rails.application.config.after_initialize do
+ puts 'MyPlugin - Rails.application.config.after_initialize'
+ end
</span></code></pre></div></p>
<p>Here is the combination result from above diffs.
<a href="#" onclick="$('#collapse-f2c93abd-show, #collapse-f2c93abd-hide').toggle(); $('#collapse-f2c93abd').fadeToggle(150);; return false;" id="collapse-f2c93abd-show" class="icon icon-collapsed collapsible">Show</a><a href="#" onclick="$('#collapse-f2c93abd-show, #collapse-f2c93abd-hide').toggle(); $('#collapse-f2c93abd').fadeToggle(150);; return false;" id="collapse-f2c93abd-hide" class="icon icon-expended collapsible" style="display:none;">Hide</a><div id="collapse-f2c93abd" class="collapsed-text" style="display:none;"><pre><code>
# 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 - 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 - ActiveSupport::Reloader.to_prepare
MyPlugin - Rails.application.reloader.to_prepare
MyPlugin - ActiveSupport::Reloader.to_prepare
MyPlugin - Rails.application.reloader.to_prepare
</code></pre></div><br />FYI</p> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1051082022-01-06T12:20:56ZKo Nagase
<ul></ul><blockquote>
<p>"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.</p>
</blockquote>
<p>About this, just adding once execution guard by class variable may be enough.
<a href="#" onclick="$('#collapse-96fba600-show, #collapse-96fba600-hide').toggle(); $('#collapse-96fba600').fadeToggle(150);; return false;" id="collapse-96fba600-show" class="icon icon-collapsed collapsible">Show</a><a href="#" onclick="$('#collapse-96fba600-show, #collapse-96fba600-hide').toggle(); $('#collapse-96fba600').fadeToggle(150);; return false;" id="collapse-96fba600-hide" class="icon icon-expended collapsible" style="display:none;">Hide</a><div id="collapse-96fba600" class="collapsed-text" style="display:none;"><pre><code class="diff syntaxhl"><span class="gd">--- a/lib/redmine/plugin_loader.rb
</span><span class="gi">+++ b/lib/redmine/plugin_loader.rb
</span><span class="p">@@ -86,6 +86,8 @@</span> module Redmine
cattr_accessor :directory
self.directory = Rails.root.join('plugins')
+ @@initialized = false
<span class="gi">+
</span> # Absolute path to the plublic directory where plugins assets are copied
cattr_accessor :public_directory
self.public_directory = Rails.root.join('public/plugin_assets')
<span class="p">@@ -105,9 +107,12 @@</span> module Redmine
add_autoload_paths
Rails.application.config.to_prepare do
<span class="gd">- PluginLoader.directories.each(&:run_initializer)
</span><span class="gi">+ if !@@initialized
+ PluginLoader.directories.each(&:run_initializer)
</span>
- Redmine::Hook.call_hook :after_plugins_loaded
<span class="gi">+ Redmine::Hook.call_hook :after_plugins_loaded
+ @@initialized = true
+ end
</span> end
end
</code></pre></div></p>
<p>With this change, the result becomes as follows, and I think that this is the most similar with past (Redmine <= 4.2) sequence.
<a href="#" onclick="$('#collapse-7eab9ab5-show, #collapse-7eab9ab5-hide').toggle(); $('#collapse-7eab9ab5').fadeToggle(150);; return false;" id="collapse-7eab9ab5-show" class="icon icon-collapsed collapsible">Show</a><a href="#" onclick="$('#collapse-7eab9ab5-show, #collapse-7eab9ab5-hide').toggle(); $('#collapse-7eab9ab5').fadeToggle(150);; return false;" id="collapse-7eab9ab5-hide" class="icon icon-expended collapsible" style="display:none;">Hide</a><div id="collapse-7eab9ab5" class="collapsed-text" style="display:none;"><pre><code>
# 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 - ActiveSupport::Reloader.to_prepare
MyPlugin - Rails.application.reloader.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
</code></pre></div></p> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1051122022-01-06T15:54:47ZKo Nagase
<ul></ul><p>Sorry, above note-13 comment seemed to be completely wrong...</p>
<p>Now, I am using "Rails.application.config.after_initialize" with current <code>master (trunk)</code> branch, and it seems to be no problem.<br /><a class="external" href="https://github.com/gtt-project/redmine_custom_fields_groups/pull/14">https://github.com/gtt-project/redmine_custom_fields_groups/pull/14</a></p> Redmine - Defect #36245: ActiveSupport::Reloader.to_prepare not working in trunk 21287https://www.redmine.org/issues/36245?journal_id=1129222024-02-14T12:23:10ZDmitry Lisichkin
<ul></ul><p>Some additions to this problem.<br />Using of <code>Rails.configuration.after_initialize</code> is not correct in init.rb form redmine >= 5.<br /><code>after_initialize</code> callback should be called only once.<br />If we include in this block patches for non reloadable code we will patch them again and again on every code reload.<br /><pre><code class="ruby syntaxhl"><span class="no">A</span> <span class="o">=</span> <span class="no">Module</span><span class="p">.</span><span class="nf">new</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">configuration</span><span class="p">.</span><span class="nf">after_initialize</span> <span class="k">do</span>
<span class="no">Redmine</span><span class="p">.</span><span class="nf">include</span> <span class="no">A</span>
<span class="nb">puts</span> <span class="no">Redmine</span><span class="p">.</span><span class="nf">ancestors</span>
<span class="k">end</span>
</code></pre><br />After several reloads output will be:<br /><pre><code class="ruby syntaxhl"><span class="no">Redmine</span>
<span class="no">A</span>
<span class="no">A</span>
<span class="no">A</span>
<span class="no">A</span>
<span class="no">A</span>
<span class="no">A</span>
</code></pre></p>
<p>In our codebase we just move this patches to local gem railtie:<br /><pre><code class="ruby syntaxhl"><span class="c1"># Gemfile</span>
<span class="n">gemspec</span> <span class="ss">name: </span><span class="s1">'my_plugin'</span>
<span class="n">group</span> <span class="ss">:development</span> <span class="k">do</span>
<span class="n">gemspec</span> <span class="ss">name: </span><span class="s1">'my_plugin-development'</span>
<span class="k">end</span>
<span class="c1"># my_plugin.gemspec</span>
<span class="no">Gem</span><span class="o">::</span><span class="no">Specification</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">spec</span><span class="o">|</span>
<span class="n">spec</span><span class="p">.</span><span class="nf">name</span> <span class="o">=</span> <span class="s1">'my_plugin'</span>
<span class="n">spec</span><span class="p">.</span><span class="nf">version</span> <span class="o">=</span> <span class="s1">'0.0.1'</span>
<span class="n">spec</span><span class="p">.</span><span class="nf">authors</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'John Doe'</span><span class="p">]</span>
<span class="n">spec</span><span class="p">.</span><span class="nf">summary</span> <span class="o">=</span> <span class="s1">'Initializers and dependencies for my_plugin'</span>
<span class="n">spec</span><span class="p">.</span><span class="nf">required_ruby_version</span> <span class="o">=</span> <span class="s1">'>= 2.7.0'</span>
<span class="n">spec</span><span class="p">.</span><span class="nf">require_paths</span> <span class="o">=</span> <span class="sx">%w[non_reloadable_lib]</span>
<span class="n">spec</span><span class="p">.</span><span class="nf">files</span> <span class="o">=</span> <span class="sx">%w[
non_reloadable_lib/my_plugin.rb
non_reloadable_lib/my_plugin/railtie.rb
]</span>
<span class="n">spec</span><span class="p">.</span><span class="nf">add_dependency</span> <span class="s1">'some_dep'</span>
<span class="k">end</span>
<span class="c1"># non_reloadable_lib/my_plugin.rb</span>
<span class="nb">require</span> <span class="s1">'some_dep'</span>
<span class="nb">require</span> <span class="s1">'my_plugin/railtie'</span>
<span class="k">module</span> <span class="nn">MyPlugin</span>
<span class="no">VERSION</span> <span class="o">=</span> <span class="s1">'0.0.1'</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">apply_patches</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># non_reloadable_lib/my_plugin/railtie.rb</span>
<span class="nb">require</span> <span class="s1">'rails/railtie'</span>
<span class="n">moduel</span> <span class="no">MyPlugin</span>
<span class="k">module</span> <span class="nn">Railtie</span> <span class="o"><</span> <span class="no">Rails</span><span class="o">::</span><span class="no">Railtie</span>
<span class="n">initializer</span> <span class="s1">'my_plugin.init'</span> <span class="k">do</span> <span class="o">|</span><span class="n">app</span><span class="o">|</span>
<span class="n">app</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">to_prepare</span> <span class="k">do</span>
<span class="no">MyPlugin</span><span class="p">.</span><span class="nf">apply_patches</span>
<span class="k">end</span>
<span class="n">app</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">after_initialize</span> <span class="k">do</span>
<span class="no">Redmine</span><span class="p">.</span><span class="nf">include</span> <span class="no">NonReloadablePatch</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre><br />In this case we have both callbacks worked properly.</p>
Good things with this approche:
<ul>
<li>plugin parts can be loaded before redmine so we even can add some basic initializers inside plugin</li>
<li>same dependencies from several plugins will not show warnings at <code>bundle install</code></li>
<li>we can manage plugin-dependencies by bundler and not use redmine for this.</li>
<li>we can manage patches order on separate plugins</li>
</ul>
Bad things:
<ul>
<li>Gemfile.lock content after bundle install is not deterministic in general</li>
<li>plugin Railtie can be loaded too early</li>
</ul>
<p>First problem becouse of:<br /><code>Dir.glob File.expand_path("../plugins/*/{Gemfile,PluginGemfile}", __FILE__) do |file|</code><br />There no deterministic sort order on old ruby.<br />Can be fixed like this:<br /><code>Dir.glob(File.expand_path("../plugins/*/{Gemfile,PluginGemfile}", __FILE__)).sort.each do |file|</code></p>
<p>Second problem:<br />For example we can't patch <code>Issue</code> before redmine loaded becouse <code>acts_as_mentionable</code> and others still not loaded to <code>ActiveRecord::Base</code></p>
<p>This can be fixed by this code called at app start:<br /><pre><code class="ruby syntaxhl"><span class="no">ActiveSupport</span><span class="p">.</span><span class="nf">on_load</span><span class="p">(</span><span class="ss">:active_record</span><span class="p">)</span> <span class="k">do</span>
<span class="kp">include</span> <span class="no">Redmine</span><span class="o">::</span><span class="no">Acts</span><span class="o">::</span><span class="no">Mentionable</span>
<span class="k">end</span>
</code></pre><br />Btw this change still nessessary:<br /><pre>
pp ActiveRecord::Base.ancestors
# On current redmine version after several code reloads output will be:
# ...
# Redmine::I18n,
# Redmine::Acts::Mentionable,
# Redmine::Acts::Positioned,
# Redmine::I18n,
# Redmine::Acts::Mentionable,
# Redmine::Acts::Positioned,
# Redmine::I18n,
# Redmine::Acts::Mentionable,
# Redmine::Acts::Positioned,
# Redmine::I18n,
# Redmine::Acts::Mentionable,
# Redmine::Acts::Positioned,
# ...
</pre><br />For complete fix this files should be moved from auto_load_paths somewhere.</p>