Write your first Rubygems plugin

Posted by Ilija Eftimov on March 31, 2016

I don’t think that at this point Rubygems needs any introduction. Except if
you have been living under a rock for the last 10 years or so. In that case,
I think that you wouldn’t be here reading this blog. You would be having a
problem understanding why someone would like to share what they are eating, or
what they are doing at the moment. For the rest of you, have you heard that
Rubygems is extensible? Let’s see how Rubygems does this, and how we can make
our own Rubygem plugin.

Extensibility

Rubygems, since version 1.3.2 has the ability to load plugins installed in the
gems collection or the $LOAD_PATH. This means that you can create a Ruby program
that will extend Rubygems with your own logic. The best way to go about this is
by packing it into a gem. It gets a bit of meta, I know. Basically, what I am
saying is that you can have a gem which will extend Rubygems, when installed.

Now, there are is a naming convention when it comes to plugins. They must be
named rubygems_plugin and placed at the root of your gem’s #require_path.
Plugins are discovered via Gem::find_files and then loaded. But, instead of
dwelling into the theory, why don’t we go ahead and build one ourselves?

Taken?

No, not this guy!

We are going to keep it peaceful here, no guns, kidnappers and violence. We will
continue talking about gems. Any time we want to create a new gem, we are in a
need of a name. Let’s make a Rubygems plugin that will add a command to check
if a gem name is available. The desired command will be:

gem taken liam

And the output can vary between

The gem name 'liam' is taken.

and

It will be yours. Oh yes. It will be yours. The gem name 'liam' is available.

First, let’s do a bit of restructuring of our gem files and directories, before
we start with writing some code. In the lib directory, we will need to add
the rubygems_plugin.rb file, which will be picked up by Rubygems and will load
our new command. Next, in the same directory, we will need to create a new
directory with the name rubygems. Here, we will store all of the needed files
for this particular Rubygems plugin.

This is what our lib directory should look like:

➜ ls lib
rubygems/ rubygems_plugin.rb

That’s all. Let’s open the rubygems_plugin.rb and start writing our new
command.

Defining the new command

First, in the plugin file we will need to register our new command. Rubygems
has this utility class, called CommandManager. It registers and installs all
the individual sub-commands supported by the gem command. If you would like to
register a new command against the Gem::CommandManager instance, you need to
do the following:

This will tell Rubygems that there’s a new command in town, called taken. This
also expects that the command program will be stored in the
lib/rubygems/commands/taken_command.rb file. Let’s create it!

classGem::Commands::TakenCommand<Gem::Commandend

There are couple of key things when it comes to Rubygems plugins. The first
would be that each command class should inherit from the Gem::Command
class. If you look inside the actual source code of the Gem::Command class,
you will notice the following comment:

This means that, in our new command we will need to implement the required
methods. Let’s begin by adding the initialize method:

definitializesuper("taken","Checks if the gem name is taken.")end

Since our taken command will not need any special configurations, we can
keep the initialize method short and sweet. As the first argument to the
initializer, we pass the command name - in our case taken. The second argument
is the short summary of the command.

The next method that we will need to override is the arguments methods. This
method is pretty simple - it’s just used to provide details of the arguments that
a command will take. In our case, that’s the gem name, whose name we would
like to check. Our implementation of the method would look like:

defarguments# :nodoc:"GEMNAME gem name to check"end

Next is the description method. All it does is return a string containing a
longer description of what the command will do.

defdescription<<-EOF
The `taken` command gets all of the available gems names and check if a gem name is free or taken.
EOFend

These are the only methods that we need at the moment. The last one is the
execute method, which will do all of the work in our new Rubygems command.

Implementing execute

This method will do most of the heavy lifting in our plugin. We can separate the
flow of the command in couple of steps:

Get the gem name from the command arguments,

Throw some kind of an error if it’s blank or has multiple gem names,

Fetch the latest list of gems,

Find out if a gem with the name already exists, and

Depending on the former step, inform the user for the result

First, let’s take the gem name from the command arguments. If you took a bit of
a deeper look in the Gem::Command class, I am sure you’ve noticed the
Gem::Command#get_one_gem_name method. This method will take care of the first
two steps of the flow. The actual source code is the following:

### Get a single gem name from the command line. Fail if there is no gem name# or if there is more than one gem name given.defget_one_gem_nameargs=options[:args]ifargs.nil?orargs.empty?thenraiseGem::CommandLineError,"Please specify a gem name on the command line (e.g. gem build GEMNAME)"endifargs.size>1thenraiseGem::CommandLineError,"Too many gem names (#{args.join(', ')}); please specify only one"endargs.firstend

Nice and easy. Let’s add invoke this method in our execute method:

defexecutegem_name=get_one_gem_nameend

The next step is to get the latest list of gems. Rubygems has a class called
Gem::SpecFetcher, that handles metadata updates from remote gem repositories.
Simply said, this class with retrieve gem specifications and allow you to work
with them. Now, under the hood, the Gem::SpecFetcher uses a class called
Gem::RemoteFetcher. This class handles communication with remote sources, like
Rubygems.org. It knows how to fetch gem details and info.

Since we would like to have our command to work with the gem specs, we need to
introduce a fetcher to our execute method and utilize the
Gem::SpecFetcher#detect method.

The spec_tuples will be a collection of objects from the Gem::NameTuple
class. If you open it’s source code, you can see that it’s a utility class that
represents a gem, in a certain format. The explanation within the class is the
following:

# Represents a gem of name +name+ at +version+ of +platform+. These# wrap the data returned from the indexes.

Since each Gem::NameTuple object has a name method, we compare the gem name
that was provided by the user with the name from the gem spec. If it matches,
that means that there’s already a gem with that name.

defexecutegem_name=get_one_gem_namefetcher=Gem::SpecFetcher.fetcherspec_tuples=fetcher.detectdo|name_tuple|gem_name==name_tuple.nameendifname_free?(spec_tuples)$stdout.puts"It will be yours. Oh yes. It will be yours. The gem name '#{name}' is available."else$stdout.puts"The gem name '#{name}' is taken."endprivatedefname_free?(spec)!spec.flatten.length.zero?endend

This is it. We check if the the spec_tuples length is not zero and we show the
user the appropriate message.

Beyond the command

If you have been following this small tutorial, I am sure you are starting to
get an idea of how you can utilize Rubygems’ API to build a plugin. You could
pack all of this logic into a gem, add some tests and publish it. Then people
can install the gem and use the newly created taken command.

But, this command is just scratching the surface of the possibilities that
Rubygems offers. And as a side-win, the Rubygems code is really great. In the
Git logs you can see names that are well respected in the Ruby community. You can
learn how they’ve modeled their exceptions. Or maybe, how they do the security or
the HTTP layer.

Have you ever done any contributions to Rubygems? Or maybe you have written a
Rubygems plugin? Or maybe, you would like to write one but you have run out of
ideas? Hit me up in the comments!