Building & Publishing a Gem
Reviewed & published by Brayan K
A gem is how Ruby code is shared: a packaged, versioned library anyone can install. Learning to author one turns your useful code into something the whole community can depend on.
Part of the free Ruby course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
By the end of this lesson you'll scaffold a gem with bundle gem, write the gemspec, structure lib/, build a package, and publish it to RubyGems.org with semantic versioning.
What You'll Learn in This Lesson
- Scaffolding a gem with bundle gem
- Writing the .gemspec (name, version, files, deps)
- Structuring lib/ and a VERSION constant
- Loading code with require
- Building with gem build and pushing with gem push
- Semantic versioning and Bundler for local dev
1️⃣ Scaffolding and the Gemspec
bundle gem name generates a standard skeleton: lib/ for code, a .gemspec for metadata, and a version.rb.
# Scaffold a new gem with Bundler:
bundle gem greeter
# It generates a standard structure:
greeter/
lib/
greeter.rb # main entry point (required first)
greeter/
version.rb # holds VERSION constant
greeter.gemspec # gem metadata + dependencies
Gemfile # development dependencies
README.md
bin/ # console/setup helpers
# cd into it and run the tests Bundler set up:
cd greeter
bundle installThe gemspec declares the gem's name, version, files, and dependencies, reading the version from a constant.
# lib/greeter/version.rb — a single source of truth for the version.
module Greeter
VERSION = "0.1.0" # MAJOR.MINOR.PATCH (semantic versioning)
end
# greeter.gemspec — the metadata that describes the gem.
require_relative "lib/greeter/version"
Gem::Specification.new do |spec|
spec.name = "greeter"
spec.version = Greeter::VERSION # read from the constant
spec.authors = ["Ada Lovelace"]
spec.summary = "A friendly greeting library"
spec.files = Dir["lib/**/*.rb"] # which files to package
spec.required_ruby_version = ">= 3.0"
spec.add_dependency "thor", "~> 1.2" # a runtime dependency
spec.add_development_dependency "rspec" # dev-only dependency
end2️⃣ Writing lib/, Building, and Publishing
The entry point in lib/ is what users load with require. Then gem build packages it and gem push publishes it to RubyGems.org.
# lib/greeter.rb — the entry point users load with require "greeter".
require_relative "greeter/version"
module Greeter
class Hello
def greet(name)
"Hello, #{name}!"
end
end
end
# A user installs and uses it like this:
# gem install greeter
# require "greeter"
# Greeter::Hello.new.greet("Ada") # => "Hello, Ada!"
# Build and publish from the gem's directory:
# gem build greeter.gemspec -> greeter-0.1.0.gem
# gem push greeter-0.1.0.gem -> uploads to RubyGems.orgYour turn. Apply a backward-compatible feature bump per semantic versioning, then build the gem.
# 🎯 YOUR TURN — finish the version bump and the entry point.
# lib/calc/version.rb
module Calc
# 👉 TODO: this is a small backward-compatible feature.
# Bump MINOR per semantic versioning: 0.1.0 -> 0.2.0
VERSION = "0.2.0"
end
# lib/calc.rb — the file loaded by require "calc"
require_relative "calc/version"
module Calc
def self.add(a, b)
a + b
end
end
# ✅ Then build it:
# gem build calc.gemspec -> calc-0.2.0.gemMini-Challenge: Package a Utility Gem
Walk through scaffolding, coding, versioning, and building a tiny stringy gem end to end.
# 🎯 MINI-CHALLENGE: package a tiny utility gem.
# Sketch the steps (no need to publish for real):
#
# 1. Run: bundle gem stringy
# 2. In lib/stringy.rb add a method, e.g. Stringy.shout(s) -> s.upcase + "!"
# 3. Set VERSION = "0.1.0" in lib/stringy/version.rb
# 4. List files in the gemspec and add any dependencies.
# 5. Build with: gem build stringy.gemspec
# 6. (Optionally) publish with: gem push stringy-0.1.0.gem
#
# ✅ Goal: a buildable gem with a version, a lib file, and a gemspec.
# your steps hereCommon Errors (and the fix)
- Files missing from the package — Make sure spec.files actually includes your lib/**/*.rb.
- Version mismatch — Keep one VERSION constant and read it in the gemspec.
- Re-pushing the same version — RubyGems rejects duplicates; bump the version before gem push.
- Wrong require path — Users require "greeter", so the entry file must be lib/greeter.rb.
- Runtime vs dev dependencies — Use add_dependency for runtime and add_development_dependency for tools.
Pro Tips
- 💡 Test locally first: point a Gemfile at a path: to use the gem in a real app before publishing.
- 💡 Use the rake release task: bundle gem sets up build/tag/push in one command.
- 💡 Follow SemVer: bump MAJOR for breaking changes so users can upgrade safely.
📋 Quick Reference — Gem Authoring
| Concept | Command / Path |
|---|---|
| Scaffold | bundle gem greeter |
| Metadata | greeter.gemspec |
| Code | lib/greeter.rb |
| Version | VERSION = "0.1.0" |
| Build | gem build greeter.gemspec |
| Publish | gem push greeter-0.1.0.gem |
🎉 Lesson Complete!
- ✅ bundle gem scaffolds a standard gem
- ✅ The .gemspec declares name, version, files, and deps
- ✅ Code lives in lib/ with a VERSION constant
- ✅ gem build packages and gem push publishes
- ✅ Semantic versioning communicates the impact of each release
- ✅ You finished the Ruby course — congratulations!
Practice quiz
What is a Ruby gem?
- a packaged, distributable Ruby library
- a web server
- a CSS file
- a database table
Answer: a packaged, distributable Ruby library. A gem is a packaged Ruby library you can share, version, and install.
Which command scaffolds a new gem project skeleton?
- gem new mylib
- rails g gem mylib
- bundle gem mylib
- gem create mylib
Answer: bundle gem mylib. bundle gem mylib generates a standard gem skeleton with lib, gemspec, and tests.
What does the .gemspec file describe?
- the CSS styles
- the gem's metadata: name, version, files, and dependencies
- only the README text
- the database schema
Answer: the gem's metadata: name, version, files, and dependencies. The gemspec declares the gem's name, version, authors, files, and dependencies.
Where does a gem's Ruby source code conventionally live?
- the bin/ directory
- the test/ directory
- the doc/ directory
- the lib/ directory
Answer: the lib/ directory. Library code goes under lib/, typically lib/mylib.rb and lib/mylib/.
How is a gem's version number usually defined?
- in a VERSION constant, e.g. in lib/mylib/version.rb
- only inside the gemspec as a literal
- in the Gemfile
- in config/routes.rb
Answer: in a VERSION constant, e.g. in lib/mylib/version.rb. Convention is a VERSION constant in lib/mylib/version.rb that the gemspec reads.
What does the require keyword do for a gem user?
- deletes the gem
- loads the gem's code so its classes are available
- builds the gem
- publishes the gem
Answer: loads the gem's code so its classes are available. require "mylib" loads the library so you can use its constants and methods.
Which command builds a .gem package from the gemspec?
- gem package
- bundle build
- rake compile
- gem build mylib.gemspec
Answer: gem build mylib.gemspec. gem build mylib.gemspec produces a versioned .gem file ready to publish.
Where does gem push publish your gem by default?
- Docker Hub
- GitHub Pages
- RubyGems.org
- npm
Answer: RubyGems.org. gem push uploads the built .gem to RubyGems.org, the public gem host.
Under semantic versioning, which part changes for a backward-incompatible change?
- the MINOR number
- the MAJOR number
- the build metadata
- the PATCH number
Answer: the MAJOR number. In MAJOR.MINOR.PATCH, a breaking change bumps the MAJOR version.
What does Bundler help with during gem development?
- managing dependencies and loading the gem locally for testing
- deploying to production servers
- running a database
- styling HTML
Answer: managing dependencies and loading the gem locally for testing. Bundler installs and manages dependencies and lets you load your gem locally with bundle install or a path.
Continue this course
- Previous: Hotwire, Turbo & Action Cable
- Quick reference: Ruby cheat sheet
Frequently asked questions
What exactly is a gem and why package one?
A gem is a packaged, versioned, distributable Ruby library. Packaging your code as a gem lets you reuse it across projects, share it with a team, or publish it for the whole community to install with a single gem install command. A gem bundles your Ruby source under lib/, a .gemspec describing its name, version, files, and dependencies, and usually tests and documentation. Because RubyGems and Bundler understand this format, anyone can declare your gem in their Gemfile and pull it in reliably. In short, gems are how Ruby code is shared and versioned — turning a useful script into something installable and dependable.
What goes in the .gemspec file?
The gemspec is the manifest that describes your gem. It is a Ruby file that builds a Gem::Specification with metadata: the name, version (usually read from a VERSION constant), authors, summary and description, homepage, and license. Crucially it lists which files to include with spec.files, the required Ruby version, and dependencies — spec.add_dependency for runtime libraries your gem needs and spec.add_development_dependency for tools used only while developing, like RSpec. When you run gem build, RubyGems reads the gemspec to assemble the .gem package. Keeping the version in a constant means the gemspec and your code never disagree about which version you are shipping.
How do I build and publish a gem to RubyGems.org?
From the gem's directory, run gem build mygem.gemspec, which packages your files into a versioned file like mygem-0.1.0.gem. To share it publicly, run gem push mygem-0.1.0.gem, which uploads it to RubyGems.org, the central host for Ruby gems; you authenticate the first time with your RubyGems account. After that, anyone can install it with gem install mygem or add it to a Gemfile. Each version must be unique — you cannot overwrite an already-published version — so bump the version before pushing again. Bundler's bundle gem scaffold even includes rake tasks that build, tag, and release in one step.
What is semantic versioning and how does Bundler help during development?
Semantic versioning formats versions as MAJOR.MINOR.PATCH. You bump PATCH for backward-compatible bug fixes, MINOR for backward-compatible new features, and MAJOR for breaking changes that could require users to update their code. This communicates the impact of an upgrade at a glance and lets others set safe version constraints like ~> 1.2. During development, Bundler manages your gem's dependencies via the Gemfile and lockfile, and lets you load the gem locally — for example by pointing a Gemfile entry at a path or git source — so you can test it in a real app before publishing. Together, clear versions and Bundler make releasing and consuming gems predictable.