如何让Gem::Specification.find_by_name返回正确的Ruby Gem版本
解决方案:获取当前激活的Gem规格而非全局查找
这个问题我之前也碰到过,核心原因是Bundler和RubyGems处理版本锁定的逻辑差异:
- 用
bundle exec运行时,Bundler会强制整个环境使用Gemfile.lock里的版本,所以Gem::Specification.find_by_name会乖乖返回锁定的0.0.1版本。 - 但脱离Bundler用
my_exe _0.0.1_(或gem _0.0.1_ my_exe)运行时,RubyGems只是把指定版本的gem加入加载路径,但find_by_name默认会遍历所有已安装的gem,返回最新的匹配版本,完全忽略你在命令行指定的版本。
下面是两种可靠的解决方法:
方法一:直接使用当前激活的Gem规格(推荐)
RubyGems在激活指定版本的gem时,会把它的规格信息存入Gem.loaded_specs哈希表,这个表里的就是当前进程正在使用的gem版本。直接替换你的代码:
# 原来的代码(有问题) spec = Gem::Specification.find_by_name("my_gem") # 替换成这个 spec = Gem.loaded_specs["my_gem"]
这样不管你是通过bundle exec还是RubyGems版本指定符运行,都能拿到当前正在使用的0.0.1版本的规格信息。
方法二:给find_by_name指定版本约束
如果你一定要保留find_by_name的调用,可以从当前激活的规格里获取版本,再作为约束传入:
current_version = Gem.loaded_specs["my_gem"].version spec = Gem::Specification.find_by_name("my_gem", "= #{current_version}")
或者如果你能从命令行参数里解析出指定的版本(比如用户输入的_0.0.1_),也可以直接用那个版本字符串作为约束,但这种方法不如第一种可靠,因为RubyGems可能已经处理了版本激活,loaded_specs里的信息是最准确的。
为什么这能解决问题?
当你用my_exe _0.0.1_运行时,RubyGems会自动激活0.0.1版本的my_gem,并把它的规格加入Gem.loaded_specs。而find_by_name默认不考虑当前激活的版本,只会全局搜索最新的符合条件的gem,所以必须明确指定版本约束或者直接使用loaded_specs。
内容的提问来源于stack exchange,提问作者CodeSmith
相关产品推荐
相关产品推荐

