ruby - 改进 hacky ruby 'method_alias' 修复冲突的 redmine 插件?
问题描述
使用 redmine 3.x,我在两个插件之间存在依赖冲突 - redmine subtask list Accordion和Subtask list inherit fields。尝试查看问题时,安装两者都会引发 500 错误。
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'
经过大量试验和错误后,我们通过在第一个插件的原始源代码中添加以下内容来解决问题:
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?
这是原始代码:
其中有来自上述源代码的前两个alias_method调用。
通过为原始类中的每个方法创建一个具有相同名称的别名方法,代码可以正常工作。然而,这似乎是一个 hacky 修复,我不明白它为什么会起作用。有人可以解释为什么修复有效以及如何正确重写它吗?
解决方案
alias_method
在调用它的上下文中创建命名方法的副本。
现在,如果原始方法( 的第二个参数alias_method
)在继承链的某处定义并在以后以任何方式更改,那么您仍然拥有未更改的副本。这可以解释你看到的行为。
至于重写:根据经验,丢弃所有方法别名(在两个插件中)并使用Module#prepend
. 这个 SO Answer很好地概述了(猴子)修补 Ruby 代码的好坏技术。
由于 Rails 处理它们的方式(结果可能会因代码加载顺序而异),因此在插件中修补 Rails 的视图助手可能会很棘手。尽可能避免使用它或创建新的辅助模块并使用类似的方法将它们添加到相关控制器中
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
不久前,我碰巧写了一篇关于这件事的博客文章。希望在这里引用它是可以的。
显然,如果两个插件期望某个 Redmine 方法改变它们的行为方式与它在股票 Redmine 中的行为方式相同,则可能仍然存在冲突,但是根据我的经验,删除方法别名并且不尝试修补现有的帮助程序有助于避免许多问题。
更新:您链接到的特定模块中的大多数其他方法实际上不属于那里,但可以例如混合到Issue
模型中(例如sla_has_grandson_issues?
或声明为顶级插件命名空间中的方法(所有设置和 Redmine 版本相关的东西- 例如RedmineSubtaskListAccordion.tree_render_34?
)。