This is the original source reformatted in a single-page book edition (using the Manuscripts format).
See the source repo for how the book gets auto-built with "plain" Jekyll - of course - and hosted on GitHub Pages.
Onwards.
Unpack the mystery behind what’s in a RubyGem.
Each gem has a name, version, and platform. For example, the
rake gem has a 0.8.7
version (from May,
2009). Rake’s platform is ruby
, which means it works on any platform Ruby
runs on.
Platforms are based on the CPU architecture, operating system type and
sometimes the operating system version. Examples include “x86-mingw32” or
“java”. The platform indicates the gem only works with a ruby built for the
same platform. RubyGems will automatically download the correct version for
your platform. See gem help platform
for full details.
Inside a gems are the following components:
Each gem follows the same standard structure of code organization:
% tree freewill
freewill/
├── bin/
│ └── freewill
├── lib/
│ └── freewill.rb
├── test/
│ └── test_freewill.rb
├── README
├── Rakefile
└── freewill.gemspec
Here, you can see the major components of a gem:
lib
directory contains the code for the gemtest
or spec
directory contains tests, depending on which test
framework the developer usesRakefile
, which the
rake program uses to automate tests,
generate code, and perform other tasks.bin
directory, which will be loaded into the user’s PATH
when the gem is
installed.README
and inline with the code.
When you install a gem, documentation is generated automatically for you.
Most gems include RDoc documentation,
but some use YARD docs instead.More information on the gemspec file
Your application, your gem’s users, and you 6 months from now will want to know who wrote a gem, when, and what it does. The gemspec contains this information.
Here’s an example of a gemspec file. You can learn more in how to make a gem.
% cat freewill.gemspec
Gem::Specification.new do |s|
s.name = 'freewill'
s.version = '1.0.0'
s.summary = "Freewill!"
s.description = "I will choose Freewill!"
s.authors = ["Nick Quaranto"]
s.email = 'nick@quaran.to'
s.homepage = 'http://example.com/freewill'
s.files = ["lib/freewill.rb", ...]
end
For more information on the gemspec, please check out the full Specification Reference which goes over each metadata field in detail.
This guide was adapted from Gonçalo Silva’s original tutorial on docs.rubygems.org and from Gem Sawyer, Modern Day Ruby Warrior.
From start to finish, learn how to package your Ruby code in a gem.
Creating and publishing your own gem is simple thanks to the tools baked right into RubyGems. Let’s make a simple “hello world” gem, and feel free to play along at home! The code for the gem we’re going to make here is up on GitHub.
I started with just one Ruby file for my hola
gem, and the gemspec.
You’ll need a new name for yours (maybe hola_yourusername
) to publish it. Check the Patterns guide for
basic recommendations to follow
when naming a gem.
% tree
.
├── hola.gemspec
└── lib
└── hola.rb
Code for your package is placed within the lib
directory. The convention is
to have one Ruby file with the same name as your gem, since that gets
loaded when require 'hola'
is run. That one file is in charge of setting up
your gem’s code and API.
The code inside of lib/hola.rb
is pretty bare bones. It just makes sure that you
can see some output from the gem:
% cat lib/hola.rb
class Hola
def self.hi
puts "Hello world!"
end
end
The gemspec defines what’s in the gem, who made it, and the version of the gem. It’s also your interface to RubyGems.org. All of the information you see on a gem page (like jekyll’s) comes from the gemspec.
% cat hola.gemspec
Gem::Specification.new do |s|
s.name = 'hola'
s.version = '0.0.0'
s.date = '2010-04-28'
s.summary = "Hola!"
s.description = "A simple hello world gem"
s.authors = ["Nick Quaranto"]
s.email = 'nick@quaran.to'
s.files = ["lib/hola.rb"]
s.homepage =
'http://rubygems.org/gems/hola'
s.license = 'MIT'
end
The description member can be much longer than you see in this example. If it matches
/^== [A-Z]/
then the description will be run through RDoc’s markup formatter for display on the RubyGems web site. Be aware though that other consumers of the data might not understand this markup.
Look familiar? The gemspec is also Ruby, so you can wrap scripts to generate the file names and bump the version number. There are lots of fields the gemspec can contain. To see them all check out the full reference.
After you have created a gemspec, you can build a gem from it. Then you can install the generated gem locally to test it out.
% gem build hola.gemspec
Successfully built RubyGem
Name: hola
Version: 0.0.0
File: hola-0.0.0.gem
% gem install ./hola-0.0.0.gem
Successfully installed hola-0.0.0
1 gem installed
Of course, the smoke test isn’t over yet: the final step is to require
the gem and use it:
% irb
>> require 'hola'
=> true
>> Hola.hi
Hello world!
If you’re using an earlier Ruby than 1.9.2, you need to start the session with
irb -rubygems
or require the rubygems library after you launch irb.
Now you can share hola with the rest of the Ruby community. Publishing your gem out to RubyGems.org only takes one command, provided that you have an account on the site. To setup your computer with your RubyGems account:
$ curl -u qrush https://rubygems.org/api/v1/api_key.yaml >
~/.gem/credentials; chmod 0600 ~/.gem/credentials
Enter host password for user 'qrush':
If you’re having problems with curl, OpenSSL, or certificates, you might want to simply try entering the above URL in your browser’s address bar. Your browser will ask you to login to RubyGems.org. Enter your username and password. Your browser will now try to download the file api_key.yaml. Save it in ~/.gem and call it ‘credentials’
Once this has been setup, you can push out the gem:
% gem push hola-0.0.0.gem
Pushing gem to RubyGems.org...
Successfully registered gem: hola (0.0.0)
In just a short time (usually less than a minute), your gem will be available for installation by anyone. You can see it on the RubyGems.org site or grab it from any computer with RubyGems installed:
% gem list -r hola
*** REMOTE GEMS ***
hola (0.0.0)
% gem install hola
Successfully installed hola-0.0.0
1 gem installed
It’s really that easy to share code with Ruby and RubyGems.
Having everything in one file doesn’t scale well. Let’s add some more code to this gem.
% cat lib/hola.rb
class Hola
def self.hi(language = "english")
translator = Translator.new(language)
translator.hi
end
end
class Hola::Translator
def initialize(language)
@language = language
end
def hi
case @language
when "spanish"
"hola mundo"
else
"hello world"
end
end
end
This file is getting pretty crowded. Let’s break out the Translator
into a
separate file. As mentioned before, the gem’s root file is in charge of
loading code for the gem. The other files for a gem are usually placed in a
directory of the same name of the gem inside of lib
. We can split this gem
out like so:
% tree
.
├── hola.gemspec
└── lib
├── hola
│ └── translator.rb
└── hola.rb
The Translator
is now in lib/hola
, which can easily be picked up with a
require
statement from lib/hola.rb
. The code for the Translator
did not
change much:
% cat lib/hola/translator.rb
class Hola::Translator
def initialize(language)
@language = language
end
def hi
case @language
when "spanish"
"hola mundo"
else
"hello world"
end
end
end
But now the hola.rb
file has some code to load the Translator
:
% cat lib/hola.rb
class Hola
def self.hi(language = "english")
translator = Translator.new(language)
translator.hi
end
end
require 'hola/translator'
Gotcha: For newly created folder/file, do not forget to add one entry in hola.gemspec file, as shown-
% cat hola.gemspec
Gem::Specification.new do |s|
...
s.files = ["lib/hola.rb", "lib/hola/translator.rb"]
...
end
without the above change, new folder would not be included into the installed gem.
Let’s try this out. First, fire up irb
:
% irb -Ilib -rhola
irb(main):001:0> Hola.hi("english")
=> "hello world"
irb(main):002:0> Hola.hi("spanish")
=> "hola mundo"
We need to use a strange command line flag here: -Ilib
. Usually RubyGems
includes the lib
directory for you, so end users don’t need to worry about
configuring their load paths. However, if you’re running the code outside of
RubyGems, you have to configure things yourself. It’s possible to manipulate
the $LOAD_PATH
from within the code itself, but that’s considered an
anti-pattern in most cases. There are many more anti-patterns (and good patterns!)
for gems, explained in this guide.
If you’ve added more files to your gem, make sure to remember to add them to
your gemspec’s files
array before publishing a new gem! For this reason (among others),
many developers automate this with
Hoe,
Jeweler,
Rake,
Bundler, or
just a dynamic gemspec
.
Adding more directories with more code from here is pretty much the same process. Split your Ruby files up when it makes sense! Making a sane order for your project will help you and your future maintainers from headaches down the line.
In addition to providing libraries of Ruby code, gems can also expose one or many
executable files to your shell’s PATH
. Probably the best known example of
this is rake
. Another very useful one is prettify_json.rb
, included
with the JSON gem, which formats JSON in a
readable manner (and is included with Ruby 1.9). Here’s an example:
% curl -s http://jsonip.com/ | \
prettify_json.rb
{
"ip": "24.60.248.134"
}
Adding an executable to a gem is a simple process. You just need to place the file in
your gem’s bin
directory, and then add it to the list of executables
in the gemspec. Let’s add one for the Hola gem. First create the file
and make it executable:
% mkdir bin
% touch bin/hola
% chmod a+x bin/hola
The executable file itself just needs a shebang in order to figure out what program to run it with. Here’s what Hola’s executable looks like:
% cat bin/hola
#!/usr/bin/env ruby
require 'hola'
puts Hola.hi(ARGV[0])
All it’s doing is loading up the gem, and passing the first command line argument as the language to say hello with. Here’s an example of running it:
% ruby -Ilib ./bin/hola
hello world
% ruby -Ilib ./bin/hola spanish
hola mundo
Finally, to get Hola’s executable included when you push the gem, you’ll need to add it in the gemspec.
% head -4 hola.gemspec
Gem::Specification.new do |s|
s.name = 'hola'
s.version = '0.0.1'
s.executables << 'hola'
Push up that new gem, and you’ll have your own command line utility published!
You can add more executables as well in the bin
directory if you need to,
there’s an executables
array field on the gemspec.
Note that you should change the gem’s version when pushing up a new release. For more information on gem versioning, see the Patterns Guide
Testing your gem is extremely important. Not only does it help assure you that your code works, but it helps others know that your gem does its job. When evaluating a gem, Ruby developers tend to view a solid test suite (or lack thereof) as one of the main reasons for trusting that piece of code.
Gems support adding test files into the package itself so tests can be run when a gem is downloaded.
In short: TEST YOUR GEM! Please!
Test::Unit
is Ruby’s built-in test framework. There are
lots of
tutorials for
using it online. There are many other test frameworks available for Ruby as
well. RSpec is a popular choice. At the end of the day,
it doesn’t matter what you use, just TEST!
Let’s add some tests to Hola. This requires adding a few more files, namely a
Rakefile
and a brand new test
directory:
% tree
.
├── Rakefile
├── bin
│ └── hola
├── hola.gemspec
├── lib
│ ├── hola
│ │ └── translator.rb
│ └── hola.rb
└── test
└── test_hola.rb
The Rakefile
gives you some simple automation for running tests:
% cat Rakefile
require 'rake/testtask'
Rake::TestTask.new do |t|
t.libs << 'test'
end
desc "Run tests"
task :default => :test
Now you can run rake test
or simply just rake
to run tests. Woot! Here’s
a basic test file for hola:
% cat test/test_hola.rb
require 'minitest/autorun'
require 'hola'
class HolaTest < Minitest::Test
def test_english_hello
assert_equal "hello world",
Hola.hi("english")
end
def test_any_hello
assert_equal "hello world",
Hola.hi("ruby")
end
def test_spanish_hello
assert_equal "hola mundo",
Hola.hi("spanish")
end
end
Finally, to run the tests:
% rake test
(in /Users/qrush/Dev/ruby/hola)
Loaded suite
Started
...
Finished in 0.000736 seconds.
3 tests, 3 assertions, 0 failures, 0 errors, 0 skips
Test run options: --seed 15331
It’s green! Well, depending on your shell colors. For more great examples, the best thing you can do is hunt around GitHub and read some code.
By default most gems use RDoc to generate docs. There are plenty of great tutorials for learning how to mark up your code with RDoc. Here’s a simple example:
# The main Hola driver
class Hola
# Say hi to the world!
#
# Example:
# >> Hola.hi("spanish")
# => hola mundo
#
# Arguments:
# language: (String)
def self.hi(language = "english")
translator = Translator.new(language)
puts translator.hi
end
end
Another great option for documentation is YARD, since when you push a gem, RubyDoc.info generates YARDocs automatically from your gem. YARD is backwards compatible with RDoc, and it has a good introduction on what’s different and how to use it.
With this basic understanding of building your own RubyGem, we hope you’ll be on your way to making your own! The next few guides cover patterns in making a gem and the other capabilities of the RubyGems system.
This tutorial was adapted from Gem Sawyer, Modern Day Ruby Warrior. The code for this gem can be found on GitHub.
Creating a gem that includes an extension that is built at install time.
Many gems use extensions to wrap libraries that are written in C with a ruby wrapper. Examples include nokogiri which wraps libxml2 and libxslt, pg which is an interface to the PostgreSQL database and the mysql and mysql2 gems which provide an interface to the MySQL database.
Creating a gem that uses an extension involves several steps. This guide will
focus on what you should put in your gem specification to make this as easy and
maintainable as possible. The extension in this guide will wrap malloc()
and
free()
from the C standard library.
Every gem should start with a Rakefile which contains the tasks needed by
developers to work on the gem. The files for the extension should go in the
ext/
directory in a directory matching the extension’s name. For this
example we’ll use “my_malloc” for the name.
Some extensions will be partially written in C and partially written in ruby.
If you are going to support multiple languages, such as C and Java extensions,
you should put the C-specific ruby files under the ext/
directory as well in a
lib/
directory.
Rakefile
ext/my_malloc/extconf.rb # extension configuration
ext/my_malloc/my_malloc.c # extension source
lib/my_malloc.rb # generic features
When the extension is built the files in ext/my_malloc/lib/
will be installed
into the lib/
directory for you.
The extconf.rb configures a Makefile that will build your extension based. The extconf.rb must check for the necessary functions, macros and shared libraries your extension depends upon. The extconf.rb must exit with an error if any of these are missing.
Here is an extconf.rb that checks for malloc()
and free()
and creates a
Makefile that will install the built extension at lib/my_malloc/my_malloc.so
:
require "mkmf"
abort "missing malloc()" unless have_func "malloc"
abort "missing free()" unless have_func "free"
create_makefile "my_malloc/my_malloc"
See the mkmf documentation and extension.rdoc for further information about creating an extconf.rb and for documentation on these methods.
The C extension that wraps malloc()
and free()
goes in
ext/my_malloc/my_malloc.c
. Here’s the listing:
#include <ruby.h>
struct my_malloc {
size_t size;
void *ptr;
};
static void
my_malloc_free(void *p) {
struct my_malloc *ptr = p;
if (ptr->size > 0)
free(ptr->ptr);
}
static VALUE
my_malloc_alloc(VALUE klass) {
VALUE obj;
struct my_malloc *ptr;
obj = Data_Make_Struct(klass, struct my_malloc, NULL, my_malloc_free, ptr);
ptr->size = 0;
ptr->ptr = NULL;
return obj;
}
static VALUE
my_malloc_init(VALUE self, VALUE size) {
struct my_malloc *ptr;
size_t requested = NUM2SIZET(size);
if (0 == requested)
rb_raise(rb_eArgError, "unable to allocate 0 bytes");
Data_Get_Struct(self, struct my_malloc, ptr);
ptr->ptr = malloc(requested);
if (NULL == ptr->ptr)
rb_raise(rb_eNoMemError, "unable to allocate %ld bytes", requested);
ptr->size = requested;
return self;
}
static VALUE
my_malloc_release(VALUE self) {
struct my_malloc *ptr;
Data_Get_Struct(self, struct my_malloc, ptr);
if (0 == ptr->size)
return self;
ptr->size = 0;
free(ptr->ptr);
return self;
}
void
Init_my_malloc(void) {
VALUE cMyMalloc;
cMyMalloc = rb_const_get(rb_cObject, rb_intern("MyMalloc"));
rb_define_alloc_func(cMyMalloc, my_malloc_alloc);
rb_define_method(cMyMalloc, "initialize", my_malloc_init, 1);
rb_define_method(cMyMalloc, "free", my_malloc_release, 0);
}
This extension is simple with just a few parts:
struct my_malloc
to hold the allocated memorymy_malloc_free()
to free the allocated memory after garbage collectionmy_malloc_alloc()
to create the ruby wrapper objectmy_malloc_init()
to allocate memory from rubymy_malloc_release()
to free memory from rubyInit_my_malloc()
to register the functions in the MyMalloc
class.You can test building the extension as follows:
$ cd ext/my_malloc
$ ruby extconf.rb
checking for malloc()... yes
checking for free()... yes
creating Makefile
$ make
compiling my_malloc.c
linking shared-object my_malloc.bundle
$ cd ../..
$ ruby -Ilib:ext -r my_malloc -e "p MyMalloc.new(5).free"
#<MyMalloc:0x007fed838addb0>
But this will get tedious after a while. Let’s automate it!
rake-compiler is a set of rake tasks for automating extension building. rake-compiler can be used with C or Java extensions in the same project (nokogiri uses it this way).
Adding rake-compiler is very simple:
require "rake/extensiontask"
Rake::ExtensionTask.new "my_malloc" do |ext|
ext.lib_dir = "lib/my_malloc"
end
Now you can build the extension with rake compile
and hook the compile task
into other tasks (such as tests).
Setting lib_dir
places the shared library in lib/my_malloc/my_malloc.so
(or
.bundle
or .dll
). This allows the top-level file for the gem to be a ruby
file. This allows you to write the parts that are best suited to ruby in ruby.
For example:
class MyMalloc
VERSION = "1.0"
end
require "my_malloc/my_malloc"
Setting the lib_dir
also allows you to build a gem that contains pre-built
extensions for multiple versions of ruby. (An extension for Ruby 1.9.3 cannot
be used with an extension for Ruby 2.0.0). lib/my_malloc.rb
can pick the
correct shared library to install.
The final step to building the gem is adding the extconf.rb to the extensions list in the gemspec:
Gem::Specification.new "my_malloc", "1.0" do |s|
# [...]
s.extensions = %w[ext/my_malloc/extconf.rb]
end
Now you can build and release the gem!
To avoid unintended interactions between gems, it’s a good idea for each gem to
keep all of its files in a single directory. Here are the recommendations for
a gem with the name <name>
:
ext/<name>
is the directory that contains the source files and
extconf.rb
ext/<name>/<name>.c
is the main source file (there may be others)ext/<name>/<name>.c
contains a function Init_<name>
. (The name
following Init_
function must exactly match the name of the extension for
it to be loadable by require.)ext/<name>/extconf.rb
calls create_makefile('<name>/<name>')
only when
the all the pieces needed to compile the extension are present.extensions = ['ext/<name>/extconf.rb']
and includes any
of the necessary extension source files in the files
list.lib/<name>.rb
contains require '<name>/<name>'
which loads the C
extensionOur recommendation on the use of “_” and “-“ in your gem’s name.
Here are some examples of our recommendations for naming gems:
Gem name | Require statement | Main class or module |
---|---|---|
ruby_parser |
require 'ruby_parser' |
RubyParser |
rdoc-data |
require 'rdoc/data' |
RDoc::Data |
net-http-persistent |
require 'net/http/persistent' |
Net::HTTP::Persistent |
net-http-digest_auth |
require 'net/http/digest_auth' |
Net::HTTP::DigestAuth |
The main goal of these recommendations is to give the user some clue about how to require the files in your gem. Following these conventions also lets Bundler require your gem with no extra configuration.
If you publish a gem on rubygems.org it may be removed if the name is objectionable, violates intellectual property or the contents of the gem meet these criteria. You can report such a gem on the RubyGems Support site.
If a class or module has multiple words, use underscores to separate them. This matches the file the user will require, making it easier for the user to start using your gem.
If you’re adding functionality to another gem, use a dash. This usually
corresponds to a /
in the require statement (and therefore your gem’s
directory structure) and a ::
in the name of your main class or module.
If your class or module has multiple words and you’re also adding functionality
to another gem, follow both of the rules above. For example,
net-http-digest_auth
adds
HTTP digest authentication to net/http
.
The user will require 'net/http/digest_auth'
to use the extension
(in class Net::HTTP::DigestAuth
).
OS X and Windows have case-insensitive filesystems by default. Users may mistakenly require files from a gem using uppercase letters which will be non-portable if they move it to a non-windows or OS X system. While this will mostly be a newbie mistake we don’t need to be confusing them more than necessary.
This guide was expanded from How to Name Gems by Eric Hodel.
Start with an idea, end with a distributable package of Ruby code.
Ways to share your gem code with other users.
Now that you’ve created your gem, you’re probably ready to share it. While it is perfectly reasonable to create private gems solely to organize the code in large private projects, it’s more common to build gems so that they can be used by multiple projects. This guide discusses the various ways that you can share your gem with the world.
The simplest way (from the author’s perspective) to share a gem for other developers’ use is to distribute it in source code form. If you place the full source code for your gem on a public git repository (often, though not always, this means sharing it via GitHub), then other users can install it with Bundler’s git functionality.
For example, you can install the latest code for the wicked_pdf gem in a project by including this line in your Gemfile:
gem "wicked_pdf", :git => "git://github.com/mileszs/wicked_pdf.git"
Installing a gem directly from a git repository is a feature of Bundler, not a feature of RubyGems. Gems installed this way will not show up when you run
gem list
.
If you want to control who can install a gem, or directly track the activity surrounding a gem, then you’ll want to set up a private gem server. You can set up your own gem server or use a commercial service such as Gemfury.
RubyGems 2.2.0 and newer support the allowed_push_host
metadata value to
restrict gem pushes to a single host. If you are publishing private gems you
should set this value to prevent accidental pushes to rubygems.org:
Gem::Specification.new 'my_gem', '1.0' do |s|
# ...
s.metadata['allowed_push_host'] = 'https://gems.my-company.example'
end
See the Resources guide for an up-to-date listing of options for private gem servers.
The simplest way to distribute a gem for public consumption is to use
RubyGems.org. Gems that are published to RubyGems.org
can be installed via the gem install
command or through the use of tools such
as Isolate or Bundler.
To begin, you’ll need to create an account on RubyGems.org. Visit the sign up page and supply an email address that you control, a handle (username) and a password.
After creating the account, use your email and password when pushing the gem. (RubyGems saves the credentials in ~/.gem/credentials for you so you only need to log in once.)
To publish version 0.1.0 of a new gem named ‘squid-utils’:
$ gem push squid-utils-0.1.0.gem
Enter your RubyGems.org credentials.
Don't have an account yet? Create one at https://rubygems.org/sign_up
Email: gem_author@example
Password:
Signed in.
Pushing gem to RubyGems.org...
Successfully registered gem: squid-utils (0.1.0)
Congratulations! Your new gem is now ready for any ruby user in the world to install!
If you have multiple maintainers for your gem you can give your fellow maintainers permission to push the gem to rubygems.org through the gem owner command.
See Security page.
Common practices to make your gem users’ and other developers’ lives easier.
There are only two hard things in Computer Science: cache invalidation and naming things. -Phil Karlton
Be consistent with how your gem files in lib
and bin
are named. The
hola gem from the make your own
gem guide is a great example:
% tree
.
├── Rakefile
├── bin
│ └── hola
├── hola.gemspec
├── lib
│ ├── hola
│ │ └── translator.rb
│ └── hola.rb
└── test
└── test_hola.rb
The executable and the primary file in lib
are named the same. A developer
can easily jump in and call require 'hola'
with no problems.
Naming your gem is important. Before you pick a name for your gem, do a quick search on RubyGems.org and GitHub to see if someone else has taken it. Every published gem must have a unique name. Be sure to read our naming recommendations when you’ve found a name you like.
A versioning policy is merely a set of simple rules governing how version numbers are allocated. It can be very simple (e.g. the version number is a single number starting with 1 and incremented for each successive version), or it can be really strange (Knuth’s TeX project had version numbers: 3, 3.1, 3.14, 3.141, 3.1415; each successive version added another digit to PI).
The RubyGems team urges gem developers to follow the Semantic Versioning standard for their gem’s versions. The RubyGems library itself does not enforce a strict versioning policy, but using an “irrational” policy will only be a disservice to those in the community who use your gems.
Suppose you have a ‘stack’ gem that holds a Stack
class with both push
and
pop
functionality. Your CHANGELOG
might look like this if you use
semantic versioning:
Stack
class is released.depth
method.top
and made pop
return nil
(pop
used to
return the old top item).push
now returns the value pushed (it used to return
nil
).Semantic versioning boils down to:
0.0.x
level changes for implementation level detail changes, such
as small bug fixes0.x.0
level changes for any backwards compatible API changes,
such as new functionality/featuresx.0.0
level changes for backwards incompatible API changes,
such as changes that will break existing users code if they updateGems work with other gems. Here are some tips to make sure they’re nice to each other.
RubyGems provides two main “types” of dependencies: runtime and development. Runtime dependencies are what your gem needs to work (such as rails needing activesupport).
Development dependencies are useful for when someone wants to make
modifications to your gem. When you specify development dependencies, another
developer can run gem install --dev your_gem
and RubyGems will grab both sets
of dependencies (runtime and development). Typical development dependencies
include test frameworks and build systems.
Setting dependencies in your gemspec is easy. Just use add_runtime_dependency
and add_development_dependency
:
Gem::Specification.new do |s|
s.name = "hola"
s.version = "2.0.0"
s.add_runtime_dependency "daemons",
["= 1.1.0"]
s.add_development_dependency "bourne",
[">= 0"]
gem
from within your gemYou may have seen some code like this around to make sure you’re using a specific version of a gem:
gem "extlib", ">= 1.0.8"
require "extlib"
It’s reasonable for applications that consume gems to use this (though they could also use a tool like Bundler). Gems themselves should not do this. They should instead use dependencies in the gemspec so RubyGems can handle loading the dependency instead of the user.
If your gem properly follows semantic versioning with its versioning scheme, then other Ruby developers can take advantage of this when choosing a version constraint to lock down your gem in their application.
Let’s say the following releases of a gem exist:
Someone who wants to use your gem has determined that version 2.2.0 works with
their software, but version 2.1.0 doesn’t have a feature they need. Adding a
dependency in a gem (or a Gemfile
from Bundler) might look like:
# gemspec
spec.add_runtime_dependency 'library',
'>= 2.2.0'
# bundler
gem 'library', '>= 2.2.0'
This is an “optimistic” version constraint. It’s saying that all changes from 2.x on will work with my software, but for version 3.0.0 this will not be true.
The alternative here is to be “pessimistic”. This explicitly excludes the version that might break your code.
# gemspec
spec.add_runtime_dependency 'library',
['>= 2.2.0', '< 3.0']
# bundler
gem 'library', '>= 2.2.0', '< 3.0'
RubyGems provides a shortcut for this, commonly known as the twiddle-wakka:
# gemspec
spec.add_runtime_dependency 'library',
'~> 2.2'
# bundler
gem 'library', '~> 2.2'
Notice that we dropped the PATCH
level of the version number. Had we said
~> 2.2.0
, that would have been equivalent to ['>= 2.2.0', '< 2.3.0']
.
If you want to allow use of newer backwards-compatible versions but need a specific bug fix you can use a compound requirement:
# gemspec
spec.add_runtime_dependency 'library', '~> 2.2', '>= 2.2.1'
# bundler
gem 'library', '~> 2.2', '>= 2.2.1'
The important note to take home here is to be aware others will be using
your gems, so guard yourself from potential bugs/failures in future releases
by using ~>
instead of >=
if at all possible.
If you’re dealing with a lot of gem dependencies in your application, we recommend that you take a look into Bundler or Isolate which do a great job of managing a complex version manifest for many gems.
If you want to allow prereleases and regular releases use a compound requirement:
# gemspec
spec.add_runtime_dependency 'library', '>= 2.0.0.a', '< 3'
Using ~>
with prerelease versions will restrict you to prerelease versions
only.
Summary: don’t.
This line…
require 'rubygems'
…should not be necessary in your gem code, since RubyGems is loaded
already when a gem is required. Not having require 'rubygems'
in your code
means that the gem can be easily used without needing the RubyGems client to
run.
For more information please check out Ryan Tomayko’s original post about the subject.
At its core, RubyGems exists to help you manage Ruby’s $LOAD_PATH
, which is
how the require
statement picks up new code. There’s several things you can
do to make sure you’re loading code the right way.
When packaging your gem files, you need to be careful of what is in your lib
directory. Every gem you have installed gets its lib
directory appended onto
your $LOAD_PATH
. This means any file on the top level of the lib
directory
could get required.
For example, let’s say we have a foo
gem with the following structure:
.
└── lib
├── foo
│ └── cgi.rb
├── erb.rb
├── foo.rb
└── set.rb
This might seem harmless since your custom erb
and set
files are within
your gem. However, this is not harmless, anyone who requires this gem will not
be able to bring in the
ERB or
Set classes
provided by Ruby’s standard library.
The best way to get around this is to keep files in a different directory
under lib
. The usual convention is to be consistent and put them in the same
folder name as your gem’s name, for example lib/foo/cgi.rb
.
Gems should not have to use __FILE__
to bring in other Ruby files in your
gem. Code like this is surprisingly common in gems:
require File.join(
File.dirname(__FILE__),
"foo", "bar")
Or:
require File.expand_path(File.join(
File.dirname(__FILE__),
"foo", "bar"))
The fix is simple, just require the file relative to the load path:
require 'foo/bar'
Or use require_relative:
require_relative 'foo/bar'
The make your own gem guide has a great example of this behavior in practice, including a working test suite. The code for that gem is on GitHub as well.
Gems should not change the $LOAD_PATH
variable. RubyGems manages this for
you. Code like this should not be necessary:
lp = File.expand_path(File.dirname(__FILE__))
unless $LOAD_PATH.include?(lp)
$LOAD_PATH.unshift(lp)
end
Or:
__DIR__ = File.dirname(__FILE__)
$LOAD_PATH.unshift __DIR__ unless
$LOAD_PATH.include?(__DIR__) ||
$LOAD_PATH.include?(File.expand_path(__DIR__))
When RubyGems activates a gem, it adds your package’s lib
folder to the
$LOAD_PATH
ready to be required normally by another lib or application. It
is safe to assume you can then require
any file in your lib
folder.
Many gem developers have versions of their gem ready to go out for testing or “edge” releases before a big gem release. RubyGems supports the concept of “prerelease” versions, which could be betas, alphas, or anything else that isn’t ready as a regular release.
Taking advantage of this is easy. All you need is one or more letters in the
gem version. For example, here’s what a prerelease gemspec’s version
field
might look like:
Gem::Specification.new do |s|
s.name = "hola"
s.version = "1.0.0.pre"
Other prerelease version numbers might include 2.0.0.rc1
, or 1.5.0.beta.3
.
It just has to have a letter in it, and you’re set. These gems can then be
installed with the --pre
flag, like so:
% gem list factory_girl -r --pre
*** REMOTE GEMS ***
factory_girl (2.0.0.beta2, 2.0.0.beta1)
factory_girl_rails (1.1.beta1)
% gem install factory_girl --pre
Successfully installed factory_girl-2.0.0.beta2
1 gem installed
Several sources were used for content for this guide:
How to build and install cryptographically signed gems– and other security concerns.
Security practices are being actively discussed. Check back often.
Installing a gem allows that gem’s code to run in the context of your application. Clearly this has security implications: installing a malicious gem on a server could ultimately result in that server being completely penetrated by the gem’s author. Because of this, the security of gem code is a topic of active discussion within the Ruby community.
RubyGems has had the ability to cryptographically sign
gems since version
0.8.11. This signing works by using the gem cert
command to create a key
pair, and then packaging signing data inside the gem itself. The gem install
command optionally lets you set a security policy, and you can verify the
signing key for a gem before you install it.
However, this method of securing gems is not widely used. It requires a number of manual steps on the part of the developer, and there is no well-established chain of trust for gem signing keys. Discussion of new signing models such as X509 and OpenPGP is going on in the rubygems-trust wiki, the RubyGems-Developers list and in IRC. The goal is to improve (or replace) the signing system so that it is easy for authors and transparent for users.
Install with a trust policy.
gem install gemname -P HighSecurity
: All dependent gems must be signed
and verified.
gem install gemname -P MediumSecurity
: All signed dependent gems must be
verified.
bundle --trust-policy MediumSecurity
: Same as above, except Bundler only
recognizes the long --trust-policy
flag, not the short -P
.
Caveat: Gem certificates are trusted globally, such that adding a cert.pem for one gem automatically trusts all gems signed by that cert.
Verify the checksum, if available
gem fetch gemname -v version
ruby -rdigest/sha2 -e "puts Digest::SHA512.new.hexdigest(File.read('gemname-version.gem'))
Know the risks of being pwned, as described by Benjamin Smith’s Hacking with Gems talk
gem cert
1) Create self-signed gem cert
cd ~/.ssh
gem cert --build your@email.com
chmod 600 gem-p*
2) Configure gemspec with cert
Add cert public key to your repository
cd /path/to/your/gem
mkdir certs
cp ~/.ssh/gem-public_cert.pem certs/yourhandle.pem
git add certs/yourhandle.pem
Add cert paths to your gemspec
s.cert_chain = ['certs/yourhandle.pem']
s.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $0 =~ /gem\z/
3) Add your own cert to your approved list, just like anyone else
gem cert --add certs/yourhandle.pem
4) Build gem and test that you can install it
gem build gemname.gemspec
gem install gemname-version.gem -P HighSecurity
# or -P MediumSecurity if your gem depends on unsigned gems
5) Example text for installation documentation
MetricFu is cryptographically signed. To be sure the gem you install hasn’t been tampered with:
Add my public key (if you haven’t already) as a trusted certificate
gem cert --add <(curl -Ls https://raw.github.com/metricfu/metric_fu/master/certs/bf4.pem)
gem install metric_fu -P MediumSecurity
The MediumSecurity trust profile will verify signed gems, but allow the installation of unsigned dependencies.
This is necessary because not all of MetricFu’s dependencies are signed, so we cannot use HighSecurity.
require 'digest/sha2'
built_gem_path = 'pkg/gemname-version.gem'
checksum = Digest::SHA512.new.hexdigest(File.read(built_gem_path))
checksum_path = 'checksum/gemname-version.gem.sha512'
File.open(checksum_path, 'w' ) {|f| f.write(checksum) }
# add and commit 'checksum_path'
For details, see discussion with Yorick Peterse.
If you spot a security vulnerability in someone else’s gem, then you first step should be to check whether this is a known vulnerability. One way is by searching for an advisory on RubySec.
If this looks like a newly discovered vulnerability, then you should contact the author(s) privately (i.e., not via a pull request or issue on a public project) explaining the issue, how it can be exploited, and ideally offering an indication of how it might be fixed.
First, request a CVE identifier by emailing one of these places. This identifier will make it easy to uniquely identify the vulnerability when talking about it.
Second, work out what people who depend on your gem should do to resolve the vulnerability. This may involve releasing a patched version of your gem that you can recommend they upgrade to.
Finally, you need to tell people about the vulnerability. Currently there is no single place to broadcast this information but some good places to start might be to:
Send an email to several lists including ruby-security-ann@googlegroups.com, rubysec-announce@googlegroups.com, and oss-security@lists.openwall.com outlining the vulnerability, which versions of your gem it affects, and what actions those depending on the gem should take. Make sure to use a subject that includes the gem name, some short summary of the vulnerability, and the CVE ID if you have one.
Add it to ruby-advisory-db. You can do this by following the CONTRIBUTING guidelines and submitting a pull request.
Several sources were used for content for this guide:
Great blog posts, tutorials, and other sites to help you out.
A collection of helpful material about RubyGems. Feel free to fork and add your own!
require 'rubygems'
Is WrongTools to help build gems.
Tools to watch gems for changes.
This site is open source and its content is Creative Commons licensed.
These people have donated time to creating and improving the RubyGems Guides site:
Material for the Guides was adapted from these sources:
Hosted by GitHub Pages.