gem-builder について
gem-builderは、Ruby gemを作成・保守するRuby開発者向けのリファレンススキルです。プロジェクト構成、設定、テスト、ドキュメント、リリース、Rails連携のパターンを扱います。
主な機能
- 01標準的なgemのディレクトリとエントリーポイント
- 02gemspecと依存関係の指針
- 03設定とエラー処理のパターン
- 04MinitestとWebMockのテスト設定
- 05YARD、README、変更履歴の指針
- 06ビルド、リリース、Rails連携の実践
活用シーン
- 01Ruby gemのプロジェクト構成を作成
- 02APIクライアントgemを設計
- 03公開APIのテストとドキュメントを追加
- 04gemのリリースを準備
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ majesticlabs-dev/ majestic-marketplace --skill gem-builderインストール後、Claude Code で/gem-builderを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/advanced-patterns.mdengine-migrations.mdtemplates.md詳細
Gem Builder
基本方針
- 依存関係は最小限に:本当に必要な gem のみを追加します
- 単一責任:各クラス/モジュールは、それぞれ1つの役割を適切に果たします
- セマンティックバージョニング:SemVer(MAJOR.MINOR.PATCH)に厳密に従います
- テストカバレッジ:すべての公開メソッドにテストを用意します
- ドキュメント:YARD ドキュメント、README、CHANGELOG を用意します
- 早期に失敗させる:入力を早い段階で検証し、説明的なエラーを発生させます
Gem の構成
my_gem/
├── lib/
│ ├── my_gem.rb # Main entry point
│ ├── my_gem/
│ │ ├── version.rb # VERSION constant
│ │ ├── config.rb # Configuration class
│ │ ├── errors.rb # Error hierarchy
│ │ └── [feature].rb # Feature modules
├── test/ # Test suite
├── my_gem.gemspec # Gem specification
├── Gemfile # Development dependencies
├── Rakefile # Build tasks
├── README.md # User documentation
├── CHANGELOG.md # Version history
└── LICENSE.txt # License file
メインエントリーポイント
# lib/my_gem.rb
require_relative "my_gem/version"
require_relative "my_gem/config"
require_relative "my_gem/errors"
module MyGem
class << self
def config
@config ||= Config.new
end
def configure
yield(config)
end
def reset_configuration!
@config = nil
end
end
end
Gemspec のベストプラクティス
# my_gem.gemspec
require_relative "lib/my_gem/version"
Gem::Specification.new do |spec|
spec.name = "my_gem"
spec.version = MyGem::VERSION
spec.authors = ["Your Name"]
spec.email = ["[email protected]"]
spec.summary = "One-line description"
spec.homepage = "https://github.com/username/my_gem"
spec.license = "MIT"
spec.required_ruby_version = ">= 3.2.0"
spec.metadata["rubygems_mfa_required"] = "true"
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
# Exclude test/CI files
spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
ls.readlines("\x0", chomp: true).reject { |f| f.start_with?(*%w[bin/ test/ .github/]) }
end
spec.require_paths = ["lib"]
end
Gem の種類
| 種類 | 主な特徴 |
|---|---|
| ライブラリ | 純粋な Ruby で、外部サービスは使用しません |
| API クライアント | リソースパターンを使用する HTTP ラッパー |
| CLI ツール | spec.executables、bindir の設定 |
| Rails 連携 | ActiveSupport.on_load を使用する Railtie |
API クライアントのパターン
class Client
def initialize(api_key: nil)
@api_key = api_key || MyGem.config.api_key
raise ArgumentError, "API key required" if @api_key.to_s.empty?
end
def users = @users ||= Resources::Users.new(self)
def posts = @posts ||= Resources::Posts.new(self)
end
Rails 連携
Rails を直接 require しないでください。 遅延読み込みを使用します。
# lib/my_gem/railtie.rb
class Railtie < Rails::Railtie
initializer "my_gem.configure" do
ActiveSupport.on_load(:active_record) do
extend MyGem::Model
end
end
end
# lib/my_gem.rb
require_relative "my_gem/railtie" if defined?(Rails)
クラスマクロ DSL
searchkick、lockbox で使用されているパターンです。
# Usage: mygemname word_start: [:name]
module Model
def mygemname(**options)
unknown = options.keys - KNOWN_OPTIONS
raise ArgumentError, "Unknown: #{unknown.join(", ")}" if unknown.any?
mod = Module.new
mod.module_eval { define_method(:some_method) { options[:key] } }
include mod
class_variable_set(:@@mygemname_options, options.dup)
end
end
設定パターン
# lib/my_gem/config.rb
class Config
attr_accessor :api_key, :base_url, :timeout
attr_writer :logger
def initialize
@api_key = ENV.fetch("MY_GEM_API_KEY", nil)
@base_url = ENV.fetch("MY_GEM_BASE_URL", "https://api.example.com")
@timeout = Integer(ENV.fetch("MY_GEM_TIMEOUT", 30)) rescue 30
end
def logger
@logger ||= defined?(Rails) ? Rails.logger : Logger.new($stderr)
end
end
使用例:
MyGem.configure do |config|
config.api_key = "secret"
end
エラー処理
# lib/my_gem/errors.rb
module MyGem
class Error < StandardError
attr_reader :status, :body
def initialize(message = nil, status: nil, body: nil)
super(message)
@status, @body = status, body
end
end
class ConfigurationError < Error; end
class AuthenticationError < Error; end # 401
class ClientError < Error; end # 4xx
class ServerError < Error; end # 5xx
class NetworkError < Error; end # Connection failures
end
テストのセットアップ
# test/test_helper.rb
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
require "my_gem"
require "minitest/autorun"
require "webmock/minitest"
module TestConfig
def setup_config
WebMock.reset!
MyGem.reset_configuration!
MyGem.configure { |c| c.api_key = "test-key" }
end
def teardown_config
WebMock.reset!
MyGem.reset_configuration!
end
end
class ClientTest < Minitest::Test
include TestConfig
def setup = setup_config
def teardown = teardown_config
def test_requires_api_key
MyGem.config.api_key = nil
assert_raises(ArgumentError) { MyGem::Client.new }
end
end
ドキュメント
YARD のセットアップ(.yardopts)
--markup markdown
--no-private
lib/**/*.rb
- README.md
README のセクション
インストール、クイックスタート、設定、機能、開発、ライセンス。
CHANGELOG(Keep a Changelog)
## [1.0.0] - 2025-01-15
### Added
- Initial release
ビルドとリリース
Rakefile
require "bundler/gem_tasks"
require "minitest/test_task"
Minitest::TestTask.create
require "rubocop/rake_task"
RuboCop::RakeTask.new
task default: %i[test rubocop]
リリースワークフロー
# 1. Update lib/my_gem/version.rb
# 2. Update CHANGELOG.md
# 3. Commit and release
git commit -am "Release v1.0.0"
bundle exec rake release
アンチパターン
| 避けるもの | 代わりに使うもの |
|---|---|
method_missing | define_method |
@@class_variables | インスタンス変数を使った class << self |
| Rails を直接 require する | ActiveSupport.on_load |
| 多数の実行時依存関係 | 標準ライブラリを優先する |
| Gemfile.lock をコミットする | アプリケーションでのみロックする |
| 大がかりな DSL | 明示的な Ruby の記述 |
autoload | require_relative |
ベストプラクティスのチェックリスト
構成:
- 標準的なディレクトリ構成
- バージョン情報を1か所に集約
- 文字列リテラルを凍結
Gemspec:
- すべてのメタデータを記入
-
rubygems_mfa_required= true - 実行時依存関係を最小限にする
設定:
- 環境変数へのフォールバック
- ブロックベースの DSL
- テストしやすいリセットメソッド
エラー処理:
- 独自のエラー階層
- 説明的なメッセージ
- ステータスと本文を保持
テスト:
- テスト間の分離
- HTTP 通信には WebMock を使用
- 成功ケースと失敗ケース
ドキュメント:
- 公開メソッドに YARD を記述
- クイックスタートを含む README
- CHANGELOG
Rails(該当する場合):
-
if defined?(Rails)を使って任意で有効化 - 独立した名前空間
-
ActiveSupport.on_loadフック
参考資料
references/templates.md- コピーして使えるテンプレート(CI、gemspec、README)references/advanced-patterns.md- データベースアダプター、複数バージョンのテストreferences/engine-migrations.md- Rails エンジン内にマイグレーションを保持する方法
コメント
Markdown 対応