Using redmine 3.x, I have a dependency conflict between two plugins - redmine subtask list accordion and Subtask list inherited fields. Installing both raises 500 errors when trying to view an issue.
ActionView::Template::Error (undefined method `sla_has_grandson_issues?' for #<#<Class:0x000056319e27d668>:0x00007f237ad02588>):
1: <% if sla_has_grandson_issues?(@issue) %>
2: <%= content_for :header_tags do
3: stylesheet_link_tag(sla_use_css, :plugin => "redmine_subtask_list_accordion") +
4: javascript_include_tag("subtask_list_accordion" + (subtask_tree_client_processing? ? "_client" : ""), :plugin => "redmine_subtask_list_accordion")
plugins/redmine_subtask_list_accordion/app/views/issues/_subtask_list_accordion_partial.html.erb:1:in `_292e8187f64bee60c61b7b15c99630ab'
After lots of trial and error we fixed the issue by adding the following to the original source code of the first plugin:
included do
alias_method :render_descendants_tree_original, :render_descendants_tree
alias_method :render_descendants_tree, :switch_render_descendants_tree
alias_method :sla_use_css, :sla_use_css
alias_method :switch_render_descendants_tree, :switch_render_descendants_tree
alias_method :render_descendants_tree_accordion, :render_descendants_tree_accordion
alias_method :expand_tree_at_first?, :expand_tree_at_first?
alias_method :sla_has_grandson_issues?, :sla_has_grandson_issues?
alias_method :subtask_tree_client_processing?, :subtask_tree_client_processing?
alias_method :subtask_list_accordion_tree_render_32?, :subtask_list_accordion_tree_render_32?
alias_method :subtask_list_accordion_tree_render_33?, :subtask_list_accordion_tree_render_33?
alias_method :subtask_list_accordion_tree_render_34?, :subtask_list_accordion_tree_render_34?
This is the original code:
Which has the first two alias_method calls from the above source code.
By making an alias method with the same name for each method in the original class, the code works fine. However this seems like a hacky fix, and I don't understand why it works. Can someone explain why the fix works and how to rewrite it properly?
alias_method
creates a copy of the named method in the context where it is called.
Now, if the original method (second argument to alias_method
) is defined somewhere up the inheritance chain and changed in any way later on, you still have that copy around that did not change. Which might explain the behavior you see.
As for the rewriting: As a rule of thumb, throw out all method aliasing (in both plugins) and use Module#prepend
. This SO Answer is a nice overview of good and bad techniques for (monkey) patching Ruby code.
Patching Rails' view helpers as appears to be the case in your plugins can be tricky due to the way Rails handles them (and results may vary depending on code loading order). Wherever possible, avoid it or create new helper modules and add them to the relevant controllers using something like
module RedmineSubtaskListAccordion
module IssuesHelper
def render_descendants_tree(issue)
if sla_has_grandson_issues?(issue) && !subtask_tree_client_processing?
render_descendants_tree_accordion(issue)
else
super # this will call the stock Redmine IssuesHelper#render_descendants_tree method
end
end
end
end
# in init.rb:
IssuesController.class_eval do
helper RedmineSubtaskListAccordion::IssuesHelper
end
I happened to write a blog post about this exact thing a while ago. Hope it's OK to refer to that here.
Obviously there may still be conflicts if two plugins expect a certain Redmine method they are changing to behave in the way it does in stock Redmine, but removing method aliasing and not trying to monkey patch already existing helpers in my experience helps to avoid many problems.
Update: Most of the other methods in that particular module you linked to actually do not belong there, but could for example be mixed into the Issue
model (like sla_has_grandson_issues?
or declared as methods in the top level plugin namespace (all the settings and Redmine version related stuff - eg RedmineSubtaskListAccordion.tree_render_34?
).