4. Coding With RubyGems

Here we demonstrate the use of the progressbar gem. This library may use terminal features that are not available in your system.

If you wish, enter the following code into a file and run it. (Note that you must require the rubygems library before executing this code, as detailed in chapter 3 of this manual.)

require 'progressbar'

bar = ProgressBar.new("Example progress", 50)
total = 0
until total >= 50
  sleep(rand(2)/2.0)
  increment = (rand(6) + 3)
  bar.inc(increment)
  total += increment
end

Here is a “screenshot” of the partially complete progress bar.

Example progr: 29% |ooooooooooo | ETA: 00:00:04

The first line of the program requires the progressbar library file. RubyGems will look for the progressbar.rb file in the standard library locations. If not found, it will look through its gem repository for a gem that contains progressbar.rb. If a gem is used, RubyGems attempts to use the latest installed version by default.

Note that the program was able to use the latest available installed gem by default without any explicit action in the code. The developer may develop a library without worrying about using RubyGems.

However, to run the code, the environment does need to be “gem enabled”. See Setting Up the RubyGems Environment for details on how to make this happen.

The distinguishing feature of RubyGems is the ability to use versioned libraries when running an application. How do we take advantage of versioning.

Explicit Versioning

To explicitly use a particular version of a library, you need to use the gem method. This method specifies the name of a gem package and the version you wish to have loaded.

For example, suppose your application uses RedCloth, but needs a version of RedCloth in the 3.x series. You can include explcitly in your code:

  require 'rubygems'
  gem 'RedCloth', '~> 3.0'

RubyGems will select the latest installed version of the RedCloth software that has a version number in the 3.x series. If no such software is found, a exception is generated.

Planning Ahead

Rather than spread your version requirements all over your code, it is best to gather them in one location to make it easy to maintain.

The Rails application framework is a good example. You will find an environment file in the config directory of any (recent) Rails application. The environment file, in part, contains the following lines:

# ...
require 'rubygems'
require 'activesupport'
require 'activerecord'
require 'actionpack'
require 'actionmailer'
require 'actionwebservice'
require 'rails'
# ...

The lines will load the most recent available version of the Rails software. Note that they didn’t need to use the gem method above because rubygems was required first, which automatically put the gems in their $LOAD_PATH.

If the most current version is not appropriate (perhaps your ISP has upgraded to an incompatible version of Rails and you haven’t converted your web app yet), then all you need to do is edit environment.rb to be something like:

# ...
require 'rubygems'
gem 'activerecord', '= 1.4.0'
gem 'actionpack', '= 1.2.0'
gem 'actionmailer', '= 0.5.0'
gem 'rails', '= 0.9.3'
# ...

Now your webapp will use an older version of Rails without interfering with anyone else’s use of the newer version.

Using explicit versions in your code is a nice way to make sure you get exactly the libraries you want. The downside is that your code becomes dependent upon the presence of RubyGems, even when no explicit versions are required.

Here is an easy way to avoid explicit dependencies on RubyGems without giving up the ability to handle versions upon request. We move the version requests into a configuration file (we will use Yaml for this example, but that is not critical to the solution).

Execute the following code early in your program before you require any non-core1 libraries.

config = open("config.yml") { |f| YAML.load(f.read) }
if config[:gems]
  require 'gemconfigure'
  Gem.configure(config[:gems])
end

gemconfigure is a lightweight library file provided by RubyGems (lightweight in that it only defines a single method in the Gem module, it does not bring in the entire RubyGems system into memory). If your config file has gem information (indicated by the presence of :gems entry in the hash table), then the gemconfigure file is loaded and the Gem.configure method is given the configuration information.

Gem.configure walks through the a list of name/version pairs from the config and loads the gems specified with the requested version numbers. The RubyGems software is only loaded when Gem.configure determines that an actual versioned gem is required.

This five line code snippet allows an application to use versioning if requested in a config file, but have no dependencies on RubyGems at all if no versioned gems are requested. The gemconfigure file is not even loaded unless the config file requests versioning.

For reference, the Yaml config file might look something like this:

---
:gems:
  - ['RedCloth', '~> 3.0']
  - ['rubyzip', '= 0.5.5']

1 I.e. any libraries that are not delivered with the Ruby language.