CrackedRuby logo

CrackedRuby

Bundler

Overview

Bundler manages Ruby gem dependencies through declarative specification and deterministic resolution. The core workflow centers on the Gemfile where developers declare gem requirements, and the Gemfile.lock file where Bundler records exact versions and dependencies.

The Bundler module provides programmatic access to dependency management, while the bundle command-line tool handles installation, updates, and environment setup. Bundler resolves complex dependency graphs, ensuring all gems work together without version conflicts.

# Gemfile
source 'https://rubygems.org'

gem 'rails', '~> 7.0'
gem 'pg', '>= 1.1'
gem 'puma', '~> 5.0'

group :development, :test do
  gem 'rspec-rails'
  gem 'factory_bot_rails'
end

Bundler creates isolated environments where applications run with exact gem versions. The Bundler.require method loads gems according to groups, while Bundler.with_clean_env provides access to system gems outside the bundle.

# Load gems for current environment
Bundler.require(:default, Rails.env)

# Access bundled gems programmatically
Bundler.load.specs.each do |spec|
  puts "#{spec.name}: #{spec.version}"
end

The dependency resolution algorithm examines all gem specifications, calculates compatible versions, and generates a solution that satisfies all constraints. Bundler maintains this solution in Gemfile.lock, ensuring identical environments across deployments.

Basic Usage

Create a Gemfile in the project root to declare dependencies. Bundler reads this file during installation and resolution operations.

# Gemfile
source 'https://rubygems.org'

# Exact version
gem 'rake', '13.0.6'

# Version constraints
gem 'minitest', '~> 5.16'
gem 'sqlite3', '>= 1.4', '< 2.0'

# Git repositories
gem 'rack', git: 'https://github.com/rack/rack.git'

# Local path
gem 'custom_gem', path: '../custom_gem'

# Groups for conditional loading
group :development do
  gem 'debug'
  gem 'rubocop'
end

group :test do
  gem 'capybara'
  gem 'selenium-webdriver'
end

group :production do
  gem 'unicorn'
end

Run bundle install to resolve dependencies and install gems. Bundler creates Gemfile.lock with exact versions and dependencies.

# Check current bundle status
bundle check

# Install missing gems
bundle install

# Update specific gems
bundle update rails

# Update all gems
bundle update

Execute commands within the bundle environment using bundle exec. This ensures the command runs with bundled gem versions rather than system gems.

# Run with bundled gems
bundle exec rake test
bundle exec rails server
bundle exec rspec

# Generate binstubs for common commands
bundle binstubs rake rspec rails
./bin/rake test  # Uses bundled version

Load gems programmatically in Ruby applications. The Bundler.require method loads gems based on current groups.

require 'bundler/setup'

# Load default group
Bundler.require

# Load specific groups
Bundler.require(:default, :development)

# Load for Rails environment
Bundler.require(:default, Rails.env.to_sym)

Configure Bundler behavior through command-line options or configuration files. Settings control installation locations, gem sources, and resolution behavior.

# Configure gem installation path
bundle config set --local path 'vendor/bundle'

# Exclude development gems in production
bundle config set --local without development test

# Use specific number of parallel jobs
bundle config set --local jobs 4

# Check current configuration
bundle config list

Error Handling & Debugging

Bundler raises specific exceptions for different failure scenarios. The Bundler::GemNotFound exception occurs when gems cannot be located in configured sources.

begin
  Bundler.require
rescue Bundler::GemNotFound => e
  puts "Missing gem: #{e.name}"
  puts "Available in groups: #{e.groups}"
  # Attempt recovery or exit gracefully
  exit 1
end

Version conflict resolution failures generate Bundler::VersionConflict exceptions. These include detailed information about conflicting requirements and potential solutions.

begin
  Bundler.definition.resolve_remotely!
rescue Bundler::VersionConflict => e
  puts "Dependency conflict:"
  puts e.message
  # Parse conflict details
  e.conflicts.each do |conflict|
    puts "Gem: #{conflict.name}"
    puts "Requirements: #{conflict.requirements.join(', ')}"
  end
end

Platform-specific gem installation failures raise Bundler::InstallError. These often occur with native extensions or missing system dependencies.

# Diagnose installation issues
begin
  Bundler::Installer.install(Bundler.root, Bundler.definition)
rescue Bundler::InstallError => e
  puts "Installation failed: #{e.message}"
  # Check for common issues
  if e.message.include?('native extension')
    puts "Install system dependencies for native gems"
  elsif e.message.include?('permission')
    puts "Check file permissions and user access"
  end
end

Debug dependency resolution using verbose output and diagnostic commands. The bundle show command reveals gem locations and versions.

# Show gem information
bundle show rails
bundle show --paths

# List all gems in bundle
bundle list

# Show dependency tree
bundle viz --format=png

# Check for security vulnerabilities
bundle audit check

Lock file corruption or inconsistency triggers Bundler::LockfileError. Recovery involves regenerating the lock file or resolving conflicts manually.

begin
  Bundler.definition.lock
rescue Bundler::LockfileError => e
  puts "Lockfile error: #{e.message}"
  # Common recovery steps
  File.delete('Gemfile.lock') if File.exist?('Gemfile.lock')
  system('bundle install')
end

Network connectivity issues during gem fetching raise Bundler::HTTPError. Implement retry logic and fallback sources for reliability.

retries = 3
begin
  Bundler::Source::Rubygems.new.fetch_gem(gem_spec)
rescue Bundler::HTTPError => e
  retries -= 1
  if retries > 0
    sleep(2)
    retry
  else
    puts "Network error: #{e.message}"
    raise
  end
end

Production Patterns

Deploy applications with exact gem versions using the committed Gemfile.lock file. Never modify lock files in production environments.

# Production deployment workflow
bundle config set --local deployment true
bundle config set --local without development test
bundle install --frozen

# Verify bundle integrity
bundle check || bundle install

Cache gems during container builds to reduce deployment time and network dependencies. Layer gem installation before application code changes.

# Dockerfile pattern
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment true && \
    bundle config set --local without development test && \
    bundle install

COPY . .

Monitor gem loading performance and memory usage in production applications. Track bundle size and load times across deployments.

# Profile gem loading time
start_time = Time.now
Bundler.require
load_time = Time.now - start_time
puts "Gems loaded in #{load_time.round(3)}s"

# Memory usage before and after
before_memory = `ps -o rss= -p #{Process.pid}`.to_i
Bundler.require
after_memory = `ps -o rss= -p #{Process.pid}`.to_i
puts "Gems added #{after_memory - before_memory} KB memory"

Implement gem vulnerability scanning in deployment pipelines. Use bundle audit to check for known security issues.

# Security scanning integration
audit_result = system('bundle audit check --update')
unless audit_result
  puts "Security vulnerabilities detected"
  exit 1
end

# Custom vulnerability checking
vulnerable_gems = []
Bundler.load.specs.each do |spec|
  if vulnerable_version?(spec.name, spec.version)
    vulnerable_gems << "#{spec.name}-#{spec.version}"
  end
end

if vulnerable_gems.any?
  puts "Vulnerable gems: #{vulnerable_gems.join(', ')}"
  exit 1
end

Handle gem source authentication for private repositories. Configure credentials securely without exposing tokens in version control.

# Configure private gem sources
bundle config set --global credentials.fury.io $GEMFURY_TOKEN
bundle config set --global credentials.github.com $GITHUB_TOKEN

# Gemfile with authenticated sources
source 'https://rubygems.org'
source 'https://gems.fury.io/mycompany/' do
  gem 'internal_gem'
end

Optimize bundle installation in CI/CD environments using parallel jobs and caching strategies.

# CI configuration
bundle config set --local jobs 4
bundle config set --local retry 3
bundle config set --local deployment true

# Cache gems between builds
cache_key = Digest::SHA256.hexdigest(File.read('Gemfile.lock'))
cache_path = "cache/bundler/#{cache_key}"

if File.directory?(cache_path)
  system("cp -r #{cache_path} vendor/bundle")
else
  bundle install
  system("mkdir -p cache/bundler && cp -r vendor/bundle #{cache_path}")
end

Performance & Memory

Large dependency graphs impact application startup time and memory consumption. Profile bundle loading to identify expensive gems.

# Measure individual gem load times
gem_times = {}
Bundler.definition.specs.each do |spec|
  start_time = Time.now
  require spec.name
  gem_times[spec.name] = Time.now - start_time
rescue LoadError
  # Gem may not have a main require file
end

# Sort by load time
gem_times.sort_by { |_, time| -time }.first(10).each do |name, time|
  puts "#{name}: #{(time * 1000).round(2)}ms"
end

Reduce bundle size by excluding unnecessary groups and gems. Use platform-specific gems to avoid installing unused native extensions.

# Gemfile optimization
gem 'pg', platforms: [:ruby]
gem 'sqlite3', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

group :development, optional: true do
  gem 'debug'
  gem 'rubocop'
end

# Install without optional groups
bundle install --without development test

Memory usage grows with the number of loaded gems and their dependencies. Monitor memory consumption patterns during application lifecycle.

# Track memory usage by gem loading phase
def memory_usage
  `ps -o rss= -p #{Process.pid}`.to_i
end

baseline = memory_usage
require 'bundler/setup'
after_setup = memory_usage

Bundler.require
after_require = memory_usage

puts "Bundler setup: #{after_setup - baseline} KB"
puts "Gem loading: #{after_require - after_setup} KB"
puts "Total bundle memory: #{after_require - baseline} KB"

Parallel gem installation reduces resolution and download time. Configure job count based on available CPU cores and network bandwidth.

# Optimize installation performance
require 'etc'
cpu_count = Etc.nprocessors
bundle config set --local jobs [cpu_count, 8].min

# Benchmark installation times
install_times = []
3.times do
  FileUtils.rm_rf('vendor/bundle')
  start_time = Time.now
  system('bundle install --quiet')
  install_times << Time.now - start_time
end

average_time = install_times.sum / install_times.length
puts "Average install time: #{average_time.round(2)}s"

Dependency resolution complexity increases exponentially with constraint combinations. Simplify version specifications to improve resolution speed.

# Measure resolution time
resolution_start = Time.now
definition = Bundler::Definition.build('Gemfile', 'Gemfile.lock', nil)
definition.resolve_remotely!
resolution_time = Time.now - resolution_start

puts "Resolution time: #{resolution_time.round(3)}s"
puts "Dependency count: #{definition.dependencies.length}"
puts "Resolved specs: #{definition.specs.length}"

# Identify resolution bottlenecks
definition.dependencies.each do |dep|
  if dep.requirements.length > 3
    puts "Complex dependency: #{dep.name} (#{dep.requirements.length} constraints)"
  end
end

Common Pitfalls

Version constraint syntax confusion leads to unexpected dependency resolution. The ~> operator allows patch-level updates but restricts minor version changes.

# Common constraint misunderstandings
gem 'rails', '~> 7.0'     # Allows 7.0.x, 7.1.x, but not 8.0.x
gem 'rails', '~> 7.0.0'   # Allows 7.0.x only, not 7.1.x
gem 'rails', '>= 7.0'     # Allows any version >= 7.0, including 8.0, 9.0

# Verify constraint behavior
constraint = Gem::Requirement.new('~> 7.0')
puts constraint.satisfied_by?(Gem::Version.new('7.0.5'))  # => true
puts constraint.satisfied_by?(Gem::Version.new('7.1.0'))  # => true
puts constraint.satisfied_by?(Gem::Version.new('8.0.0'))  # => false

Group specifications affect gem loading in unexpected ways. Gems declared in multiple groups load when any specified group is required.

# Problematic group usage
group :development, :test do
  gem 'factory_bot_rails'
end

group :test do
  gem 'capybara'
end

# This loads both factory_bot_rails and capybara
Bundler.require(:test)

# Solution: separate group declarations
group :development do
  gem 'factory_bot_rails'
end

group :test do
  gem 'factory_bot_rails'
  gem 'capybara'
end

Lock file modifications without regeneration create inconsistent states. Always use bundle update commands rather than manual lock file edits.

# Detect lock file inconsistencies
definition = Bundler::Definition.build('Gemfile', 'Gemfile.lock', nil)
if definition.missing_specs.any?
  puts "Missing specs: #{definition.missing_specs.map(&:name).join(', ')}"
  puts "Run 'bundle install' to resolve"
end

if definition.unlocked_gems.any?
  puts "Unlocked gems: #{definition.unlocked_gems.join(', ')}"
  puts "Lock file may be inconsistent"
end

Git gem references without specific commits cause deployment inconsistencies. Production deployments may fetch different commits than development environments.

# Problematic git gem usage
gem 'my_gem', git: 'https://github.com/user/my_gem.git'

# Better: specify commit, tag, or branch
gem 'my_gem', git: 'https://github.com/user/my_gem.git', 
               ref: 'abc123'
gem 'my_gem', git: 'https://github.com/user/my_gem.git', 
               tag: 'v1.2.3'
gem 'my_gem', git: 'https://github.com/user/my_gem.git', 
               branch: 'stable'

Platform-specific gems create deployment failures when lock files generate on different operating systems. Use bundle lock --add-platform to include multiple platforms.

# Add platforms to lock file
bundle lock --add-platform ruby
bundle lock --add-platform x86_64-linux
bundle lock --add-platform x86_64-darwin-20

# Verify platform coverage
platforms = []
Bundler.definition.platforms.each do |platform|
  platforms << platform.to_s
end
puts "Supported platforms: #{platforms.join(', ')}"

Source priority and gem name conflicts cause resolution to select unexpected versions. Explicit source blocks prevent ambiguous gem resolution.

# Ambiguous source configuration
source 'https://rubygems.org'
source 'https://gems.mycompany.com'

gem 'rails'  # Could come from either source

# Clear source specification
source 'https://rubygems.org'
source 'https://gems.mycompany.com' do
  gem 'internal_rails_extension'
end

gem 'rails'  # Always from rubygems.org

Reference

Core Commands

Command Options Description
bundle install --deployment, --without GROUP Install gems according to Gemfile
bundle update GEM_NAME, --all Update gems to latest versions
bundle exec CMD Execute command with bundled gems
bundle check --path PATH Verify bundle installation
bundle show GEM --paths Display gem information
bundle list --name-only List bundled gems
bundle outdated --pre, --source SOURCE Show outdated gems
bundle clean --force Remove unused gems

Configuration Options

Setting Values Description
deployment true, false Enable deployment mode
path Directory path Gem installation directory
without Group names Exclude gem groups
with Group names Include gem groups
jobs Integer Parallel installation jobs
retry Integer Network retry attempts
timeout Integer Network timeout seconds
frozen true, false Prevent lock file changes

Gemfile Directives

Directive Parameters Description
source URL, &block Define gem source
gem name, *requirements, **options Declare gem dependency
group *names, &block Group conditional gems
platform *names, &block Platform-specific gems
git_source name, &block Custom git source
gemspec name: nil, path: '.' Load gemspec dependencies

Version Constraint Operators

Operator Example Matches
= = 1.2.3 Exactly 1.2.3
> > 1.2 Greater than 1.2
>= >= 1.2 Greater than or equal 1.2
< < 2.0 Less than 2.0
<= <= 1.9 Less than or equal 1.9
~> ~> 1.2 Compatible with 1.2
!= != 1.3.0 Not equal to 1.3.0

Exception Classes

Exception Cause Recovery
Bundler::GemNotFound Gem not in sources Add gem source or check name
Bundler::VersionConflict Incompatible versions Relax constraints or update gems
Bundler::InstallError Installation failure Check system dependencies
Bundler::LockfileError Lock file corruption Delete and regenerate lock file
Bundler::HTTPError Network failure Retry or check connectivity
Bundler::PathError Invalid path Verify file system permissions

Environment Variables

Variable Purpose Values
BUNDLE_GEMFILE Gemfile location File path
BUNDLE_PATH Installation path Directory path
BUNDLE_WITHOUT Excluded groups Group names
BUNDLE_DEPLOYMENT Deployment mode true, false
BUNDLE_JOBS Parallel jobs Integer
BUNDLE_RETRY Retry attempts Integer