For this article I created an example gem sylhare/Vermelinho, obviously based on the Make your own gem from the ruby gem guide.

Let’s check the set up, tests, and how to deploy the gem

Create Gem

You don’t need much to create a gem, only create a *.gemspec file at the root of your project. Here is an example Gem tree structure for the gem vermelinho:

.
├── Gemfile                                 // Gem necessary to install for it
├── Gemfile.lock                            // Lockfile of the installed dependant gems
├── Rakefile                                // For the test
├── bin
│   └── vermelinho                          // Executable
├── lib
│   ├── lingo
│   │   └── translator.rb                   // Gem module
│   └── vermelinho.rb                       // Gem main class
├── test
│   └── test_vermelinho.rb                  // tests
└── vermelinho.gemspec                      // Gem specifications

For the gemspec file you can use something like:

Gem::Specification.new do |s|
  s.name = 'vermelinho'
  s.version = '0.0.1'
  s.summary = "vermelinho!"
  s.description = "Vermilinho attempts to translate 'hello world' in multiple languages"
  s.authors = ["sylhare"]
  s.files = Dir["lib/**//*"]
  s.homepage = 'https://github.com/sylhare/Vermelinho'
  s.license = 'MIT'
  s.executables << 'vermelinho'
end

With s that can be referred as the “spec” of the gem. Once deployed, the information set is displayed in the Gem package host.

Tests

Install the dependencies

To run the tests, you will need some dependencies, that you can put in your Gemfile:

source "https://rubygems.org"

gem "rake"
gem "minitest"

gemspec

The source specifies where the gems will be “looked for”, and the gempsec will reference the gem that you put in your *.gemspec file.

Your gemspec can also include development dependencies:

spec.add_development_dependency 'example', '~> 1.1', '>= 1.1.4'

You can use bundler which facilitates the management of your gems with:

gem install bundler   # to install bundler
bundle install        # to install the dependencies

Write the test

Let’s write a test class with a first test inside, we’ll use require to specify the necessary package:

require 'minitest/autorun'
require 'vermelinho'

class VermelinhoTest < Minitest::Test
  def test_english_hello
    assert_equal "hello world", Vermelinho.hi("english")
  end
end

This is a basic test making sure that hi in english is “hello world”. Now to run the tests you need to modify your _ Rakefile_ with:

require 'rake/testtask'

Rake::TestTask.new do |t|
  t.libs << 'test'
end

desc "Run tests"
task :default => :test

With that set, now when we run:

rake test

We get the result of our test 🎉

Using GitHub action

There’s nothing to do on this step, the Ruby template workflow already does everything for you. Just go to Actions > New Workflow > Ruby and you should be set.

Check Building and testing ruby on GitHub for more info.

Implementation

So much TDD (Test Driven Development) going on 😛 now we’re going to actually implement the logic. Here the implementation does not really matter, let’s have a look at a simplified version for our use case.

First the main class:

class Vermelinho
  def self.hi(language = "english")
    translator = Translator.new(language)
    hi = translator.hi
    return hi
  end
end

require 'lingo/translator'

You can see at the end that we have a require 'lingo/translator' which is mandatory to be at the bottom. It references the code in the lingo module where the translator class named class Vermelinho::Translator is located.

Let’s pretend that translator.hi always return “hello world” as there is nothing else of interest in it for us.

Deploy

Now that you have nice gem packaged, tested and implemented you will want to deploy it to a package host so that other people can enjoy it.

Naming convention

Naming is key, you can’t have two gems under the same name, so verify that the name you have chosen is available! Also depending on the name of your gem, you may “require” them differently in your code.

Check the rubygem guide on how to name your gem, tl;dr:

  • The _ doesn’t change in require and gets “CamelCased” in class name:
    • e.g. gem ruby_parser:
      • require 'ruby_parser'
      • Main class: RubyParser
  • The - are replaced by / in require and :: in class name:
    • e.g. gem rdoc-data:
      • require 'rdoc/data'
      • Main class: RDoc::Data

The name of the gem is specified by the name attribute in the gemspec.

Using GitHub Action

The GitHub action already exists to deploy the gem, go to: Actions > New Workflow > Ruby Gem. By default, it is available for both GPR (GitHub packages registry) and RubyGem.

In order to only push on release, I added:

on:
  push:
    tags:
      - v*

So it only pushes on new tagged commits tagged with a name starting with v. Also you can have continue-on-error: true if you’re okay with the task failing for now.

Github GPR

The only requirement to push a gem to the GitHub Package Registry is to make sure that the gem name is the same as the repository name. In our case:

You can see it deployed in the project. The secrets.GITHUB_TOKEN doesn’t need to be created since it’s already within the action’s environment.

Rubygem

To deploy on Rubygems you will need to create an api key in Settings > Api keys > New Api Key. I suggest using the gem’s name as key identifier and use those permissions:

  • Index rubygems
  • Push rubygem

After that create a GitHub secrets in Settings > Secrets > Add New Repository Secret with name RUBYGEMS_AUTH_TOKEN and value your rubygem api key.

You can see it deployed on rubygem 💎