User is starting a new Ruby application (gem, Rails app, Sinatra service, CLI tool, background worker) and needs a production-ready project structure from scratch
User wants to establish Ruby version management, Bundler configuration, and dependency isolation for a team environment
Guides expert-level ruby project setup implementation: ruby and best-practices decision frameworks, production-ready patterns, and concrete templates for ruby project setup workflows.
How much does it cost?+
Nothing. It ships with Murage, which is open source. Your bots run on the AI plan you already have.
Can I change it?+
Yes. Open it in Settings → Skills to read, edit or duplicate it. Skill Guard scans it again after every edit.
Give your first job to Murage.
Download the app, connect the AI you already pay for, and tell your Chief of Staff what needs doing. Plan on about ten minutes from install to a working team.
User needs to configure code quality tooling -- RuboCop, RSpec, SimpleCov, Brakeman, or Bundler Audit -- and integrate them into a CI pipeline
User is converting an informal Ruby script or prototype into a maintainable project with proper structure, testing, and release conventions
User asks about .ruby-version, Gemfile best practices, bundler/setup, frozen string literals, or .rubocop.yml baseline configuration
User wants to set up a Ruby gem from scratch including gemspec, versioning, changelog, and Rake release tasks
User needs guidance on local development workflow parity -- ensuring developers run the same Ruby version, same Bundler version, and same environment variable setup as CI
Do NOT use this skill when:
User needs Rails-specific application architecture guidance -- that warrants a dedicated Rails architecture skill covering MVC structure, Active Record patterns, and asset pipeline
User is asking about Ruby on Rails API design, serializers, or authentication -- those are framework-level concerns beyond project scaffolding
User needs help with a specific Ruby language feature, metaprogramming technique, or algorithmic problem -- use a general Ruby programming skill
User is asking about deploying a Ruby application to Heroku, Kubernetes, or AWS -- use infrastructure or deployment skills
User needs help with a non-Ruby language project setup -- check sibling skills for Python, Node.js, Go, or Rust project setup
User has an existing, mature Ruby codebase and is asking about refactoring patterns -- this skill covers initial setup and greenfield configuration, not legacy modernization
Process
Step 1: Determine Project Type and Scope
Before writing a single file, classify the project type -- each has a distinct canonical structure.
Ruby Gem: Needs gemspec, lib/gem_name.rb, lib/gem_name/version.rb, spec/, Rakefile with release task, CHANGELOG.md, and CODE_OF_CONDUCT.md. Use bundle gem <name> as the scaffold, then customize.
Standalone CLI application: Needs exe/ or bin/ directory, lib/ for logic, an entry-point binary file with a shebang (#!/usr/bin/env ruby), and typically Thor or OptionParser for argument parsing.
Rack/Sinatra service: Needs config.ru, app/ or lib/ for route handlers, Gemfile with explicit Rack/Sinatra versions, and a Procfile for process management.
Background worker / script: Needs lib/ for domain logic, bin/ for entry points, and careful separation of configuration loading from business logic to enable testing.
Rails application: Use rails new with --database, --skip-* flags as needed; this skill provides the surrounding project hygiene layer, not Rails-specific architecture.
Confirm these questions before proceeding:
Will this be released as a gem (public or private registry)?
Is this a team project or solo? Teams need stricter automation.
What Ruby version is required or preferred? Check for end-of-life status -- Ruby versions receive security support for roughly 2 years after release. Prefer the latest stable minor version (e.g., 3.3.x as of 2024).
Does the project need to target multiple Ruby versions (gem compatibility)?
Step 2: Set Up Ruby Version Management
Ruby version management is the foundation everything else depends on.
Use rbenv or mise (formerly rtx) as the version manager. rbenv is the most widely supported; mise handles multi-language version management and is gaining adoption rapidly.
Avoid system Ruby for any project work -- system Ruby is often outdated (macOS ships with 2.6.x era), lacks write permissions to gem directories, and creates environment pollution.
Create .ruby-version in the project root containing only the exact version string: 3.3.4. No ruby- prefix, no comments.
Lock to a patch version in .ruby-version, not just a minor version. This ensures every developer and CI runner uses identical behavior.
In the gemspec (for gems), specify required_ruby_version with a range: >= 3.1.0 restricts users from installing on unsupported Rubies. Use ~> 3.1 only if you genuinely test against all 3.x versions.
In CI, test against the exact version in .ruby-version first; optionally add a job for the next minor version to catch forward-compatibility issues early.
Add .ruby-version to version control. Never add it to .gitignore.
Step 3: Configure Bundler and the Gemfile
Bundler is the dependency contract for the project. Poor Gemfile hygiene is the most common source of environment inconsistency.
Lock the Bundler version itself. Add a BUNDLED WITH section by running bundle install locally. Commit Gemfile.lock for applications; do NOT commit Gemfile.lock for gems (it blocks consumer version resolution).
Specify gem versions with pessimistic constraints (~>) rather than open-ended requirements: gem 'rack', '~> 3.0' allows patch updates but prevents breaking minor-version changes.
Group gems correctly:
ruby
# Gemfile
source 'https://rubygems.org'
ruby file: '.ruby-version' # reads from .ruby-version automatically
gem 'dry-validation', '~> 1.10'
gem 'zeitwerk', '~> 2.6'
group :development do
gem 'rubocop', '~> 1.65', require: false
gem 'rubocop-rspec', require: false
gem 'rubocop-performance', require: false
end
group :development, :test do
gem 'rspec', '~> 3.13'
gem 'factory_bot', '~> 6.4'
gem 'faker', '~> 3.3'
end
group :test do
gem 'simplecov', require: false
gem 'webmock', '~> 3.23'
gem 'vcr', '~> 6.2'
end
Set require: false on development tools (RuboCop, Brakeman, etc.) -- they should never be auto-required in application code.
Configure Bundler path via .bundle/config or environment variable:
BUNDLE_PATH: vendor/bundle # preferred for Docker-based projects
For non-Docker local dev, the default ~/.bundle global path is acceptable.
Run bundle install --frozen in CI to prevent accidental Gemfile.lock mutations during builds.
Step 4: Establish Directory Structure
A consistent directory layout enables new contributors to navigate the project immediately.
lib/gem_name.rb loads all internal files via require_relative or Zeitwerk autoloader -- it is the public contract of the gem.
lib/gem_name/version.rb contains ONLY VERSION = '1.0.0'. The gemspec requires this file to read the version. No other requires here.
bin/console should load require 'irb' and require_relative '../lib/gem_name' then call IRB.start.
bin/setup runs bundle install, any database setup commands, and prints a success message. Make it idempotent.
The exe/ directory (for executable gems) is separate from bin/ (for dev scripts) -- this is Bundler convention.
Step 5: Configure RuboCop for Code Quality
RuboCop is the standard static analysis and style enforcement tool for Ruby. Configure it thoughtfully -- a poorly configured RuboCop creates more friction than value.
Always set TargetRubyVersion -- without it, RuboCop assumes an old default and misses version-specific cops.
Enable NewCops: enable so you catch new offenses as RuboCop versions are updated rather than accumulating tech debt.
Add # frozen_string_literal: true to every Ruby file -- this enables string deduplication and prevents accidental string mutation. RuboCop's Style/FrozenStringLiteralComment cop enforces this.
Run bundle exec rubocop --auto-gen-config on legacy codebases to generate a .rubocop_todo.yml baseline, then reduce it over time.
Integrate RuboCop as a git pre-commit hook using overcommit or lefthook to catch violations before push.
Step 6: Set Up RSpec and Test Infrastructure
RSpec is the dominant testing framework for Ruby. Configure it for speed, clarity, and reliable CI behavior.
spec/spec_helper.rb:
ruby
# frozen_string_literal: true
require 'simplecov'
SimpleCov.start do
add_filter '/spec/'
add_filter '/vendor/'
minimum_coverage 85
minimum_coverage_by_file 70
end
require 'payment_gateway'
require 'webmock/rspec'
require 'factory_bot'
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true # prevents typos in stubbed method names
mocks.verify_doubled_const_names = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
config.filter_run_when_matching :focus
config.disable_monkey_patching!
config.warnings = true
config.order = :random
config.seed = rand(0xFFFF) # print seed so failures are reproducible
config.include FactoryBot::Syntax::Methods
config.before(:suite) do
FactoryBot.find_definitions
WebMock.disable_net_connect!(allow_localhost: true)
end
end
.rspec file (project root):
--require spec_helper
--format documentation
--color
--order random
Key testing configuration decisions:
Set config.warnings = true -- Ruby warnings surface deprecations and potential bugs early.
Enable verify_partial_doubles: true -- without this, stubbing a method that doesn't exist silently passes, masking misnamed methods.
Randomize test order to catch order-dependent failures. When a failure is found, reproduce it with rspec --seed <number>.
Set minimum_coverage 85 in SimpleCov as a starting threshold for new projects. Coverage below 70% on individual files should be a warning signal.
Use add_filter '/spec/' in SimpleCov -- spec files should not be included in their own coverage measurement.
Step 7: Create CI Pipeline Configuration
CI is the enforcement mechanism for all quality standards. A CI pipeline that doesn't run quality checks is a quality check that doesn't exist.
.github/workflows/ci.yml (GitHub Actions):
yaml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
name: Ruby ${{ matrix.ruby }} Tests
runs-on: ubuntu-latest
strategy:
matrix:
ruby: ['3.3'] # add '3.2', '3.1' for gems targeting multiple versions
steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true # runs bundle install and caches automatically
- name: Run RuboCop
run: bundle exec rubocop --format github
- name: Run tests
run: bundle exec rspec
env:
COVERAGE: true
- name: Run Brakeman (security scan)
run: bundle exec brakeman --no-pager
if: hashFiles('config/application.rb') != '' # only for Rails apps
- name: Run bundler-audit
run: |
bundle exec bundle-audit update
bundle exec bundle-audit check
CI configuration decisions:
Use ruby/setup-ruby with bundler-cache: true -- this action handles both rbenv setup and Bundler caching automatically. A cache hit reduces CI time from 60-90 seconds of gem installation to under 5 seconds.
Run RuboCop with --format github on GitHub Actions -- this format creates inline PR annotations rather than plain text output.
Run bundle-audit in every CI job -- it checks Gemfile.lock against the Ruby Advisory Database for known CVEs and takes under 2 seconds.
For gems, add a matrix across ['3.1', '3.2', '3.3'] to confirm compatibility.
Set a COVERAGE=true environment variable to conditionally run SimpleCov -- avoid running coverage locally unless needed, as it adds 10-15% overhead to test execution.
Step 8: Add Developer Ergonomics and Documentation Scaffolding
A project setup is incomplete without the tooling that makes day-to-day development productive.
CHANGELOG.md -- start with a template following Keep a Changelog format:
markdown
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to Semantic Versioning.
## [Unreleased]
### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security
Output Format
When delivering a Ruby project setup recommendation, use this structure:
## Ruby Project Setup: [Project Name]
### Project Classification
- Type: [Gem | Rails App | Sinatra Service | CLI Tool | Background Worker]
- Ruby Version: [e.g., 3.3.4]
- Team Size: [number]
- Multi-version Support: [Yes -- 3.1, 3.2, 3.3 | No -- 3.3 only]
### Toolchain Decision Matrix
| Tool | Selection | Rationale |
|-------------------|-------------------|--------------------------------------------|
| Version Manager | rbenv / mise | [specific reason based on team context] |
| Bundler Version | 2.5.x | Matches .ruby-version Ruby for consistency |
| Testing Framework | RSpec 3.13 | [rationale] |
| Linting | RuboCop 1.65+ | [rationale] |
| Coverage | SimpleCov | Threshold: 85% overall, 70% per file |
| Security Scan | bundler-audit | [rationale] |
| Pre-commit Hooks | lefthook / overcommit | [rationale] |
### Directory Structure
[full tree as described in Step 4]
### Configuration Files
[all files from Steps 2-8 with content]
### CI Pipeline
[full workflow YAML]
### Setup Commands
```bash
# Initial setup
git clone <repo>
cd <project>
rbenv install # reads .ruby-version automatically
bin/setup # installs gems, copies .env.example
Quality Thresholds
RuboCop: 0 offenses (enforced in CI)
Test Coverage: >= 85% overall (enforced by SimpleCov)
Security: 0 known CVEs (enforced by bundler-audit)
Test Suite Runtime: target < 30 seconds for unit tests
Next Steps
[specific immediate action]
[specific immediate action]
[specific immediate action]
---
## Rules
1. **Never use system Ruby for a project.** System Ruby on macOS is Apple's internal tooling Ruby and is typically 2 or more major versions behind. It has restricted gem installation paths and causes gem permission errors. Always install a project-specific Ruby via rbenv, mise, or asdf and confirm with `which ruby` and `ruby --version` before proceeding.
2. **Commit `Gemfile.lock` for applications, never for gems.** Applications lock dependencies so all developers and CI runners get byte-for-byte identical gem trees. Gems must NOT commit `Gemfile.lock` because doing so prevents consumers from resolving compatible versions with their own dependency trees. This is the single most common Bundler confusion for new Ruby developers.
3. **Every Ruby file must start with `# frozen_string_literal: true`.** This pragma has been the standard since Ruby 2.3 and was planned as the default for Ruby 3.0 (deferred but recommended). It prevents `String#<<` and similar mutating operations from accidentally mutating shared string literals, and enables Ruby to deduplicate identical string literals, reducing heap allocations by 15-30% in string-heavy code.
4. **Set `TargetRubyVersion` explicitly in `.rubocop.yml`.** Without it, RuboCop defaults to a conservative version assumption and disables cops for newer language features. This means real style issues introduced by Ruby 3.x features will silently pass linting.
5. **Use `verify_partial_doubles: true` in RSpec.** Without this setting, `allow(object).to receive(:nonexistent_method)` passes silently. This masks method renames and typos in test doubles, creating tests that pass while the actual code is broken. Enable it from day one.
6. **Never put secrets or credentials in `.env.example` -- put placeholder names and safe example formats only.** The `.env.example` file communicates to developers what variables are required. The actual `.env` file must be in `.gitignore` and must never be committed. Rotate any credential that touches version control immediately.
7. **Pin RuboCop to a minor version range, not open-ended.** RuboCop introduces new cops on every release. An open-ended `gem 'rubocop'` dependency will cause CI failures on Monday morning when a new RuboCop version is released over the weekend. Use `gem 'rubocop', '~> 1.65', require: false` to control upgrade timing.
8. **`lib/gem_name/version.rb` must contain only the VERSION constant.** The `gemspec` requires this file to read the version without loading the entire gem (and its potentially unresolved dependencies). If you add `require` statements to `version.rb`, the gemspec loading will fail in environments where those dependencies are not yet installed.
9. **Run `bundle exec` for every gem-installed command in scripts and CI.** Running `rubocop` instead of `bundle exec rubocop` will use whatever version is globally installed on the machine (or none at all), bypassing Bundler's resolved dependency tree and producing inconsistent results between environments.
10. **Test the `bin/setup` script on a clean environment before merging.** The most common cause of onboarding friction is a `bin/setup` script written by someone who already has the environment configured. Test it in a fresh Docker container or ask a new team member to run it on their machine. Every missing step is a future onboarding failure.
---
## Edge Cases
### Multi-Ruby Version Support for Gems
When a gem must support multiple Ruby versions (e.g., Ruby 3.1, 3.2, and 3.3), configure the CI matrix accordingly and be explicit in the `gemspec`:
```ruby
spec.required_ruby_version = '>= 3.1.0'
Test against all supported versions in CI. Use RUBY_VERSION constant guards only as a last resort -- prefer conditional require and feature detection over version branching. When a feature is available in 3.2+ only, document the minimum version requirement rather than shimming behavior.
Windows Development Environments
Some gems have native extensions that behave differently on Windows. If team members use Windows, specify the x86_64-mingw32 or x64-mingw-ucrt platform in the Gemfile.lock. Use bundle lock --add-platform x86_64-mingw32 to add Windows platform resolution. Some gems (notably bcrypt, nokogiri) have Windows-specific binary gem variants -- configure Bundler to prefer pre-compiled binaries: BUNDLE_FORCE_RUBY_PLATFORM: false.
Monorepo With Multiple Ruby Services
When multiple Ruby services share a monorepo, each service needs its own Gemfile, Gemfile.lock, .ruby-version, and .rubocop.yml. Do NOT share a single top-level Gemfile across services -- dependency conflicts are inevitable and the blast radius of bundle update becomes enormous. Share common RuboCop configuration by using inheritance: create a .rubocop-shared.yml at the monorepo root and include it in each service's .rubocop.yml with inherit_from: ../../.rubocop-shared.yml.
Private Gem Registry
When using a private gem registry (GitHub Packages, Gemstash, JFrog Artifactory), configure Bundler to authenticate without embedding credentials in Gemfile:
ruby
# Gemfile
source 'https://gems.example.com' do
gem 'internal_library', '~> 2.1'
end
Store credentials in .bundle/config (not committed) or as environment variables: BUNDLE_GEMS__EXAMPLE__COM: "token:$TOKEN". The environment variable naming convention is the source URL with dots and slashes replaced by double underscores and uppercased.
Legacy Codebase With No Tests Adopting RuboCop
Running rubocop on a codebase with zero prior enforcement will produce thousands of offenses. Do not attempt to fix them all at once -- this creates enormous, unreviable PRs with high merge conflict risk. Run bundle exec rubocop --auto-gen-config to generate .rubocop_todo.yml with all existing violations excluded. Then add a TODO count comment to each entry noting the offense count. Reduce the todo list by 10-20 offenses per PR over multiple weeks, prioritizing high-signal cops (security, correctness) over low-signal ones (style).
Docker-Based Development Environments
When developers use Docker for local development, set BUNDLE_PATH: /usr/local/bundle in the container and volume-mount it separately from the application code so gems persist across container rebuilds. The .ruby-version file is still useful as documentation, but pin the FROM ruby:3.3.4 base image in Dockerfile to a patch version -- the ruby:3.3 tag updates to new patch releases and can introduce unexpected behavior.
Project Requiring Custom require Load Order
When a project loads many files and load order matters (circular dependency risks with manual require), introduce Zeitwerk as the autoloader:
Zeitwerk uses file-system conventions to autoload constants -- lib/payment_gateway/client.rb defines PaymentGateway::Client. This eliminates require_relative chains entirely and prevents load-order bugs. Zeitwerk also enables hot-reloading in development via loader.reload. Verify the autoload map during setup with loader.eager_load in tests.
Example
Input: "I'm starting a new Ruby gem called data_validator that validates data structures against schemas. It needs to support Ruby 3.1 through 3.3, will be published to RubyGems, and I'm working with a team of 4. We want strict quality enforcement from day one."
Widest team tooling support; mise acceptable alternative
Bundler Version
2.5.x
Included with Ruby 3.3; do NOT commit Gemfile.lock (gem policy)
Testing
RSpec 3.13
Industry standard for gems; rich matcher ecosystem
Linting
RuboCop 1.65 + rspec
rubocop-rspec adds RSpec-specific cops essential for test quality
Coverage
SimpleCov 0.22
90% overall threshold appropriate for new gem with full control
Security
bundler-audit
CVE check on Gemfile.lock; fast, zero false positives
Pre-commit
lefthook
Fast, parallel hook execution; no Ruby runtime dependency
Initial Scaffold
bash
# Install the target Ruby version
rbenv install 3.3.4
# Scaffold with Bundler's built-in generator
bundle gem data_validator \
--test=rspec \
--linter=rubocop \
--changelog \
--ci=github
cd data_validator
.ruby-version
3.3.4
data_validator.gemspec
ruby
# frozen_string_literal: true
require_relative 'lib/data_validator/version'
Gem::Specification.new do |spec|
spec.name = 'data_validator'
spec.version = DataValidator::VERSION
spec.authors = ['Your Team']
spec.email = ['team@example.com']
spec.summary = 'Validates data structures against declarative schemas.'
spec.description = 'DataValidator provides a DSL for defining schemas and validating ' \
'Ruby Hash and Array structures with detailed error reporting.'
spec.homepage = 'https://github.com/yourorg/data_validator'
spec.license = 'MIT'
spec.required_ruby_version = '>= 3.1.0'
spec.metadata['homepage_uri'] = spec.homepage
spec.metadata['source_code_uri'] = spec.homepage
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
spec.metadata['rubygems_mfa_required'] = 'true' # enforce MFA on gem publishing
spec.files = Dir[
'lib/**/*.rb',
'exe/**/*',
'CHANGELOG.md',
'LICENSE.txt',
'README.md'
]
spec.require_paths = ['lib']
# Runtime dependencies -- keep this minimal
# spec.add_dependency 'dry-core', '~> 1.0'
end
Gemfile
ruby
# frozen_string_literal: true
source 'https://rubygems.org'
# Load gemspec's runtime dependencies
gemspec
ruby file: '.ruby-version'
group :development do
gem 'rubocop', '~> 1.65', require: false
gem 'rubocop-rspec', require: false
gem 'rubocop-performance', require: false
gem 'rake', '~> 13.1'
end
group :development, :test do
gem 'rspec', '~> 3.13'
end
group :test do
gem 'simplecov', '~> 0.22', require: false
gem 'webmock', '~> 3.23'
end
lib/data_validator/version.rb
ruby
# frozen_string_literal: true
module DataValidator
VERSION = '0.1.0'
end
lib/data_validator.rb
ruby
# frozen_string_literal: true
require 'zeitwerk'
loader = Zeitwerk::Loader.for_gem
loader.setup
module DataValidator
class Error < StandardError; end
class SchemaError < Error; end
class ValidationError < Error
attr_reader :errors
def initialize(errors)
@errors = errors
super("Validation failed: #{errors.join(', ')}")
end
end
end
# frozen_string_literal: true
if ENV['COVERAGE']
require 'simplecov'
SimpleCov.start do
add_filter '/spec/'
add_filter '/vendor/'
minimum_coverage 90
minimum_coverage_by_file 75
end
end
require 'data_validator'
require 'webmock/rspec'
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
mocks.verify_doubled_const_names = true
end
config.disable_monkey_patching!
config.warnings = true
config.order = :random
config.before(:suite) do
WebMock.disable_net_connect!
end
end
rbenv install 3.3.4 # install Ruby (reads .ruby-version)
bundle install # install development gems
bundle exec rake # run rubocop + rspec -- should pass clean
git add -A
git commit -m "chore: initial project scaffold with full quality toolchain"
Quality Thresholds Enforced in CI
Check
Threshold
Enforcement
RuboCop
0 offenses
CI fails on any offense
Test Coverage
90% overall, 75% per file
SimpleCov raises on merge to main
Security CVEs
0 known vulnerabilities
bundler-audit blocks the build
Ruby Compat
Ruby 3.1, 3.2, 3.3
All 3 matrix jobs must pass
Next Steps
Replace the placeholder summary and description in data_validator.gemspec with the real description before the first release.
Create lib/data_validator/schema.rb and lib/data_validator/validator.rb as the first domain files -- Zeitwerk will autoload them as DataValidator::Schema and DataValidator::Validator.
Write the first failing RSpec example before writing implementation code -- the test infrastructure is ready immediately.
Add CHANGELOG.md entries under [Unreleased] for every user-visible change during development.
When ready to publish: confirm MFA is enabled on your RubyGems.org account (required by rubygems_mfa_required in gemspec), then run bundle exec rake release.