03 Jun 2018

Bundler Tips and Tricks

This post was originally a guest episode of Ruby Tapas, a series of regular, short screencasts about Ruby topics. If that sounds good to you, try out Ruby Tapas.

As a Ruby developer, chances are really good that you already know and use Bundler on a daily basis, and you can git pull && bundle install with the best of them. What you might not know is that Bundler has changed and grown over the last 8 years. The newer, lesser-known features can provide a lot of help with other gem-related workflows and problems. What other problems, you ask? Let’s take a look.

Creating and releasing your own gems

The first thing Bundler can help with is making your own gems. It’s as easy as bundle gem foobar, and you end up with a new gem named foobar ready for you to add code.

There’s a one-time setup to tell Bundler if you want rspec or minitest, an MIT license, or a code of conduct. After that, you can create gem after gem in just a few seconds each.

$ bundle gem foobar
Creating gem 'foobar'...
Do you want to generate tests with your gem?
Type 'rspec' or 'minitest' to generate those test files now and in the future. rspec/minitest/(none): rspec
Do you want to license your code permissively under the MIT license?
This means that any other developer or company will be legally allowed to use your code for free as long as they admit you created it. You can read more about the MIT license at http://choosealicense.com/licenses/mit. y/(n): y
MIT License enabled in config
Do you want to include a code of conduct in gems you generate?
Codes of conduct can increase contributions to your project by contributors who prefer collaborative, safe spaces. You can read more about the code of conduct at contributor-covenant.org. Having a code of conduct means agreeing to the responsibility of enforcing it, so be sure that you are prepared to do that. Be sure that your email address is specified as a contact in the generated code of conduct so that people know who to contact in case of a violation. For suggestions about how to enforce codes of conduct, see http://bit.ly/coc-enforcement. y/(n): y
Code of conduct enabled in config
      create  foobar/Gemfile
      create  foobar/lib/foobar.rb
      create  foobar/lib/foobar/version.rb
      create  foobar/foobar.gemspec
      create  foobar/Rakefile
      create  foobar/README.md
      create  foobar/bin/console
      create  foobar/bin/setup
      create  foobar/.gitignore
      create  foobar/.travis.yml
      create  foobar/.rspec
      create  foobar/spec/spec_helper.rb
      create  foobar/spec/foobar_spec.rb
      create  foobar/LICENSE.txt
      create  foobar/CODE_OF_CONDUCT.md
Initializing git repo in /Users/andre/Downloads/foobar

Any gem created by Bundler comes with a couple of nice touches: first, a bin/setup file that acts as a centralized, well-known location to install dependencies and do any other specific setup needed to develop on your library. By default, it creates a bash script that echoes commands, and runs bundle install, but it’s very easy to add your own commands.

$ cd foobar
$ cat bin/setup
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx

bundle install

# Do any other automated setup that you need to do here

Every gem also includes a bin/console, to load your gems and then launch IRB, Pry, Fir, or whatever interactive prompt you prefer. It’s the fastest way to experiment with the code from your gem.

$ cat bin/console
#!/usr/bin/env ruby

require "bundler/setup"
require "foobar"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start

require "irb"
IRB.start(__FILE__)

Finally, every gem includes two extremely helpful rake tasks. The rake install will build your gem into a literal .gem file, and then run gem install to install it onto your local machine.

$ rake install
foobar 0.1.0 built to pkg/foobar-0.1.0.gem.
foobar (0.1.0) installed.
$ gem list foobar

*** LOCAL GEMS ***

foobar (0.1.0)

You can easily test that building, installing, and using your gem all work the way that you expect them to.

$ ruby -rfoobar -e 'puts Foobar::VERSION'
0.1.0

The other extremely useful task is rake release, which creates and pushes a git tag for your version, builds a .gem file, and releases the gem on RubyGems.org! What used to be an error-prone process that could take minutes is now just a single command and a few seconds. It’s marvelous.

$ rake release
foobar 0.1.0 built to pkg/foobar-0.1.0.gem.
Tagged v0.1.0.
Pushed git commits and tags.
Pushed foobar 0.1.0 to rubygems.org

Developing multiple repos at once

Now that you have a gem, what if you need to make changes to the gem and your app that depends on it at the same time? Bundler already has a feature to make this work as smoothly as possible: Local Git Repos.

To start, you tell Bundler where your local checkout of a git repo is. For example, we could continue to work on our foobar gem locally while using it in an application by running this configuration command in the application. Now that we’ve done that, running the application locally will use the code from our checkout. We can make changes, reload the application, and see them live.

$ cd app
$ bundle config local.foobar ~/src/indirect/foobar
$ bundle exec ruby -rfoobar -e 'puts Foobar::VERSION'
0.1.0

Adding gems

Now that we’re up to speed on creating and using our own gems, the next tip is about speeding up using gems that already exist. Starting with Bundler 1.15, there is an add command that will automatically add a gem to your Gemfile and install it.

Given a gem name, Bundler will look up the gem by name, add it to your Gemfile, and then resolve and install your entire bundle.

Adding new gems to your application got easier starting with Bundler 1.15–now, you can simply run bundle add GEM and watch as Bundler adds the gem.

$ bundle add rack
Fetching gem metadata from https://rubygems.org/..............
Resolving dependencies...
Fetching gem metadata from https://rubygems.org/..............
Resolving dependencies...
Using bundler 1.16.1
Fetching rack 2.0.4
Installing rack 2.0.4

Now that the command has run, we can take a look inside the Gemfile using cat to see the changes that Bundler made.

$ cat Gemfile
source "https://rubygems.org"

gem "rack", "~> 2.0"

Adding gems is pretty basic so far, but we’re continuing to improve gem management from the command-line. Watch for this to keep getting better.

Editing installed gems

After we’ve installed all of our gems, it’s a common wish to want to see the code for a gem directly. Bundler makes it easy to open any installed gem directly in your editor so you can see (or even edit) that gem’s code.

When you run bundle open GEM, Bundler will look up the location of that gem on your machine, and then open it in your editor.

$ bundle open rack

The default editor is Vim, but Bundler will respect the EDITOR variable to open any editor you want.

Once you’ve got the gem open in your editor, you can browse the source for the gem, search for the definition of a method, and even edit that gem to change behavior or add debugging code. In this example, we’re editing the the rack gem’s main file, rack.rb. To show how this works, we’ll change the VERSION constant.

Any changes that you make will be picked up by the next Ruby process you run. We can see the effect of our changes by printing the VERSION constant that we just edited.

$ bundle exec ruby -rrack -e 'p Rack::VERSION'
[5,0]

As you can probably imagine, being able to change your gems locally is an incredibly valuable tool for the times when it seems like the bug might be in a gem rather than in your own code.

Searching gems

If you’re not yet sure which gem to open, you can do a search across exactly the gems in this particular application by using the slightly-obscure command bundle show --paths. Combine that command with grep, ack, ripgrep, or your favorite search tool to get extremely precise results.

In this example, we’re using the Rails app that powers RubyGems.org. Running bundle show --paths will print out the list of directories, one for each gem used by the application.

$ bundle show --paths
/Users/andre/src/rubygems/rubygems.org/.bundle/ruby/2.3.0/gems/actioncable-5.0.3
/Users/andre/src/rubygems/rubygems.org/.bundle/ruby/2.3.0/gems/actionmailer-5.0.3
/Users/andre/src/rubygems/rubygems.org/.bundle/ruby/2.3.0/gems/actionpack-5.0.3
/Users/andre/src/rubygems/rubygems.org/.bundle/ruby/2.3.0/gems/actionview-5.0.3
/Users/andre/src/rubygems/rubygems.org/.bundle/ruby/2.3.0/gems/activejob-5.0.3
/Users/andre/src/rubygems/rubygems.org/.bundle/ruby/2.3.0/gems/activemodel-5.0.3
/Users/andre/src/rubygems/rubygems.org/.bundle/ruby/2.3.0/gems/activerecord-5.0.3
/Users/andre/src/rubygems/rubygems.org/.bundle/ruby/2.3.0/gems/activesupport-5.0.3
[...]

Once we have that list of paths, we can combine it with a search tool. In this example, we’re using rg, which is the ripgrep tool. Ripgrep is a search tool similar to grep, but optimized for source code. Finding places in our gems where the method create_or_update is defined is suddenly a breeze once we have Bundler and Ripgrep working together.

$ rg 'def create_or_update' $(bundle show --paths)
/Users/andre/.gem/ruby/2.3.3/gems/bundler-1.14.6/lib/gems/bundler-1.14.6: No such file or directory (os error 2)
/Users/andre/src/rubygems/rubygems.org/.bundle/ruby/2.3.0/gems/activerecord-5.0.3/lib/active_record/callbacks.rb
297:    def create_or_update(*) #:nodoc:

/Users/andre/src/rubygems/rubygems.org/.bundle/ruby/2.3.0/gems/activerecord-5.0.3/lib/active_record/persistence.rb
546:    def create_or_update(*args, &block)

bundler/inline for single-file scripts

We’re starting to run out of time, but before we wrap up I want to highlight one more feature that offers developers a very powerful tool. Every tip so far has been about managing gems for an application. What about the times when an application is overkill, and you just want to write a few lines of code into a single file?

Ruby was originally created for that kind of small, helpful script, and makes it very easy… until your small script starts depending on gems. Then you have to think about installing them, making sure the right version is available, and all the other thing that Bundler was invented to help with. If you have a small script that could use some gems, Bundler can help with that as well. This feature is called ‘inline Gemfiles’, and it gives your single-file scripts superpowers.

At the top of your script, require bundler/inline. Then, use the gemfile method to declare your dependencies just like you would in a standalone file. When you run the script, Bundler will jump in and make sure the gems you need are installed and loaded, and your script will always be able to run successfully.

$ vim script.rb

require "bundler/inline"

gemfile do
  source "https://rubygems.org"
  gem "rack-obama"
end

puts "rack-obama's version is: #{Rack::Obama::VERSION}"

Once we’ve created a script that uses inline Gemfiles, just running it means Bundler will take care of everything else. Any missing gems are automatically installed, all installed gems are automatically used, and you never have to think about it. As you can see, we do not currently have the rack-obama gem installed on this machine.

$ gem list rack-obama

*** LOCAL GEMS ***

Under normal circumstances, our script would fail with an error about a missing constant. Bundler is going to silently install our missing gem as part of running our script. Take a look:

$ ruby script.rb
rack-obama's version is: 0.1.1

Bundler made the script’s dependencies work, completely automatically! If we check on installed gems again, we can see that Bundler installed the gems we needed exactly as if we had run gem install ourselves:

$ gem list rack-obama

*** LOCAL GEMS ***

rack-obama (0.1.1)

With that, it’s time to wrap things up! If you’re interested in the latest developments (ha) in Bundler, check out the Bundler blog at bundler.io/blog, or follow us on twitter at @bundlerio. We post and tweet about what changed anytime there’s a new release.

If you want to support development work and maintenance on Bundler, RubyGems, and RubyGems.org, check out Ruby Together and follow us at @rubytogether.