Overview
Ruby gems provide the primary mechanism for packaging and distributing Ruby libraries. The gem system centers around the Gem::Specification
class, which defines metadata, dependencies, and file inclusion patterns. Ruby's gem build
command processes gemspec files into distributable .gem
archives, while gem push
publishes to repositories.
The gem creation process involves three core files: the gemspec specification, a library structure following Ruby's load path conventions, and optional executables in the bin
directory. The Gem::Specification
object accepts metadata through method calls or block syntax, validates dependencies, and handles version constraints.
# basic.gemspec
Gem::Specification.new do |spec|
spec.name = "my_gem"
spec.version = "1.0.0"
spec.authors = ["Author Name"]
spec.files = Dir["lib/**/*.rb"]
spec.require_paths = ["lib"]
end
Ruby loads gems through the $LOAD_PATH
mechanism, requiring the gem's require_paths
directories. The gem system automatically manages version resolution, dependency loading, and file discovery. When multiple gem versions exist, Ruby selects the latest unless version constraints specify otherwise.
# lib/my_gem.rb
module MyGem
VERSION = "1.0.0"
def self.hello
"Hello from gem"
end
end
The bundle gem
command generates standard gem scaffolding with conventional directory structure, gemspec template, and basic configuration files. This approach ensures consistency with Ruby community practices and RubyGems requirements.
Basic Usage
Creating a gem starts with directory structure establishment and gemspec definition. The conventional layout places library code under lib/
, with the main file matching the gem name. Ruby's require system loads lib/gem_name.rb
when code calls require 'gem_name'
.
# Generate gem scaffold
system("bundle gem example_calculator")
# example_calculator.gemspec
require_relative "lib/example_calculator/version"
Gem::Specification.new do |spec|
spec.name = "example_calculator"
spec.version = ExampleCalculator::VERSION
spec.authors = ["Developer"]
spec.email = ["dev@example.com"]
spec.summary = "Simple mathematical operations"
spec.description = "Provides basic arithmetic with error handling"
spec.homepage = "https://github.com/user/example_calculator"
spec.license = "MIT"
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
spec.add_dependency "activesupport", ">= 5.0"
spec.add_development_dependency "rake", "~> 13.0"
spec.add_development_dependency "minitest", "~> 5.0"
end
The gemspec's files
specification determines which files include in the built gem. The git ls-files
approach includes version-controlled files while excluding test directories. Ruby processes this during gem build
to create the final package.
# lib/example_calculator.rb
require "active_support/core_ext/numeric/conversions"
require_relative "example_calculator/version"
require_relative "example_calculator/operations"
module ExampleCalculator
class Error < StandardError; end
class DivisionByZeroError < Error; end
extend Operations
end
Building the gem requires running gem build
with the gemspec file. This command validates the specification, processes file inclusions, and creates a .gem
archive. Ruby stores built gems in the current directory unless configured otherwise.
# Build process
system("gem build example_calculator.gemspec")
# => Successfully built RubyGem
# => Name: example_calculator
# => Version: 1.0.0
# => File: example_calculator-1.0.0.gem
Installation happens through gem install
with the built gem file or directly from repositories. Ruby processes dependencies automatically, downloading required gems and their transitive dependencies. The --local
flag installs from local files without repository access.
# Local installation
system("gem install ./example_calculator-1.0.0.gem")
# Usage after installation
require "example_calculator"
result = ExampleCalculator.add(5, 3)
# => 8
Advanced Usage
Complex gems require sophisticated configuration through gemspec extensions, conditional dependencies, and platform-specific handling. The Gem::Specification
supports conditional logic for different Ruby versions, operating systems, and runtime environments.
# advanced_gem.gemspec
Gem::Specification.new do |spec|
spec.name = "advanced_processor"
spec.version = "2.1.0"
# Platform-specific dependencies
if RUBY_PLATFORM =~ /java/
spec.add_dependency "jruby-openssl"
else
spec.add_dependency "openssl", ">= 2.0"
end
# Ruby version constraints
spec.required_ruby_version = ">= 2.7.0"
spec.required_rubygems_version = ">= 3.0.0"
# Complex file patterns
spec.files = Dir[
"lib/**/*.rb",
"ext/**/*.{c,h,rb}",
"data/**/*.{json,yml}",
"README*",
"LICENSE*",
"CHANGELOG*"
].reject { |f| f.include?("spec") || f.include?("test") }
# Multiple executables
spec.executables = ["processor", "batch-processor", "processor-daemon"]
# Extensions for C code
spec.extensions = ["ext/native/extconf.rb"]
# Post-install message
spec.post_install_message = "Run 'processor --help' to get started"
end
Native extensions integrate C code into gems through extconf.rb
configuration files. Ruby processes these during installation, compiling C sources and linking against system libraries. The extension mechanism requires careful platform handling and dependency management.
# ext/native/extconf.rb
require "mkmf"
# Check for required headers
unless have_header("openssl/ssl.h")
abort "OpenSSL headers not found"
end
# Check for libraries
unless have_library("ssl", "SSL_new")
abort "OpenSSL library not found"
end
# Configure build flags
$CFLAGS << " -O3 -Wall"
$LDFLAGS << " -lssl -lcrypto"
# Create Makefile
create_makefile("native/processor_ext")
Multi-platform gems handle different operating systems through platform-specific gemspecs and conditional code paths. Ruby's RUBY_PLATFORM
constant identifies the target platform, enabling platform-appropriate behavior selection.
# lib/advanced_processor/platform.rb
module AdvancedProcessor
module Platform
case RUBY_PLATFORM
when /mswin|mingw|cygwin/
WINDOWS = true
SEPARATOR = "\\"
def self.memory_info
`wmic OS get TotalVisibleMemorySize /value`.split("=").last.to_i
end
when /linux/
LINUX = true
SEPARATOR = "/"
def self.memory_info
File.read("/proc/meminfo")[/MemTotal:\s+(\d+)/, 1].to_i
end
when /darwin/
MACOS = true
SEPARATOR = "/"
def self.memory_info
`sysctl -n hw.memsize`.to_i / 1024
end
else
UNIX = true
SEPARATOR = "/"
def self.memory_info
# Generic Unix fallback
1024 * 1024 # 1GB default
end
end
end
end
Complex gems often implement plugin systems through dynamic loading and module extension. Ruby's const_missing
and method_missing
hooks enable flexible plugin discovery and registration mechanisms.
# lib/advanced_processor/plugin_system.rb
module AdvancedProcessor
class PluginSystem
@plugins = {}
@loaded_paths = Set.new
class << self
attr_reader :plugins
def register(name, plugin_class)
@plugins[name.to_sym] = plugin_class
end
def load_plugins(directory)
return if @loaded_paths.include?(directory)
Dir.glob(File.join(directory, "**/*.rb")).each do |file|
require_relative file
rescue LoadError => e
warn "Failed to load plugin #{file}: #{e.message}"
end
@loaded_paths << directory
end
def const_missing(name)
plugin_name = name.to_s.underscore.to_sym
if plugin_class = @plugins[plugin_name]
const_set(name, plugin_class)
else
super
end
end
end
end
end
Error Handling & Debugging
Gem creation encounters various error categories: specification validation, dependency resolution, build failures, and runtime conflicts. Ruby's gem system provides specific exception classes for different failure modes, requiring targeted handling strategies.
The Gem::InvalidSpecificationException
occurs when gemspec validation fails during build or installation. Common causes include missing required fields, invalid version formats, or malformed dependency specifications.
# Debugging gemspec issues
begin
spec = Gem::Specification.load("problematic.gemspec")
spec.validate
rescue Gem::InvalidSpecificationException => e
puts "Specification errors:"
e.message.split("\n").each { |error| puts " - #{error}" }
# Common fixes
if e.message.include?("version")
puts "Fix: Ensure version follows semantic versioning (x.y.z)"
end
if e.message.include?("email")
puts "Fix: Provide valid author email address"
end
if e.message.include?("files")
puts "Fix: Verify all specified files exist"
puts "Missing files:"
spec.files.each do |file|
puts " - #{file}" unless File.exist?(file)
end
end
end
Dependency conflicts arise when gem versions cannot satisfy all requirements simultaneously. Ruby's dependency resolver attempts to find compatible versions, failing when constraints create unsolvable scenarios.
# Dependency debugging tools
module GemDebugger
def self.analyze_conflicts(gem_name)
spec = Gem::Specification.find_by_name(gem_name)
puts "Analyzing dependencies for #{gem_name} #{spec.version}"
spec.dependencies.each do |dep|
puts "\n#{dep.name} #{dep.requirement}"
available = Gem::Specification.find_all_by_name(dep.name)
compatible = available.select { |s| dep.match?(s.name, s.version) }
if compatible.empty?
puts " ERROR: No compatible versions found"
puts " Available: #{available.map(&:version).join(', ')}"
else
puts " Compatible: #{compatible.map(&:version).join(', ')}"
end
# Check for transitive conflicts
compatible.each do |compat_spec|
compat_spec.dependencies.each do |trans_dep|
next if trans_dep.name == gem_name
trans_available = Gem::Specification.find_all_by_name(trans_dep.name)
trans_compatible = trans_available.select { |s| trans_dep.match?(s.name, s.version) }
if trans_compatible.empty?
puts " WARNING: Transitive dependency #{trans_dep.name} #{trans_dep.requirement} not satisfiable"
end
end
end
end
rescue Gem::LoadError => e
puts "ERROR: Cannot find gem #{gem_name}: #{e.message}"
end
end
Build failures often stem from missing system dependencies, incorrect file paths, or platform incompatibilities. The gem build process provides limited error context, requiring systematic debugging approaches.
# Build validation script
class BuildValidator
def self.check_gemspec(gemspec_path)
errors = []
warnings = []
begin
spec = Gem::Specification.load(gemspec_path)
rescue => e
errors << "Failed to load gemspec: #{e.message}"
return { errors: errors, warnings: warnings }
end
# Validate required fields
required_fields = [:name, :version, :authors, :files, :require_paths]
required_fields.each do |field|
if spec.send(field).nil? || spec.send(field).empty?
errors << "Missing required field: #{field}"
end
end
# Check file existence
spec.files.each do |file|
unless File.exist?(file)
if file.include?("README") || file.include?("LICENSE")
warnings << "Recommended file missing: #{file}"
else
errors << "Required file missing: #{file}"
end
end
end
# Validate version format
unless spec.version.to_s =~ /\A\d+\.\d+\.\d+/
warnings << "Version should follow semantic versioning (x.y.z)"
end
# Check for common mistakes
if spec.files.any? { |f| f.include?("test/") || f.include?("spec/") }
warnings << "Test files included in gem package"
end
if spec.executables.any? && spec.bindir != "exe" && spec.bindir != "bin"
warnings << "Non-standard bin directory: #{spec.bindir}"
end
{ errors: errors, warnings: warnings }
end
def self.report(gemspec_path)
result = check_gemspec(gemspec_path)
if result[:errors].any?
puts "ERRORS (must fix):"
result[:errors].each { |error| puts " ✗ #{error}" }
end
if result[:warnings].any?
puts "WARNINGS (should fix):"
result[:warnings].each { |warning| puts " ⚠ #{warning}" }
end
puts "✓ Build validation passed" if result[:errors].empty? && result[:warnings].empty?
result[:errors].empty?
end
end
Runtime debugging requires tracking gem loading, version conflicts, and require path issues. Ruby's $LOADED_FEATURES
and Gem.loaded_specs
provide introspection into the current gem environment.
# Runtime gem debugging
module GemRuntime
def self.debug_loading
puts "Loaded gems:"
Gem.loaded_specs.each do |name, spec|
puts " #{name} #{spec.version} (#{spec.full_gem_path})"
end
puts "\nLoad path conflicts:"
conflicts = find_load_path_conflicts
conflicts.each do |path, sources|
puts " #{path}:"
sources.each { |source| puts " - #{source}" }
end
end
private
def self.find_load_path_conflicts
path_sources = Hash.new { |h, k| h[k] = [] }
$LOAD_PATH.each do |path|
if path.include?("gems/")
gem_match = path.match(/gems\/([^\/]+)-([^\/]+)/)
if gem_match
path_sources[gem_match[1]] << "#{gem_match[1]}-#{gem_match[2]} (#{path})"
end
else
path_sources["system"] << path
end
end
path_sources.select { |_, sources| sources.length > 1 }
end
end
Production Patterns
Production gem deployment requires version management, security considerations, and distribution strategies. Ruby gems in production environments face unique challenges including dependency resolution, security updates, and compatibility maintenance across deployment targets.
Version management follows semantic versioning principles with automation through continuous integration. The versioning strategy affects dependency resolution and upgrade paths for consuming applications.
# lib/production_gem/version.rb
module ProductionGem
# Semantic versioning with build metadata
MAJOR = 2
MINOR = 3
PATCH = 1
BUILD = ENV["BUILD_NUMBER"] || "dev"
VERSION = [MAJOR, MINOR, PATCH].join(".")
VERSION_WITH_BUILD = [VERSION, BUILD].join("-")
# Release metadata
RELEASE_DATE = Date.parse("2024-01-15")
RUBY_REQUIREMENT = ">= 2.7.0"
def self.compatible?(ruby_version)
Gem::Requirement.new(RUBY_REQUIREMENT).satisfied_by?(Gem::Version.new(ruby_version))
end
def self.release_info
{
version: VERSION,
build: BUILD,
released: RELEASE_DATE,
ruby_required: RUBY_REQUIREMENT,
compatible: compatible?(RUBY_VERSION)
}
end
end
Security hardening involves dependency auditing, vulnerability scanning, and secure distribution practices. Production gems require regular security assessments and rapid response capabilities for vulnerability remediation.
# security/audit.rb
require "bundler/audit"
require "json"
class SecurityAuditor
def self.run_comprehensive_audit
report = {
timestamp: Time.now.iso8601,
ruby_version: RUBY_VERSION,
bundler_version: Bundler::VERSION,
vulnerabilities: [],
advisories: [],
recommendations: []
}
# Bundler audit
begin
Bundler::Audit::Database.update!
scanner = Bundler::Audit::Scanner.new
scanner.scan do |result|
case result
when Bundler::Audit::Scanner::InsecureSource
report[:vulnerabilities] << {
type: "insecure_source",
source: result.source,
severity: "high"
}
when Bundler::Audit::Scanner::UnpatchedGem
report[:vulnerabilities] << {
type: "unpatched_gem",
gem: result.gem.name,
version: result.gem.version.to_s,
advisory: result.advisory.id,
severity: result.advisory.criticality,
title: result.advisory.title,
patched_versions: result.advisory.patched_versions.map(&:to_s)
}
end
end
rescue => e
report[:errors] = ["Audit failed: #{e.message}"]
end
# Custom security checks
check_file_permissions(report)
check_sensitive_data(report)
generate_report(report)
end
private
def self.check_file_permissions(report)
sensitive_files = ["lib/**/*.rb", "bin/*", "exe/*"]
Dir.glob(sensitive_files).each do |file|
mode = File.stat(file).mode & 0777
if mode & 0002 != 0 # World writable
report[:vulnerabilities] << {
type: "file_permissions",
file: file,
mode: mode.to_s(8),
severity: "medium",
recommendation: "Remove world write permissions"
}
end
end
end
def self.check_sensitive_data(report)
patterns = {
api_key: /(?:api[_-]?key|access[_-]?token)\s*[:=]\s*['"][^'"]+['"]/i,
password: /password\s*[:=]\s*['"][^'"]+['"]/i,
secret: /secret\s*[:=]\s*['"][^'"]+['"]/i
}
Dir.glob("lib/**/*.rb").each do |file|
content = File.read(file)
patterns.each do |type, pattern|
if content.match(pattern)
report[:vulnerabilities] << {
type: "sensitive_data",
subtype: type,
file: file,
severity: "high",
recommendation: "Move sensitive data to environment variables"
}
end
end
end
end
def self.generate_report(report)
puts "Security Audit Report - #{report[:timestamp]}"
puts "Ruby: #{report[:ruby_version]} | Bundler: #{report[:bundler_version]}"
puts
if report[:vulnerabilities].empty?
puts "✓ No vulnerabilities found"
else
puts "⚠ #{report[:vulnerabilities].length} vulnerabilities found:"
report[:vulnerabilities].group_by { |v| v[:severity] }.each do |severity, vulns|
puts "\n#{severity.upcase} (#{vulns.length}):"
vulns.each do |vuln|
puts " - #{vuln[:type]}: #{vuln[:gem] || vuln[:file]}"
puts " #{vuln[:title] || vuln[:recommendation]}"
end
end
end
# Save detailed report
File.write("security_report.json", JSON.pretty_generate(report))
report[:vulnerabilities].empty?
end
end
Distribution strategies include multiple repository deployment, CDN integration, and mirror synchronization. Production gems require reliable distribution infrastructure with fallback mechanisms and geographic distribution.
# deployment/publisher.rb
class GemPublisher
REPOSITORIES = {
rubygems: {
host: "https://rubygems.org",
api_key_env: "RUBYGEMS_API_KEY",
primary: true
},
github: {
host: "https://rubygems.pkg.github.com/company",
api_key_env: "GITHUB_TOKEN",
private: true
},
internal: {
host: "https://gems.internal.company.com",
api_key_env: "INTERNAL_GEMS_KEY",
private: true
}
}.freeze
def self.publish_multi_target(gem_file, targets: [:internal, :github])
results = {}
targets.each do |target|
config = REPOSITORIES[target]
next unless config
begin
result = publish_to_repository(gem_file, config)
results[target] = result
puts "✓ Published to #{target}: #{result[:version]}"
rescue => e
results[target] = { error: e.message }
puts "✗ Failed to publish to #{target}: #{e.message}"
end
end
# Only publish to public repository if all private targets succeed
if targets.include?(:rubygems) && results.values.none? { |r| r[:error] }
begin
result = publish_to_repository(gem_file, REPOSITORIES[:rubygems])
results[:rubygems] = result
puts "✓ Published to RubyGems: #{result[:version]}"
rescue => e
results[:rubygems] = { error: e.message }
puts "✗ Failed to publish to RubyGems: #{e.message}"
end
end
results
end
private
def self.publish_to_repository(gem_file, config)
api_key = ENV[config[:api_key_env]]
raise "Missing API key: #{config[:api_key_env]}" unless api_key
cmd = ["gem", "push", gem_file]
cmd += ["--host", config[:host]] if config[:host] != "https://rubygems.org"
cmd += ["--key", api_key]
output = `#{cmd.join(" ")} 2>&1`
success = $?.success?
unless success
raise "Push failed: #{output}"
end
# Extract version from gem filename
version = gem_file[/-(\d+\.\d+\.\d+)\.gem$/, 1]
{
version: version,
repository: config[:host],
timestamp: Time.now.iso8601,
output: output.strip
}
end
end
Testing Strategies
Gem testing requires comprehensive strategies covering unit tests, integration scenarios, version compatibility, and platform verification. Ruby gem testing faces unique challenges including dependency isolation, version matrix testing, and cross-platform validation.
Unit testing focuses on gem functionality isolation with careful attention to dependency mocking and state management. Ruby's testing frameworks provide gem-specific testing patterns and assertion methods.
# test/test_helper.rb
require "minitest/autorun"
require "minitest/pride"
require "mocha/minitest"
# Test gem in isolation
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
require "my_gem"
class MyGemTest < Minitest::Test
def setup
@original_env = ENV.to_hash
# Reset gem state between tests
MyGem.reset! if MyGem.respond_to?(:reset!)
end
def teardown
ENV.replace(@original_env)
# Clean up any global state
MyGem.instance_variables.each do |var|
MyGem.remove_instance_variable(var)
end
end
end
# Dependency isolation helpers
module TestHelpers
def with_isolated_load_path
original_load_path = $LOAD_PATH.dup
original_loaded_features = $LOADED_FEATURES.dup
yield
ensure
$LOAD_PATH.replace(original_load_path)
$LOADED_FEATURES.replace(original_loaded_features)
end
def mock_gem_dependency(gem_name, version = "1.0.0")
mock_spec = mock("gem_spec")
mock_spec.stubs(:name).returns(gem_name)
mock_spec.stubs(:version).returns(Gem::Version.new(version))
Gem::Specification.stubs(:find_by_name).with(gem_name).returns(mock_spec)
yield
ensure
Gem::Specification.unstub(:find_by_name)
end
end
Integration testing validates gem behavior within realistic application contexts, including Rails applications, Rack middleware, and CLI environments. These tests verify gem loading, configuration, and interaction patterns.
# test/integration/rails_integration_test.rb
require "test_helper"
require "rails"
require "active_record"
class RailsIntegrationTest < MyGemTest
def setup
super
create_test_rails_app
end
def teardown
cleanup_test_rails_app
super
end
def test_gem_initializes_with_rails
with_test_rails_app do |app|
app.initialize!
assert MyGem.configured?
assert_equal "test", MyGem.environment
assert_includes MyGem.middleware_stack, MyGem::RailsMiddleware
end
end
def test_activerecord_integration
with_test_rails_app do |app|
app.initialize!
# Test ActiveRecord extension
model_class = Class.new(ActiveRecord::Base) do
self.table_name = "test_models"
include MyGem::ModelExtensions
end
instance = model_class.new(name: "test")
assert_respond_to instance, :my_gem_method
assert_equal "processed: test", instance.my_gem_method
end
end
private
def create_test_rails_app
@test_app_dir = Dir.mktmpdir("my_gem_test_rails")
# Minimal Rails application
@app_file = File.join(@test_app_dir, "application.rb")
File.write(@app_file, <<~RUBY)
require "rails/all"
require "my_gem"
class TestApp < Rails::Application
config.eager_load = false
config.active_support.deprecation = :stderr
config.secret_key_base = "test_secret"
# Configure database
config.active_record.database_adapter = "sqlite3"
config.active_record.database = ":memory:"
end
Rails.application.initialize!
# Create test table
ActiveRecord::Schema.define do
create_table :test_models do |t|
t.string :name
t.timestamps
end
end
RUBY
end
def with_test_rails_app
original_dir = Dir.pwd
Dir.chdir(@test_app_dir)
load @app_file
yield Rails.application
ensure
Dir.chdir(original_dir)
Rails.application = nil if defined?(Rails.application)
end
def cleanup_test_rails_app
FileUtils.rm_rf(@test_app_dir) if @test_app_dir
end
end
Version compatibility testing ensures gem functionality across supported Ruby versions, gem dependencies, and platform combinations. Matrix testing requires automated infrastructure and systematic validation approaches.
# test/compatibility/version_matrix_test.rb
class VersionMatrixTest < MyGemTest
RUBY_VERSIONS = ["2.7.0", "3.0.0", "3.1.0", "3.2.0"].freeze
DEPENDENCY_VERSIONS = {
"activesupport" => ["5.2.0", "6.0.0", "6.1.0", "7.0.0"],
"concurrent-ruby" => ["1.0.0", "1.1.0", "1.2.0"]
}.freeze
def test_version_compatibility_matrix
results = {}
RUBY_VERSIONS.each do |ruby_version|
results[ruby_version] = test_ruby_version_compatibility(ruby_version)
end
generate_compatibility_report(results)
assert results.values.all? { |r| r[:compatible] }
end
private
def test_ruby_version_compatibility(ruby_version)
return { compatible: false, reason: "Cannot test current Ruby version" } if RUBY_VERSION != ruby_version
compatibility_results = {
compatible: true,
ruby_version: ruby_version,
dependency_tests: {},
feature_tests: {}
}
# Test core functionality
begin
require "my_gem"
MyGem.hello
compatibility_results[:feature_tests][:basic] = { status: :pass }
rescue => e
compatibility_results[:compatible] = false
compatibility_results[:feature_tests][:basic] = { status: :fail, error: e.message }
end
# Test with different dependency versions
DEPENDENCY_VERSIONS.each do |gem_name, versions|
compatibility_results[:dependency_tests][gem_name] = test_dependency_versions(gem_name, versions)
end
compatibility_results
end
def test_dependency_versions(gem_name, versions)
version_results = {}
versions.each do |version|
begin
with_isolated_load_path do
mock_gem_dependency(gem_name, version) do
# Test gem functionality with this dependency version
require "my_gem"
MyGem.hello
version_results[version] = { status: :pass }
end
end
rescue => e
version_results[version] = { status: :fail, error: e.message }
end
end
version_results
end
def generate_compatibility_report(results)
puts "\nVersion Compatibility Report"
puts "=" * 50
results.each do |ruby_version, result|
status = result[:compatible] ? "✓" : "✗"
puts "#{status} Ruby #{ruby_version}"
unless result[:compatible]
puts " Reason: #{result[:reason]}"
end
if result[:dependency_tests]
result[:dependency_tests].each do |dep_name, dep_results|
compatible_versions = dep_results.select { |_, r| r[:status] == :pass }.keys
puts " #{dep_name}: #{compatible_versions.join(', ')}"
end
end
end
end
end
Performance and memory testing validates gem efficiency under realistic load conditions. These tests identify performance regressions and memory leaks that could affect production deployments.
# test/performance/benchmark_test.rb
require "benchmark"
require "memory_profiler"
class BenchmarkTest < MyGemTest
def test_performance_benchmarks
puts "\nPerformance Benchmarks"
puts "=" * 30
# CPU performance
Benchmark.bm(20) do |x|
x.report("basic_operation") { 1000.times { MyGem.basic_operation("test") } }
x.report("complex_operation") { 100.times { MyGem.complex_operation(large_data_set) } }
x.report("concurrent_ops") { test_concurrent_performance }
end
# Memory usage
memory_report = MemoryProfiler.report do
1000.times { MyGem.basic_operation("test") }
end
puts "\nMemory Usage:"
puts "Total allocated: #{memory_report.total_allocated} objects"
puts "Total retained: #{memory_report.total_retained} objects"
# Assert performance thresholds
assert_performance_thresholds
end
private
def test_concurrent_performance
threads = 10.times.map do
Thread.new do
100.times { MyGem.thread_safe_operation("test") }
end
end
threads.each(&:join)
end
def large_data_set
@large_data_set ||= (1..10000).to_a.map { |i| "data_item_#{i}" }
end
def assert_performance_thresholds
# Basic operation should complete in under 1ms per call
time = Benchmark.realtime { 1000.times { MyGem.basic_operation("test") } }
assert time < 1.0, "Basic operation too slow: #{time}s for 1000 calls"
# Memory usage should be reasonable
memory_report = MemoryProfiler.report do
1000.times { MyGem.basic_operation("test") }
end
assert memory_report.total_allocated < 50000, "Too many objects allocated: #{memory_report.total_allocated}"
end
end
Reference
Core Classes and Modules
Class/Module | Purpose | Key Methods |
---|---|---|
Gem::Specification |
Gem metadata definition | #new , #validate , #files= , #dependencies |
Gem::Version |
Version parsing and comparison | #new , #<=> , #prerelease? , #segments |
Gem::Requirement |
Version constraint specification | #new , #satisfied_by? , #as_list |
Gem::Dependency |
Dependency declaration | #new , #match? , #runtime? , #development? |
Gem::Platform |
Platform identification | #new , #=~ , #cpu , #os , #version |
Gemspec Specification Fields
Field | Type | Required | Description |
---|---|---|---|
name |
String | Yes | Gem identifier for installation and require |
version |
String/Version | Yes | Version following semantic versioning |
authors |
Array | Yes | Author names for gem attribution |
email |
Array | No | Contact emails for maintainers |
summary |
String | Yes | Brief one-line gem description |
description |
String | No | Detailed gem functionality explanation |
homepage |
String | No | Project website or repository URL |
license |
String | No | License identifier (MIT, GPL-3.0, etc.) |
files |
Array | Yes | Files included in built gem package |
executables |
Array | No | Command-line executables to install |
require_paths |
Array | Yes | Directories added to $LOAD_PATH |
dependencies |
Array | No | Runtime and development dependencies |
Version Constraint Operators
Operator | Example | Meaning |
---|---|---|
= |
= 1.2.3 |
Exact version match |
> |
> 1.0.0 |
Greater than version |
>= |
>= 2.0.0 |
Greater than or equal |
< |
< 3.0.0 |
Less than version |
<= |
<= 2.5.0 |
Less than or equal |
~> |
~> 1.2.0 |
Compatible version (>= 1.2.0, < 1.3.0) |
!= |
!= 1.5.0 |
Not equal to version |
Command Line Tools
Command | Parameters | Purpose |
---|---|---|
gem build |
gemspec_file |
Build gem from specification |
gem install |
gem_file [options] |
Install gem locally |
gem push |
gem_file [--host URL] |
Publish to repository |
gem yank |
name -v version |
Remove from repository |
gem specification |
gem_name |
Display gem metadata |
gem dependency |
gem_name |
Show dependency tree |
gem cleanup |
[gem_name] |
Remove old versions |
bundle gem |
name [options] |
Generate gem scaffold |
Platform Identifiers
Platform | Ruby Constant | Description |
---|---|---|
ruby |
RUBY_PLATFORM =~ /ruby/ |
Pure Ruby implementation |
java |
RUBY_PLATFORM =~ /java/ |
JRuby implementation |
x86_64-linux |
RUBY_PLATFORM =~ /linux/ |
64-bit Linux systems |
x86_64-darwin |
RUBY_PLATFORM =~ /darwin/ |
64-bit macOS systems |
x64-mingw32 |
RUBY_PLATFORM =~ /mingw/ |
64-bit Windows MinGW |
mswin |
RUBY_PLATFORM =~ /mswin/ |
Windows Visual Studio |
Exception Hierarchy
StandardError
├── Gem::Exception
│ ├── Gem::CommandLineError
│ ├── Gem::DependencyError
│ ├── Gem::DocumentError
│ ├── Gem::EndOfYAMLException
│ ├── Gem::FilePermissionError
│ ├── Gem::FormatException
│ ├── Gem::GemNotFoundException
│ ├── Gem::InstallError
│ ├── Gem::InvalidSpecificationException
│ ├── Gem::LoadError
│ ├── Gem::OperationNotSupportedError
│ ├── Gem::RemoteError
│ ├── Gem::RemoteInstallationCancelled
│ ├── Gem::RemoteInstallationSkipped
│ ├── Gem::RemoteSourceException
│ ├── Gem::RequirementParseError
│ ├── Gem::RuntimeRequirementNotMetError
│ ├── Gem::SystemExitException
│ ├── Gem::UnsatisfiableDependencyError
│ └── Gem::VerificationError
Environment Variables
Variable | Purpose | Example |
---|---|---|
GEM_HOME |
Primary gem installation directory | /home/user/.gem/ruby/3.0.0 |
GEM_PATH |
Gem search path directories | /usr/local/lib/ruby/gems/3.0.0 |
RUBYGEMS_HOST |
Default gem repository URL | https://rubygems.org |
RUBYGEMS_API_KEY |
Authentication for gem push | API key string |
GEM_SPEC_CACHE |
Specification cache directory | /tmp/gem_spec_cache |
File Structure Conventions
gem_name/
├── lib/
│ ├── gem_name.rb # Main require file
│ └── gem_name/
│ ├── version.rb # Version constant
│ └── *.rb # Implementation files
├── bin/ or exe/
│ └── executable_name # Command-line executables
├── test/ or spec/
│ └── **/*_test.rb # Test files (excluded from gem)
├── gem_name.gemspec # Gem specification
├── Gemfile # Development dependencies
├── Rakefile # Build tasks
├── README.md # Documentation
├── LICENSE # License text
└── CHANGELOG.md # Version history