这篇文章只对那些使用blacklightruby gem/平台的人感兴趣,主要是我在图书馆/文化遗产部门的同事。
当我最近调查将我们的Rails应用从Blacklight更新到最新的7.19.2版本时,我遇到了很多废弃通知。它们与我的本地应用和一个试图覆盖Blacklight视图部分的插件的代码有关--特别是我遇到的代码中的 "约束"(即搜索限制/查询 "面包屑 "显示)区域,我不确定它是否适用于视图定制的更多区域。
经过更多的研究,我认为还没有完全解决如何使这些覆盖在未来的Blacklight 8中保持工作,这影响了包括blacklght_range_limit、blacklight_advanced_search、geoblacklight以及可能的spotlight等插件。 如果这些插件要在未来的Blacklight 8中更新保持工作,就需要找到一些解决方案。
我记录了我的发现/理解,以及一些前进的想法,希望它能帮助启动社区进程,找出解决方案来保持所有这些东西的工作。我可能没有得到所有的东西,也没有想到所有的东西,这只是为了帮助开始讨论,欢迎提出建议和纠正。
这确实很冗长,我希望你能发现它是有用的,如果不是全部感兴趣的话,可以跳过或略过。我相信弃用是从Blacklight 7.12(2020年10月发布)开始的。我相信Blacklight 7.14是第一个支持ruby 3.0的版本,所以任何想升级到ruby 3的人都会遇到这些问题。
背景介绍
在Blacklight存在的10多年里,定制Blacklight的特定部分是一个常见的用例,包括定制页面的某个部分显示的内容,而其他部分则保留 "原样"。一个单独的本地应用程序可以用它自己的自定义代码做到这一点;这也是许多共享的Blacklight "插件"/"扩展 "引擎宝石的常见做法。
Blacklight传统上以典型的Rails方式实现它的 "视图 "层,包括"辅助 "方法和视图模板。 本地应用程序或插件的定制和重写,是通过覆盖这些辅助方法和部分实现的。 这种传统的辅助方法和部分覆盖的方法仍然在Blacklight项目的维基中描述--它可能需要更新最近的废弃/新方法)。
这种视图/助手/覆盖的方法有一些优势。它只是使用标准的ruby和Rails,而不是自定义的Blacklight抽象;多个不同的插件可以覆盖同一个方法,只要它们都调用 "super",以合作增加功能;它非常灵活,允许覆盖 "只是工作"。
它也有一些严重的缺点。Rails的助手和视图通常以导致 "意大利面条 "或 "泥巴球 "代码而闻名,其中所有东西最终都依赖于其他所有东西,而且很难在不破坏东西的情况下进行修改。
在像Blacklight和它的生态系统这样的共享宝石代码的背景下,如果不知道什么是覆盖的公共API,就会变得更加混乱。Blacklight历史悠久,不同的维护者有不同的想法,不同的文档或机构对其意图的记忆会使其更加混乱。出于向后兼容和 "缺乏资源移除 "的原因,几代人的想法都会出现在当前的代码库中。这可能使我们很难在不破坏现有代码的情况下做出任何改变,这是我们在Blacklight中遇到的一个问题。
在Rails中出现的一个解决方案是ViewComponent gem(实际上是由github编写的),它有助于更好地封装、分离关注点,以及明确不同视图代码之间的界限。目前活跃的Blacklight维护者(我想主要来自斯坦福?这是对解决实际问题的一个值得欢迎的贡献! 此外,他们还做了一些坦率的、相当英勇的事情,使这种对ViewComponent的替换,作为一个临时的过渡步骤,非常向后兼容,甚至对现有的代码做了大量的辅助/部分覆盖,这是很难做到的,显示了他们对当前用户的关注。
通常,当我们看到弃用警告时,我们喜欢修复它们,让它们从我们的日志中消失,并为我们的应用程序在未来的版本中完全停止使用弃用行为做好准备。否则就会被认为是为未来留下了 "技术债务",因为弃用警告是在告诉你,代码最终将不得不被修改。
目前的挑战是,不清楚(至少对我来说)如何改变代码以在当前的黑光7.x和即将到来的黑光8x中仍然工作。这对于在当前的BL7中运行而不被废弃,以及对于代码在未来的BL8中继续工作的前景都是一个挑战。 我将用例子来解释。
Blacklight_range_limit(和geoblacklight),添加一个自定义的 "约束"
blacklight_range_limit为范围限制过滤器引入了新的查询参数,以前没有被Blacklight识别,这些参数看起来像&range[year_facet][begin]=1910 。除了让这些影响实际的Solr搜索,它还需要在搜索结果上方的 "约束 "区域显示这个限制(Blacklight核心是忽略的)。
为了做到这一点,它重写了Blacklight的render_constraints_filters辅助方法,通过一些花哨的代码,有效地调用super来渲染普通的Blacklight约束过滤器,但随后又加入了只有blacklight_range_limit知道的约束条件的渲染。 这种 "覆盖,调用超级,但添加 "的方法的一个好处是,多个附加组件可以这样做,而且它们不会相互干扰--只要它们都调用超级,而且只想添加额外的内容,而不是替换预先存在的内容。
但在最近的Blacklight 7.x中,重写这个辅助方法已被废弃。如果Blacklight检测到任何重写这个方法(以及其他与约束有关的方法),它将发出废弃通知,并切换到视图渲染的 "传统 "模式,所以重写仍将工作。
好吧,如果我们想改变blacklight_range_limit的做法,以避免触发弃用警告,并让blacklight继续使用 "新的"(而不是 "传统的")逻辑,这将是它在Blacklight 8中坚持使用的逻辑,那该怎么办?
新的逻辑是用新的view_component渲染,Blacklight::ConstraintsComponent.new 。这是在catalog/_constraints.html.erb 部分渲染的。 我想如果我们想让渲染在那个新系统中表现得不同,我们需要引入一个新的视图组件,它和Blacklight::ConstraintsComponent ,但表现得不同(也许是一个子类,或者一个使用委托的类)。 或者,嘿,那个组件需要一些依赖性的view_components作为args,也许我们只需要让ConstraintsComponent被赋予一个不同版本的_component args的arg,不确定这是否能做到。
写一个这些组件的新版本是很容易的......但我们如何让Blacklight使用它?
我想我们必须重写catalog/_constraints.html.erb 。但这并不令人满意:
- 我想我们是想摆脱覆盖参数的做法,但即使在这种情况下也可以......
- 对于一个引擎 gem 来说,覆盖参数是很困难的,也很容易出错,你需要确保它在 Rails 的模板 "查找路径 "中以正确的顺序结束,但即使你这样做...
- 如果多个东西想在 "约束 "区域上增加一个部分,怎么办?只有一个可以覆盖这个部分,没有办法让一个部分调用
super。
因此,也许我们需要要求本地应用程序覆盖catalog/_constraints.html.erb (或生成代码到其中),该代码调用我们的备用组件,或调用具有备用依赖性参数的库存组件:
- 这看起来已经比我们之前做的更简单的单方法覆盖要复杂和脆弱一些了,我们必须在_constraints.html.erb中复制和粘贴目前并不复杂的实现,但即使我们不担心这个问题....
- 同样,如果有多个不同的东西想加入到 "约束 "区域的内容中,会发生什么?
- 如果有多个地方需要渲染约束条件,包括其他自定义代码,该怎么办?(下面有更多关于这个问题的内容)。他们都需要用这个越来越复杂的代码进行相同的定制?
多样东西可能想加进去并不只是理论上的,geoblacklight也想在'约束'区域添加一些东西,也是通过覆盖render_constraints_filters方法来实现 的。
实际上,如果我们只是在现有的内容上添加......我想本地应用程序可以覆盖catalog/_constraints.html.erb ,复制现有的blacklight实现,然后只是在END上添加一个调用,比如说<%= render(BlacklightRangeLimit::RangeConstraintsComponent %> ,然后还有<%= <%= render(GeoBlacklight::GeoConstraintsComponent) %>......这实际上可以工作......但它似乎很脆弱,特别是当我们开始处理 "生成器",在本地应用程序中自动创建这些插件的CI,正如blacklight插件那样?
我的本地应用程序(和blacklight_advanced_search)。改变 "查询 "约束的方式
如果你只是输入查询 "猫","通用",Blacklight就会在搜索顶部的一个简单的框中以一种 "面包屑 "约束的方式显示你的搜索。
我的本地应用程序(除了改变风格外)将其改为可编辑的形式,以改变你的查询(同时保持其他面等过滤器完全相同)。 这是一个伟大的用户体验吗?不确定!但这是目前发生的情况。但这就是现在的情况。
它是通过覆盖`render_constraints_query`而不是调用super来实现的,用我自己的实现取代标准的实现。
我们如何用新的非弃用的方式来做这个?
我猜我们又得用新的自定义版本替换Blacklight::ConstraintsComponent......或者也许传递一个自定义组件给query_constraint_component......这次我们不能只是渲染和添加,我们确实需要替换一些东西。
我们有什么选择呢?也许,再次定制_constraints.html.erb ,以调用该自定义组件和/或自定义参数。并确保任何定制与任何定制是一致的,例如blacklight_range_limit或geoblacklight,确保他们不是都在试图提供相互不兼容的定制组件。
我还是不喜欢:
- 必须覆盖一个视图局部(以前我只覆盖一个辅助方法),在本地应用而不是插件中更可行,但我们仍然必须从Blacklight复制和粘贴一些非琐碎的代码到我们的本地覆盖,并希望它不会改变。
- 如果我们对它进行子类化或委托,对
Blacklight::ConstraintsComponent的实现相当敏感。我不确定哪些部分被认为是公共API,或者它们的变化频率......如果我们不小心,我们不会有比旧方法下更稳定/可靠/向前兼容的代码。 - 这个解决方案并没有为自定义代码提供一种方法来渲染一个带有任何附加组件所添加的所有自定义功能的约束区域,这是一个当前的用例,见下一节。
原来blacklight_advanced_search也定制了 "查询约束"(为了处理该插件可以做的多字段查询),也是通过覆盖render_constraints_query ,所以这个确切的用例也影响到了该插件,在插件而不是本地应用中会有一些挑战。
我不认为我们集思广益的这些解决方案有任何一个是合适和可靠的。
但是呼出黑光功能块也是如此,如spotlight....
除了重写辅助方法来定制屏幕上出现的内容外,传统上本地应用程序或插件中的自定义逻辑可以调用辅助方法来在屏幕上渲染Blacklight的某些功能。
例如,聚光灯插件在它自己的一个视图中调用render_constraints方法,以在它自己的一个自定义页面中包含整个 "约束 "区域。
使用传统的辅助方法架构,spotlight可以渲染约束条件*,包括*本地应用程序或其他插件通过覆盖辅助方法所做的任何定制。例如,当spotlight调用render_constraints ,它也会得到由blacklight_range_limit或geoblacklight添加的额外约束。
Spotlight如何使用新的架构来渲染约束? 我猜它会直接调用Blacklight view_component,render(Blacklight::ConstraintsComponent.new(... 。但是它如何管理使用任何由插件(如blacklight_range_limit)添加的自定义功能?不确定。我们在上面集思广益的解决方案似乎都不能让我们达到目的。
我想(Eg)spotlight实际上可以渲染constraints.html.erb部分,这成为约束条件渲染的标准 "API",在本地应用程序中定制,并在每次需要约束条件视图时重新使用?这可能是可行的,但对我来说,把视图部分作为API似乎是一种退步,我觉得我们试图摆脱这种做法是有充分理由的,只是感觉很乱。
这让我觉得黑光可能需要新的API,如果我们不想在黑光8中减少 "视图扩展 "的功能(这是另一个选择,说,好吧,你就是不能再做这些事情了,大大削减了插件的可能范围,可能会放弃一些插件)。
还有其他一些情况,例如blacklight_range_limit调用辅助方法来重新使用功能。我还没有完全分析它们。有可能在某些情况下,插件只是应该复制和粘贴硬编码的HTML或逻辑,而不允许其他角色定制它们。这里的blacklight_range_limit调用的例子包括
新的API?依赖性注入?
可能有一些新的API,Blacklight可以实现,使这一切工作更顺畅、更一致?
"如果我们想要一种方法来告诉Blacklight "使用我自己的自定义组件而不是Blacklight::ConstraintsComponent",最好是不需要覆盖视图模板,起初这让我想到 "依赖注入的反转控制"?我对这种通用的解决方案并不感到兴奋,但通过思考....
如果有一些方法可以让本地应用或插件做Blacklight::ViewComponentRegistration.constraints_component_class = MyConstraintsComponent ,然后当blacklight想调用它时,而不是像现在这样,做<%= render(Blacklight::ConstraintsComponent.new(search_state: stuff) %> ,它会做一些事情,比如。`<%=Blacklight::ViewComponentRegistration.constraints_component_class.new(search_state: stuff) %> 。
这让我们可以 "注入 "一个自定义类,而不必覆盖视图组件和其他所有可能用到它的地方,包括来自插件的新地方等。 该组件的具体参数必须被视为/处理为公共API。
但它仍然不允许多个附加组件合作添加一个新的约束项目。我想,要做到这一点,注册表可以为每个东西有一个数组....
Blacklight::ViewComponentRegistration.constraints_component_classes = [
Blacklight::ConstraintsComponent,
BlacklightRangeLimit::ConstraintsComponent,
GeoBlacklight::ConstraintsComponent
]
# And then I guess we really need a convenience method for calling
# ALL of them in a row and concatenating their results....
Blacklight::ViewComponentRegistration.render(:constraints_component_class, search_state: stuff)
从好的方面看,现在像spotlight这样的东西也可以调用它来渲染一个 "约束区域",包括来自BlacklightRangeLimit、GeoBlacklight等的定制。
但我对这一点的感觉很复杂,它似乎是那种通用的但又是更多的自定义抽象的东西,有时会让我们陷入麻烦和过度复杂化。不确定。
API只是为了约束视图的定制?
好吧,与其试图做一个通用的API来定制 "任何视图组件",不如我们只关注我们面前的实际用例如何?到目前为止,我遇到的所有案例都是关于 "约束 "领域的? 我们能不能只为这个添加自定义API?
它可能看起来与上面的通用 "IoC "解决方案几乎完全一样,但在Blacklight::ConstraintsComponent类....比如,我们想定制Blacklight::ConstraintsComponent用来渲染 "查询约束 "的组件(对于我的本地应用程序和高级搜索用例),现在我们必须改变Blacklight::ConstraintsComponent.new的每一个地方的调用站点,以便有一个不同的参数......如果我们可以改为只:
Blacklight::ConstraintsComponent.query_constraint_component =
BlacklightAdvancedSearch::QueryConstraintComponent
好的,对于这些我们想要添加的 "额外约束项目"......在 "传统 "架构中,我们覆盖 "render_constraints_filters"(通常用于面约束)并调用super......但那只是因为那是我们的东西,实际上这是一个不同的语义,让我们把它称为它是什么:
Blacklight::ConstraintsComponent.additional_render_components <<
BlacklightRangeLimit::RangeFacetConstraintComponent
Blacklight::ConstraintsComponent.additional_render_components <<
GeoBlacklight::GeoConstraintsComponent
所有这些组件的 "槽 "仍然需要以某种方式将它们的初始化参数建立为 "公共API",所以你可以在知道它的初始化器会得到什么参数的情况下注册一个。
注意这也解决了聚光灯的问题,聚光灯可以简单地调用render Blacklight::ConstraintsComponent(... ,而且它现在确实得到了其他附加组件添加的定制,因为它们是用Blacklight::ConstraintsComponent注册的。
我认为这个API可以满足我所确定的所有用例? 这并不意味着没有一些我没有发现的。我并不确定什么架构是最好的,我只是训练了一下头脑风暴的可能性。仔细选择会很好,因为我们最好能找到能在未来许多黑光版本中工作而不必再被废弃的东西。
需要协调切换到非弃用的技术
Blacklight对约束条件渲染实现向后兼容支持的方式是,如果它检测到应用程序中的任何东西覆盖了相关的方法或部分,它就会继续用帮助器和部分的 "传统 "方式进行渲染。
因此,如果我试图升级我的应用程序,使用一个新的非废除的方法做一些事情,而我的应用程序仍然使用blacklight_range_limit以旧的方式做事情......这将很难使它们同时工作。如果你有一个以上的Blacklight插件覆盖相关的视图助手,当然会变得更加复杂。
它几乎必须是全有或全无的。这也使得blacklight_range_limit在发布时很难使用新的方式(如果我们找到了一个)--它可能只在那些已经把所有部件都改成新方式的应用程序中工作。 我想所有的插件都可以做发布,为你提供配置/安装说明的选择,主程序可以选择新方法或旧方法。
我认为这个问题的复杂性使得它更加现实,特别是基于实际的黑光社区维护资源,很多应用程序将继续在过时的模式下运行,很多插件只能触发过时的警告,直到黑光8.0出来,过时的行为就会被打破,然后我们就需要所有插件的黑光8专用版本,应用程序一下子把所有东西都换过来。
如果不同的插件以一种不协调的方式来处理这个问题,每个插件都试图投资一种方式来做到这一点,他们真的有可能踩到对方的脚趾,并且彼此不兼容。我认为必须要有一些东西作为Blacklgiht推荐的视图覆盖的共识/最佳实践方法,以便每个人都能以一致和兼容的方式使用它。无论这是否需要黑光中尚未出现的新API,还是需要与当前黑光7版本中的内容形成清晰的模式。
理想的情况是,在Blacklight 8发布之前,由目前活跃的Blacklight维护者和/或社区来解决所有问题,这样人们至少知道需要做什么来更新代码。 许多黑光用户可能根本还没有使用黑光7.x(2018年12月发布的7.0)--例如hyrax仍然使用黑光6--所以我不确定社区的哪一部分人已经意识到这是即将到来的。
我希望我花在调查、考虑和记录这篇文章上的时间能够对社区有所帮助,作为了解土地的一个初步步骤。
现在,沉默的去势?
好吧,我真的想从现在的7.7.0升级到最新的Blacklight 7.19.2。只是为了保持最新,并为ruby 3.0做好准备。(我的应用在BL 7.7下无法通过ruby 3的测试;看起来BL在BL 7.14.0中增加了对ruby 3.0的支持?这确实已经有了弃用的内容)。
现在要消除所有废弃的调用是不可行的。但我的应用程序似乎可以正常工作,只是有废弃的调用。
我真的不想在我的CI和生产日志中留下所有这些 "暂时不考虑它们 "的弃用信息。它们只会让事情变得杂乱无章,让我很难注意到我想注意的事情。
我们能让它们安静下来吗? Blacklight使用deprecation gem来处理它的废弃信息;该gem是由cbeer提供的,其逻辑来自于ActiveSupport。
我们可以用Deprecation.silence do....,包括对blacklight_range_limit做一个PR来做所有对废弃方法的调用? 我不确定我是否喜欢让blacklight_range_limit对这个问题保持沉默的想法,在这一点上它需要更多的关注!另外,我不确定如何使用Deprecation.silence来实现_constraints.html.erb模板中的那个巧妙的条件检查。
我们可以用Deprecation.default_deprecation_behavior ,将deprecation gem中的所有东西都消音--我不喜欢这样,我们可能会错过我们想要的删减内容?
Deprecation gem的API让我觉得可能有办法用Blacklight::RenderConstraintsHelperBehavior.deprecation_behavior = :silence 等东西来消除个别类的deprecation警告,但我想我误解了API,Blacklight中似乎并没有这样的方法来有针对性地消除我想要的东西。
在Deprecation gem API中查看/脑筋急转弯......我可以*改变它的行为,改为它的"通知 "策略,发送ActiveSupport::Notification事件,而不是写到stdout/log......然后写一个自定义的ActiveSupport:然后写一个自定义的ActiveSupport::Notification订阅器,忽略我想忽略的事件......最好还是在test/rspec环境中保持未记录但被注意到的、受欢迎的默认行为,它在最后会以某种方式报告出删减的摘要......
这似乎太费事了。我意识到,在我的项目中,唯一使用Deprecation gem的是Blacklight本身和qa gem(我不认为它在blacklight/samvera社区之外已经流行起来),我想我愿意从所有这些地方沉默deprecations,尽管我不喜欢它。


