冲突的 Ruby 宝石

7

我需要在我的项目中使用两个宝石,它们都声称拥有PDF命名空间:pdf-reader和htmldoc。

有没有办法让它们一起很好地运行?我能想到的唯一方法是重写自己的版本htmldoc,以给它一个不同的命名空间。


+1 好问题。这是一个棘手的问题,我没有简单的解决方法。这引出了一个问题,为什么你需要同时阅读PDF,但我相信你有你自己的原因。 - EnabrenTane
它会给出什么错误信息? - Andrew Grimm
1
鉴于您可以在本地构建和安装自己的宝石库,可能可以进行重写。 - Andrew Grimm
@EnabrenTane:“begs the question” - 人们一直在使用这个短语。我认为它的意思不是你所想的那样。 - Andrew Grimm
1
我在 https://dev59.com/XUrSa4cB1Zd3GeqPVVJ2 和 http://stackoverflow.com/questions/2694333/ruby-is-already-using-the-class-name-of-my-model 上看到了类似的问题。 - Andrew Grimm
@EnabrenTane:这是因为我需要使用htmldoc gem来生成PDF文件,并且需要pdf-reader gem来确定生成的PDF文件中页面的数量。 - jemminger
4个回答

5
基本上,你无能为力。在 Ruby 中,使用顶层命名空间中的独特名称是一个好习惯,正是出于这个原因,你碰巧遇到了两个违反这种做法的库。
你可以做的一件事是使用 Kernel#load 而不是 Kernel#require。Kernel#load 接受一个可选的布尔参数,它将告诉它在匿名模块内评估文件。然而,请注意,这样做并不安全:完全有可能明确地将东西放在顶层命名空间中(使用类似 module ::PDF 的东西),从而打破匿名模块。
还要注意 API 真的很糟糕:load 只是像 require 一样简单地返回 true 或 false。实际上,由于 load 总是加载,它总是返回 true。没有办法真正获取匿名模块。你基本上必须手动从 ObjectSpace 中获取它。哦,当然,由于没有任何东西实际引用匿名模块,所以它会被垃圾回收,因此你不仅需要在 ObjectSpace 的深处搜寻模块,还需要与垃圾回收器竞争。
有时候,我希望 Ruby 有一个像 Newspeak、Standard ML 或 Racket 那样的适当的模块系统。

并非所有 Ruby 实现都拥有 ObjectSpace,对吗? - Andrew Grimm
1
@Andrew Grimm:我不确定IronRuby,但JRuby至少有相关的部分来执行ObjectSpace.each_object(Module),尽管它没有完整实现ObjectSpace - Jörg W Mittag

2

这个问题可能没有优雅的解决方案。如果您确实需要这两个宝石并存,我认为您最好的选择是fork其中之一(或可能都要fork)并使用您的fork。以下是我的做法:

  • If either gem is hosted on Github then fork it, or if both are on Github fork the one that seems to be the least work.
  • If neither gem is on Github, see if you can get hold of the source (grabbing it from the gem is a possibility, but finding the real repository might be helpful as there may be other files there that are not included in the gem), and put it in a repository on Github. Make sure the gem's license allows this (which it almost certainly does if it's one of the common open source licenses).
  • Make your changes.
  • Make sure there is a .gemspec file in the root of the repository, the next step will not work otherwise.
  • Use Bundler to manage your projects dependencies. Instead of specifying the dependency to the library you've modified as

    gem 'the_gem'
    

    specify it like this:

    gem 'the_gem', :git => 'git://github.com/you/the_gem.git'
    

    (but change the URL to the repository to the actual one)

  • Send an e-mail to the maintainer of the gem you modified and ask him or her to consider merging in your changes in the next release.

Bundler使得使用不同版本的gem变得非常容易,几乎没有麻烦。我经常fork一些gems,修复bug或者添加新功能,然后更改我的Gemfile指向我的版本,接着请求维护者合并我的变更。当维护者合并我的变更时(如果他们愿意),我只需将我的Gemfile改回到官方版本即可。
另一种策略是,如果维护者不想合并你的变更,而你又想分发你的版本给其他人,那么你可以将你的版本作为一个新的gem上传到Rubygems,但在这种情况下,请在gem名称前加上你的名字或其他字符串以标识你的gem是一个变体。

这是我决定走的路。原始项目在 GitHub 上:https://github.com/craigw/htmldoc,我进行了分支并创建了:https://github.com/jemminger/namespaced_htmldoc - jemminger

0

我听说过一种新的功能叫做“细化”。它旨在避免两个不同的猴子补丁影响同一个类而导致问题,但我想看看它是否能帮助解决你的问题。


来吧,甚至没有一个可行的原型实现细化。建议他看看这样的东西和建议他寻求仙女和独角兽的帮助一样有用。 - Theo
@Theo:根据这篇博客文章,只需要三行代码就可以安装refinements补丁。你能解释一下你的意思吗? - Andrew Grimm
可能需要解决一些实现细节。你是认真建议修补Ruby解释器作为解决方案吗? - Theo
我认真建议他检查一下这是否是可行的解决方案。 - Andrew Grimm

0

我在链接中回答了这个问题 https://stackoverflow.com/a/37311072/292780

对上面的答案表示不同意。以下是我的做法:

ruby -S gem list my_gem

`*** LOCAL GEMS ***
my_gem (1.0.1, 1.0.0, 0.0.2)
`

ruby -S gem lock my_gem-1.0.0 > locklist.rb

这个命令会生成一个特定版本的依赖列表到locklist文件中。

require 'rubygems'
gem 'my_gem', '= 1.0.0'
gem 'gem_base', '= 1.0.0'
gem 'rest-client', '= 1.7.2'
gem 'savon', '= 1.1.0'
gem 'addressable', '= 2.3.6'
gem 'mime-types', '= 1.25.1'
gem 'netrc', '= 0.11.0'

现在你可以执行 load('locklist.rb'),它会加载一个特定版本的gem和它所依赖的库。瞧,没有Bundler了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接