- It’s the canonical package manager for Ruby
- Works hand-in-hand with RubyGems
- Bundler + RubyGems is kind of like:
- Virtualenv + Pip for Python
- NPM for NodeJS
- Leiningen for Clojure
A) 3,921 B) 11,239 C) 34,980 X D) 121,120
A) 79 B) 492 X C) 733 D) 1,214
/dice dice.gemspec /lib dice.rb
# /dice/dice.gemspec
Gem::Specification.new do |s|
s.name = "dice" # required
s.summary = "I'm a test gem" # required
s.author = "Parker" # required
s.version = "0.0.0" # required
s.files = ["lib/dice.rb"]
s.add_development_dependency "pry", "0.5.0"
s.add_runtime_dependency "andand"
end
# /dice/lib/dice.rb
class Dice; def self.roll; rand(1..6); end; end
Use gem build
to create a packaged .gem
file.
E.g. $ gem build /dice/dice.gemspec
gives us:
/dice dice-0.0.0.gem dice.gemspec /lib dice.rb
$ gem install dice
installs the gem and any needed
runtime dependencies:
/gems/2.3.0 /cache dice-0.0.0.gem andand-1.3.3.gem /gems/dice-0.0.0 /lib dice.rb /specifications dice-0.0.0.gemspec andand-1.3.3.gemspec /gems/andand-1.3.3 ... contents of andand ... /doc ... documentation ...
And now you have access to it:
$ irb irb> require 'dice' => true irb> Dice.roll => 3
RubyGems gives us the ability to create and install gems.
But, what we’ve seen so far has been at a pretty global level -
gem install
impacts an entire Ruby installation.
Bundler enables project-level gem management, primary through two commands:
bundle install
- Install some gems specified for your projectbundle exec
- Run a Ruby process using specified gems
# /my_project/Gemfile
# Set the source for Bundler to look for gems
source "https://rubygems.org"
gem "require_all"
In addition to installing any needed gems, running bundle install
creates a new file Gemfile.lock
:
/my_project Gemfile Gemfile.lock
GEM remote: https://rubygems.org/ specs: require_all (1.3.3) PLATFORMS ruby DEPENDENCIES require_all BUNDLED WITH 1.12.4
# /my_project/Gemfile
source "https://rubygems.org"
gem "require_all" # Will use most recent version from source
gem "require_all", "1.2.0" # Will only use that exact version
gem "require_all", ">=1.2.0" # >, <, >=, <= are all supported
gem "require_all", "=1.2.0" # and = for that matter
gem "require_all", ">=1.2.1", "<1.3.0"
gem "require_all", "~>1.2.1" # same as last line
gem "require_all", "<=1.3.0", ">1.2.1"
gem "require_all", "<=1.3.0", ">1.2.1", ">1.2.0" # sure
gem "rails", "3.0.0.beta3"
Running bundle install
with no Gemfile.lock
gets the highest available version that meet your criteria, and saves that version information, along with the source it was downloaded from, to a new Gemfile.lock
.
Running bundle install
when there is a Gemfile.lock
already adds new gems to the Gemfile.lock
but does not automatically upgrade versions if new ones have been available.
An example:
- Your
Gemfile
looks like below, and you have noGemfile.lock
yet:# /my_project/Gemfile source "https://rubygems.org" gem "andand", ">= 1.0.0"
- You run
bundle install
, which creates the following, since the most recent version of theandand
gem on https://rubygems.org is 1.2.0:# /my_project/Gemfile.lock GEM remote: https://rubygems.org/ specs: andand (1.2.0) ...
- Suppose
andand
’s author(s) pushes a new version, 1.2.1, to https://rubygems.org after you ranbundle install
- If you run
bundle install
again, nothing will be updated in yourGemfile.lock
, you won’t install version 1.2.1,bundle exec
will still useandand v1.2.0
, you won’t pass go and collect $200, etc. - To get the new version, you have to run either
bundle update
or justbundle update andand
(if you want to limit your update to justandand
), which will install the new version and update yourGemfile.lock
- And of course,
bundle update
only works because of the open-ended requirement in theGemfile
(">= 1.0.0"
). Had the requirement been">= 1.0.0", "< 1.2.1"
,bundle update
would never update the gem past “1.2.0”.
- MAJOR.MINOR.PATCH
- From 1.0.0…
- 1.0.1 for a patch with no impact on API
- 1.1.0 if you added features w/o breaking existing API
- 2.0.0 if users are going to have to update their code
- ”~> 1.2.3” …is the same as… “>= 1.2.3”, “< 1.3”
- ”~> 1.2” …is the same as… “>= 1.2”, “< 2.0”
- ”~> 1” …is the same as… not really getting this are we?
Groups determine what gets required by Bundler.require
,
e.g. Bundler.require(:default, :production)
Note: Bundler.require
is the same as Bundler.require(:default)
# /my_project/Gemfile
gem "pry" # this is in group :default, you know, by default
group :development, :test do
gem "rspec"
end
gem "rack-cors", "~> 0.2.9", require: "rack/cors", group: :production
gem "sqlite", require: false
puts "Gemfile is just Ruby code, btw"
# /my_project/Gemfile
source "https://rubygems.org" # will check this second
source "https://ruby.taobao.org" # will check this first
ruby "2.1.5" # unrelated to sources, but worth mentioning
gem "rspec" # will use ruby.taobao.org
gem "pry", source: "https://rubygems.org" # will use rubygems.org
# You can also use a block format like this:
source "https://rubygems.org" do
gem "rails"
gem "sqlite"
end
Notes:
- source order was behaving differently with localhost
- don’t know how it would behave if one has > version
# /my_project/Gemfile
ruby '1.9.3', :engine => 'jruby', :engine_version => '1.6.7'
gem "dice", path: "local_gems/dice"
gem "dice", path: "~/my_project/local_gems/dice"
path "./local_gems/dice" do
gem "dice"
end
gem "rails", git: "https://github.com/rails/rails.git", branch: "2-3-stable"
gem "rails", git: "git://github.com/rails/rails.git", ref: "4aded"
gem "rails", github: "rails", tag: "v3.0.0"
# etc
From the docs: “This command executes the command, making all gems specified in the Gemfile available to require in Ruby programs.”
Some further notes:
- It doesn’t just use the Gemfile
- It scans the Gemfile to ensure the Gemfile.lock is still valid
- But then it also uses the Gemfile.lock to get exact versions
- You can see this by trying
bundle exec
before bundling
- Beyond just providing the gems, it also guards against gems that might be installed but are not required
Pre-reqs:
- Pry is installed
- There is a Gemfile and Gemfile.lock, but Pry not in them
# /my_project/w_setup.rb
require 'bundler/setup'
require 'pry'
# /my_project/wo_setup.rb
require 'pry'
$ ruby w_setup.rb #=> error $ ruby wo_setup.rb #=> no error $ bundle exec ruby wo_setup.rb #=> error
You can be more specific with Bundler.setup
# /my_project/w_setup.rb
require 'bundler'
Bundler.setup(:default)
require 'pry'
- Install our gem using local gem server
- Install from a different source https://ruby.taobao.org
- Bump version and use our gem from path
- Bump version and install our gem from public github repo
- Bump version and install our gem from private github repo
- Create a new gem that conflicts with our
require
statement - Try to install gems with conflicting dependencies
- Specify two sources, where the 1st has a more recent version
Notes:
- Installations from source use the
.gem
file - Installations from path / git:
- Use
.gemspec
(not.gem
) and are annotated with a!
in theGemfile.lock
- Don’t handle interconnected dependencies
- Use
- Installations from git:
- Install to a different directory
- Pass github credentials in like BUNDLE_GITHUB__COM=”username:password”
bundle show
- Show the path to the “bundled” version of a gembundle binstubs
- Provide wrappers for executables likerake
, which ensure the bundled version is usedbundle package
- Create project-local copies of gemsbundle update
- Update gem dependencies to latest versionsbundle check
- Check if all required gems are installedbundle outdated
- List bundled gems with updates availablebundle clean
- Remove unused gemsbundle init
- Create example Gemfilebundle gem
- Create gem skeletonbundle config
- Show Bundler configurationbundle console
- Alias forbundle exec irb
bundle viz
- Create a gem dependency mapbundle help
- Helpbundle inject
- Add a gem to the Gemfilebundle open
- Open gem directory in default editorbundle version
- Show Bundler versionbundle platform
- Show bundled platform specs
Not much magic, really…
# /config/boot.rb
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' # Set up gems listed in the Gemfile.
# /config/application
Bundler.require(*Rails.groups)
Note: Rails.groups
gets set via ENV["RACK_ENV"]
Gemfile order shouldn’t matter, but we’ve all probably run into situations where it sure seemed to. So, what’s going on?
- The order of gems in your Gemfile does determine in what order
Bundler.require
requires the gems - So, if there is a gem that refers to, say,
ActiveSupport
, or one of its provided methods like.try
, it can throw errors - This will only happen if the gem itself doesn’t explicitly have
require 'active_support'
, though - The lesson here is, when writing gems, you can’t just specify
requirements in your runtime dependencies. You must
require
.
# /my_project/thing_one.rb
require_relative "thing_two"
puts "one"
# /my_project/thing_two.rb
require_relative "thing_one"
puts "two"
What does $ ruby thing_one.rb
do?
# /my_project/thing_one.rb
puts "one - #{Time.now.to_f} - #{require_relative 'thing_two'}"
# /my_project/thing_two.rb
puts "two - #{Time.now.to_f} - #{require_relative 'thing_one'}"
$ ruby thing_one.rb one - 1463494329.522492 - false two - 1463494329.423543 - true one - 1463494329.348982 - true
- If you put this in your Gemfile, sometimes it will puts “no error” then “error” without breaking. So, I think they must have bare rescues.
if [true, false].sample puts "error" 1 / 0 else puts "no error" end
- Bundler seems to require things to be in
/lib
but not RubyGems