Skip to Content
Course content

107: Building and Publishing a Gem

Click on the "Edit" button in the top corner of the screen to edit your slide content.

By now, you've probably written a few utility classes or helper modules that you find yourself copying and pasting from one project to another. Maybe it's a specific API wrapper or a custom string formatter. The natural instinct is to just keep doing that, or perhaps create a "shared" folder in your repositories. But once you hit that point, you're actually prime for building a gem. I've seen plenty of developers shy away from this because they think "gem development" is some arcane art reserved for the creators of Rails or Devise, but it's really just about packaging and distribution.

The "Just Make it Work" folder approach

The naive way to start is to manually create a directory, throw a .gemspec file in there, and write your code in a single lib/my_gem.rb file. You might think, "Why bother with a complex structure if my logic only takes up 50 lines?" On the surface, this is fast. You run gem build my_gem.gemspec and gem push my_gem-0.1.0.gem, and suddenly your code is on RubyGems.org.

But this is where things break. When your "simple" utility grows—say you're building a gem called WeatherSnap to fetch data from an API—you'll quickly realize that putting your API client, your response parser, and your error classes all in one file is a nightmare for testing and readability. More importantly, manual gemspecs are prone to errors. I've seen people forget to specify their Ruby version or leave "TODO" placeholders in the description, which RubyGems will actually reject upon upload. If you're managing the folder structure yourself, you're also likely to miss the exe/ directory for CLI tools or the spec/ directory for your tests, making your gem a "black box" that other developers are afraid to contribute to.

Leveraging Bundler's scaffolding

The professional way to do this is to let Bundler do the heavy lifting. Instead of manual folders, you run bundle gem weathersnap. This isn't just a shortcut; it sets up a standardized directory structure that every Rubyist expects. It separates the entry point (lib/weathersnap.rb) from the actual implementation (lib/weathersnap/). This distinction is crucial. The entry point should be lean—mostly just requiring other files—while the weathersnap/ directory holds the actual logic divided into logical files like client.rb or version.rb.

When you use the scaffold, you get a version.rb file. This is where the real trade-off happens. In the naive approach, you might just hardcode the version in the gemspec. But by putting it in a separate file, your gem can report its own version at runtime (WeatherSnap::VERSION), which is a lifesaver when you're debugging a production issue and need to know exactly which iteration of the code is running. I highly recommend sticking to Semantic Versioning (SemVer) here. If you change the method signature of your weather fetcher, bump the major version. If you just add a new feature, bump the minor. Don't just increment numbers randomly; your users will thank you.

Pushing to RubyGems without breaking the internet

Before you push, you have to handle dependencies. In the naive approach, people often add dependencies to their Gemfile and forget they aren't included in the .gemspec. Remember: the Gemfile is for your development environment; the .gemspec is for the people who install your gem. If WeatherSnap depends on httparty, it must be listed as spec.add_dependency "httparty". If it's not in the gemspec, the gem will install fine, but will crash the moment someone calls a method that relies on that missing library.

Once your gemspec is cleaned up—meaning no "TODO"s and correct metadata—you build and push. But here is my advice: always install your gem locally from the path first (gem install ./pkg/weathersnap-0.1.0.gem) and try to use it in a completely separate, empty project. It's incredibly humbling to publish a gem to the world only to realize you accidentally relied on a constant defined in your local environment that isn't actually part of the gem. Verify the "clean room" installation, and then you're ready to gem push.




📋 Practical Task

Build and Package the "GeoDistance" Gem

Your task is to create a functional Ruby gem called geo_distance that calculates the distance between two points on Earth using their latitude and longitude. Follow these requirements:

  • Use bundle gem geo_distance to generate the structure.
  • Implement a GeoDistance.calculate(lat1, lon1, lat2, lon2) method. You can use the Haversine formula for the calculation.
  • Ensure the logic is placed in lib/geo_distance/calculator.rb and required by the main lib/geo_distance.rb file.
  • Update the .gemspec file: replace all "TODO" strings with actual descriptions and a valid homepage URL (you can use a placeholder GitHub URL).
  • Add rake as a development dependency in the gemspec.
  • Build the gem locally using the gem build command and verify that the .gem file is created in your directory.

Verification: To consider this complete, open a fresh terminal, install your generated .gem file, and successfully run GeoDistance.calculate(52.5200, 13.4050, 48.8566, 2.3522) (Berlin to Paris) in IRB.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.