A PlanarFe Adventure

LearnLoveCode

How to Build a Valueless Gem

H. Tracy Hall with his synthetic diamond machine.

What are gems made out of? Code. (dammit Avi…)

I realized the other day that, while I’ve been using gems for awhile now I didn’t actually know what goes into building and publishing one. Let’s fix that.

What is a Gem?

A gem is the standard format for distributing Ruby programs and libraries, typically through the RubyGems package manager.

OK, so let’s make one.

Fine (pushy…).

I started out writing out the steps to properly set up your file structure and build your gem. It looked something like this:

I.Create a directory with the name of your gem.

It should look something like this:

1
2
3
4
root
├── bin
├── lib
└── test

Ruby Gem Naming convention as per RubyGems.org:

1
2
3
4
5
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

II. WRITE YO CODE!

And put it in the ‘lib’ folder. The convention is to have one Ruby file with the name of your gem, since that will be what is loaded when someone requires your gem. This file is in charge of setting up your gem’s code and application program interface.

Require additional files:

  • These should go into a subfolder within lib named for the gem and required in your main gem file. These dependencies will also be listed in your gem spec. We’ll get to that in a bit.
1
2
3
4
└── lib
├── poezix
│  └── more_poezix.rb
└── poezix.rb

III. Build the gemspec.

The gem spec contains all the information about your gem. Who made it, what it’s for, its version, homepage, yada yada yada. It’s al in the gemspec. You can even make a gem without filling out the required attributes of your gem in a gem spec file. So it’s kinda important.

IV. Build the gem:

1
gem build poezix.gemspec

V. Install the gem locally.

1
gem install ./poezix-0.0.1.gem

VI. Test yo ish!

You don’t want to send out non-functional code with all your contact info in it. Include a test suite and documentation.

1
2
3
4
5
6
7
8
9
10
11
.
├── Rakefile
├── poezix.gemspec
├── bin
│   └── poezix
├── lib
│   ├── poezix
│   │   └── more_poezix.rb
│   └── poezix.rb
└── test
    └── test_poezix.rb

1
2
3
4
5
6
% irb
>> require 'poezix'
#=> true
“twice”.upon_a_midnight_dreary{puts ‘Nevermore!’}
#=> Nevermore!
#=> Nevermore!

VII. Document your code with RDoc, Yard, or something similar.

VIII. Publish!

Create an account at ruby gems.org.

Setup your account on your computer

1
curl -u qrush https://rubygems.org/api/v1/api_key.yaml>~/.gem/credentials; chmod 0600 ~/.gem/credentials

Push your gem to the interwebs.

1
gem push poezix-0.0.1.gem

Now Ignore All of That and Do This Instead:

Why? because it’s faster, easier, and idiot-proof.

1
bundle gem your_gem_name

Oh, look! It made just about everything you need and it even included hints of what to put where in the files that Bundler created for you.

1
2
3
4
5
6
7
8
9
10
11
poezix
├── .gitignore
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── poezix.gemspec
└── lib
    ├── poezix
    │   └── version.rb
    └── poezix.rb

Hey! Look! A TODO list!

 1 # coding: utf-8
 2 lib = File.expand_path('../lib', __FILE__)
 3 $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
 4 require 'test_gem/version'
 5 
 6 Gem::Specification.new do |spec|
 7   spec.name          = "test_gem"
 8   spec.version       = TestGem::VERSION
 9   spec.authors       = ["user_name"]
10   spec.email         = ["user_email@some_mail.com"]
11 
12   spec.summary       = %q{TODO: Write a short summary, because Rubygems requires one.}
13   spec.description   = %q{TODO: Write a longer description or delete this line.}
14   spec.homepage      = "TODO: Put your gem's website or public repo URL here."
15   spec.license       = "MIT"
16 
17   # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18   # delete this section to allow pushing this gem to any host.
19   if spec.respond_to?(:metadata)
20     spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
21   else
22     raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23   end
24 
25   spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26   spec.bindir        = "exe"
27   spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28   spec.require_paths = ["lib"]
29 
30   spec.add_development_dependency "bundler", "~> 1.10"
31   spec.add_development_dependency "rake", "~> 10.0"
32   spec.add_development_dependency "rspec"
33 end

Now write your ruby, fill out your gem spec and readme, build your gem, write some tests, test your gem, and publish your fancy, new red rock.

Here’s the not all that useful gem that I built for the purposes of this gemsperiment:

 1 require "poezix/version"
 2 
 3 class String
 4     def upon_a_midnight_dreary
 5       if self == "once"
 6         1.times do
 7           yield
 8         end        
 9       elsif self == "twice"
10         2.times do
11           yield
12         end
13       elsif self == "thrice"
14         3.times do
15           yield
16         end
17       else
18         raise "Mayhaps an Integer you seek?"
19       end
20     end
21 end
22 
23 
24 class Integer
25   def upon_a_midnight_dreary
26     self.times do
27       yield
28     end
29   end
30 end
31 
32 
33 
34 def quoth(phrase = "Nevermore")
35   puts phrase.upcase + "!!!"
36 end
37 
38 
39 #Generates filler content from a text randomly selected from one of 42 poems/short stories.
40 #Takes an arguement to determine the number of line returned.
41 
42 def poesem_ipsum(number_of_lines = "full")
43   all_texts = Dir[File.dirname(__FILE__) + "/texts/*.txt"]
44   number_of_texts = all_texts.size
45   selection = rand(1..number_of_texts)
46 
47   text = File.open(all_texts[selection]).readlines #An Array
48 
49   if number_of_lines == "full"
50     number_of_lines = text.count - 1
51   end
52 
53   text[0..number_of_lines].join
54 end

Sources:

[Engineering Lunch Series] Step-by-Step Guide to Building Your First Ruby Gem https://quickleft.com/blog/engineering-lunch-series-step-by-step-guide-to-building-your-first-ruby-gem/