对于从Node.js转Ruby的人很可能会有和我一样的疑惑,为什么要有Bundler这个东西?Rubygems不够吗?

从Node.js到Ruby的包管理器

在Node的世界里,依赖管理是由npm来完成的。所有依赖信息都写在package.json里面之后,一个npm install就能安装所有的依赖,然后直接运行程序即可。简单方便。

那当然是方便了,因为Node出来的太晚了,包管理器已经很成熟,所以才成了现在的这个样子。

RubyGems登场

我们把时间倒回去回到Ruby的早期,其包管理器RubyGems被创造出来的时候。在当时,RubyGems的使命其实很简单,就是安装依赖包(Ruby的包有个专门的名字叫做gem)。具体来说是,指定一个包名和版本号,由包管理器来安装指定版本的包以及其所有依赖的包。(当然如果没有指定版本号那当然是默认安装最新的了)。

在这个用例上,npm install xxxxgem install xxxx的功能是一样的。

只不过在行为上有一点区别。npm默认装在当前目录(在设计的时候就考虑到这个功能),而gem装在系统目录(和pip一样,早期的包管理器都是装在全局的,Ruby的每个包都带有版本号)

在gem里,它的依赖信息都在写在xxx.gemspec文件里(注意不是Gemfile,Bundler的文件)。当你gem install的时候,gem会根据gemspec里面的依赖信息来获取对应的依赖包。

团队协作的大麻烦

有了gem命令以后,Ruby开发起来就确实方便多了。需要什么包就用gem下一个,然后查查文档就能用了。对于个人开发者来说很好用。

但是一旦有团队协作,问题就来了。现在假设你要接手开发一个Ruby项目,你手上有项目的全部源码。那么首先你要要做的第一步就是安装所有的依赖库,因为你至少要先跑起来嘛!

好,装吧。

npm install,不对哟,这里是Ruby世界。哦,那么gem install。对不起,你会得到一个错误告诉你必须至少指定一个gem名称!想想吧,gem是用来装单个gem的,而不能用来装一个项目的依赖。这主要是因为:

  • 绝大多数Ruby项目本身不是gem,所以没必要有.gemspec文件(顾名思义这个文件是用来描述gem的元信息的)

  • 由于没有.gemspec文件,也就无从记录依赖有哪些了。这导致gem install就算想装也不知道依赖的包是什么

当然,你可以说不是有源文件吗,里面不都记录了require了什么东西吗?理论上讲只要把里面require的东西全部装一遍不就好了吗?

可以是可以。但是真的这么做的话会发现另外一个问题:你安装的gem和作者当时用的gem很可能不是一个版本。因为使用默认的gem install安装的总是最新的gem。那么只要任何一个依赖的gem在其版本更新中变更了现有的API,那么你复现安装的项目几乎必然是有问题的。

Bundler comes and saves the world!

说起来,解决这个问题的方法很简单,只要在最初开发的时候用一个文件记录了项目里用了什么包什么版本就好了嘛!对的,Bundler就是干这个事的。

具体来说,先创建一个Gemfile文件,在里面写上你要的gem名称和版本,然后用bundler install命令就可以安装所有的依赖了。虽然比npm麻烦了一点,但还算过得去。

普通的项目这样就可以了。但是还有一种情况就是,如果团队开发的项目本身就是一个gem呢?这样就会同时存在.gemspec和Gemfile两个文件,一个记录gem的元信息,一个是bundler要用来安装项目依赖的文件。这样的话每添加一个gem依赖就要同时在两边加(而且不幸的是,这两个文件书写依赖的格式还不太一样,不能简单的复制粘贴)

有没有觉得哪里不对?是的,Gemfile包含的信息其实就是.gemspec的子集,换句话说Gemfile里有的东西.gemspec里也都有。Bundler也想到了这个问题,所以你只要在Gemfile里加上gemspec这一句,bundler在安装的时候就会自动到.gemspec里面去找依赖,因此Gemfile剩下的就什么也不用写了(当然第一行指定安装源的命令还是要写的)。

版本选择

依赖全都装完了,那我们是不是可以愉快地开始用了呢?还不行,还差一步。现在出现的一个问题是,我们requrie到的gem的版本还是有可能不对的。为什么?

最直接的理由:require不能直接指定版本号!所以当你require一个包的时候,即便你装了Gemfile中指定版本的gem,如果这个gem你的系统里有好几个不同的版本(可能是以前装的),Ruby怎么知道你要依赖哪个?于是只能默认require最新的那一个了。

这当然也不是缺陷,Ruby中Kernel有个方法叫做gem。在require之前调用这个方法可以指定之后require进来的gem要用哪个版本。这个调用在本质上就是找到安装的gem包,然后把它的地址加到$LOAD_PATH里面去(注意仅仅是修改了$LOAD_PATH,包本身还没有被require进来)。下次require这个包的时候,会首先搜索$LOAD_PATH,找到这个包后就加载。这就达到了挑选gem版本的目的。

但这显然不方便啊,require一个包之前还要指定一下版本?那我改了依赖版本怎么办?

Bundler提供了一个解决方案。在require你要的包之前加一句require 'bundler/setup'即可。

本质上说,require了bundler/setup的这个包的时候,bundler读取Gemfile文件,在你所有依赖的库上调用一下gem方法,以此来指定要用的gem的版本,把它们加到$LOAD_PATH上面去。之后你在require的时候就自然地使用了在Gemfie里指定的包了。你所要做的就是加一句require 'bundler/setup'而已。

总结

Bundler其实是一个项目依赖管理器,而不是一个包管理器。如果那只是想下一个gem,那么Rubygems足矣。如果你是想管理项目的依赖(gem自身也是一个项目),那么在大多数情况下使用bundler会方便得多。

为什么需要Bundler的更多相关文章

  1. vs合并压缩css,js插件——Bundler & Minifier

    之前做了一个大转盘的抽奖活动,因为比较火,部分用户反馈看不到页面的情况,我怀疑js加载请求过慢导致,所以今天针对之前的一个页面进行调试优化. 首先想到的是对页面的js和css进行压缩优化,百度了下vs ...

  2. 避免每次输入bundler Exec命令

    bundle在ruby的世界里是个好东西,它可以用来管理应用程序的依赖库.它能自动的下载和安装指定的gem,也可以随时更新指定的gem. rvm则是一个命令行工具,能帮助你轻松的安装,管理多个ruby ...

  3. brew,gem,rvm 和 bundler软件包的管理工具

    brew是OS X上提供软件包的管理.Homebrew将软件包安装到单独的目录,然后符号链接到/usr/local 中,完全基于git和ruby.使用gem来安装你的gems,用brew来搞定他们的依 ...

  4. linux安装ruby ruby-devel rubygems bundler

    linux安装ruby ruby-devel rubygems yum install ruby ruby-devel rubygems 安装bundler gem install bundleror ...

  5. gem install bundler

    http://stackoverflow.com/questions/7483515/rake-aborted-no-such-file-to-load-bundler-setup-rails-3-1 ...

  6. 在CYGWIN下编译和运行软件Bundler ,以及PMVS,CMVS的编译与使用

    本人按照 http://blog.csdn.net/zzzblog/article/details/17166869 http://oliver.zheng.blog.163.com/blog/sta ...

  7. Laravel 5.2 INSTALL- node's npm and ruby's bundler.

    https://getcomposer.org/doc/00-intro.md Introduction# Composer is a tool for dependency management i ...

  8. rails 部署 can't find gem bundler (>= 0.a) with executable bundle

    多方寻找终得果,先感谢原作者,原作者博文 原因是本地项目bundler 和 服务器 bundler 版本不一致导致,项目是在本地建立,故Gemfile.lock最后一行BUNDLED WITH中是1. ...

  9. bundler简介(ruby gem)

    簡介 Bundler   RubyGem 是包裝.散佈Ruby程式庫的標準方式,相關文件可以參考 RubyGems Guides 的說明,或是 簡介 plugins 中的第二個例子.在使用rails ...

随机推荐

  1. Hibernate Id Generator and Primary Key

    Use automate id by hibernate: If you want the tables' id be created automation. How to do it? When u ...

  2. ubuntu vps折腾记

    买了burgetVM的vps,512M内存/1024M交换内存,40G硬盘,2TB流量/月,cpu xeon E5-2620 操作系统选择了ubuntu 12,开始折腾. 第一步,配置vpn 找了很多 ...

  3. 让Entity Framework启动不再效验__MigrationHistory表

    Entity Framework中DbContext首次加载OnModelCreating会检查__MigrationHistory表,作为使用Code Frist编程模式,而实际先有数据库时,这种检 ...

  4. RCP:利用actionSet在菜单(menu)里添加内容

    eclipse的菜单menu.工具栏toolbar乃至视图的上下文菜单contextmenu都是使用Action或Command实现的. Action即是 org.eclipse.jface.acti ...

  5. 使用OAuth打造webapi认证服务供自己的客户端使用(二)

    在上一篇”使用OAuth打造webapi认证服务供自己的客户端使用“的文章中我们实现了一个采用了OAuth流程3-密码模式(resource owner password credentials)的W ...

  6. 《C#图解教程》读书笔记之六:接口和转换

    本篇已收录至<C#图解教程>读书笔记目录贴,点击访问该目录可获取更多内容. 一.接口那点事儿 (1)什么是接口? 一组函数成员而未实现的引用类型.只有类和结构能实现接口. (2)从ICom ...

  7. zendframework 2 链接数据库

    相对于zf1,来说,zf2让我们对于数据库这方面的操作我的个人感觉是对于字段起别名简单了,但是对数据库的操作虽然配置写好的就基本不需要动了,但是还是比1的配置要繁琐, 还是那句话,大家可以去看看源码. ...

  8. HttpContext.Current:异步模式下的疑似陷阱之源

    最近园子里首页有好几篇文章都是讲异步编程的,尤其是几篇讲博客园自身的异步化建设的文章,看了以后很有收获. 闲暇之余再重新查查资料温故知新学习一遍,重新认识了SynchronizationContext ...

  9. 虚拟化平台cloudstack(8)——从UI开始

    UI ucloudstack采用的是前后端分离的架构,就是说前端可以选择使用web.swing甚至其它的界面,都可以. 我们来看cloudstack的UI信息吧,所有的cloudstack的UI都在{ ...

  10. [备忘]删除SQL Server中无登录名的用户

    这个问题通常会在还原虚拟主机的备份SQL文件后发生,原先在虚拟主机上的用户会被还原到本地,但是本地没有权限对其进行操作. SELECT N'ALTER AUTHORIZATION ON SCHEMA: ...