Ruby Project Setup
When to Use
Use this skill when:
- 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
- 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,Gemfilebest practices,bundler/setup, frozen string literals, or.rubocop.ymlbaseline 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/,Rakefilewithreleasetask,CHANGELOG.md, andCODE_OF_CONDUCT.md. Usebundle gem <name>as the scaffold, then customize. - Standalone CLI application: Needs
exe/orbin/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/orlib/for route handlers,Gemfilewith explicit Rack/Sinatra versions, and aProcfilefor 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 newwith--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-versionin the project root containing only the exact version string:3.3.4. Noruby-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), specifyrequired_ruby_versionwith a range:>= 3.1.0restricts users from installing on unsupported Rubies. Use~> 3.1only if you genuinely test against all 3.x versions. - In CI, test against the exact version in
.ruby-versionfirst; optionally add a job for the next minor version to catch forward-compatibility issues early. - Add
.ruby-versionto 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 WITHsection by runningbundle installlocally. CommitGemfile.lockfor applications; do NOT commitGemfile.lockfor 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:
# 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: falseon development tools (RuboCop, Brakeman, etc.) -- they should never be auto-required in application code. - Configure Bundler path via
.bundle/configor environment variable:
For non-Docker local dev, the defaultBUNDLE_PATH: vendor/bundle # preferred for Docker-based projects~/.bundleglobal path is acceptable. - Run
bundle install --frozenin CI to prevent accidentalGemfile.lockmutations during builds.
Step 4: Establish Directory Structure
A consistent directory layout enables new contributors to navigate the project immediately.
For a gem named payment_gateway:
payment_gateway/
├── .bundle/
│ └── config
├── .github/
│ └── workflows/
│ └── ci.yml
├── bin/
│ ├── console # interactive console with gem loaded
│ └── setup # bootstraps local dev environment
├── exe/
│ └── payment_gateway # executable binary (if CLI gem)
├── lib/
│ ├── payment_gateway.rb # main entry point, requires internals
│ └── payment_gateway/
│ ├── version.rb # ONLY the VERSION constant
│ ├── client.rb
│ ├── errors.rb
│ └── configuration.rb
├── spec/
│ ├── spec_helper.rb
│ └── payment_gateway/
│ ├── client_spec.rb
│ └── configuration_spec.rb
├── .ruby-version
├── .rubocop.yml
├── CHANGELOG.md
├── Gemfile
├── payment_gateway.gemspec
├── Rakefile
└── README.md
Key structural rules:
lib/gem_name.rbloads all internal files viarequire_relativeor Zeitwerk autoloader -- it is the public contract of the gem.lib/gem_name/version.rbcontains ONLYVERSION = '1.0.0'. The gemspec requires this file to read the version. No other requires here.bin/consoleshould loadrequire 'irb'andrequire_relative '../lib/gem_name'then callIRB.start.bin/setuprunsbundle install, any database setup commands, and prints a success message. Make it idempotent.- The
exe/directory (for executable gems) is separate frombin/(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.
Create .rubocop.yml at the project root:
require:
- rubocop-rspec
- rubocop-performance
AllCops:
NewCops: enable
TargetRubyVersion: 3.3
Exclude:
- 'vendor/**/*'
- 'bin/**/*'
- 'exe/**/*'
- '*.gemspec'
- 'db/schema.rb' # for Rails projects
# Layout
Layout/LineLength:
Max: 120
AllowedPatterns:
- '^\s*#' # allow long comment lines
# Style
Style/FrozenStringLiteralComment:
Enabled: true
EnforcedStyle: always
Style/StringLiterals:
EnforcedStyle: single_quotes
Style/Documentation:
Enabled: false # disable for internal projects; enable for gems
# Metrics -- start permissive, tighten over time
Metrics/MethodLength:
Max: 20
Metrics/ClassLength:
Max: 200
Metrics/AbcSize:
Max: 20
# RSpec
RSpec/ExampleLength:
Max: 10
RSpec/MultipleExpectations:
Max: 3
Critical configuration decisions:
- Always set
TargetRubyVersion-- without it, RuboCop assumes an old default and misses version-specific cops. - Enable
NewCops: enableso you catch new offenses as RuboCop versions are updated rather than accumulating tech debt. - Add
# frozen_string_literal: trueto every Ruby file -- this enables string deduplication and prevents accidental string mutation. RuboCop'sStyle/FrozenStringLiteralCommentcop enforces this. - Run
bundle exec rubocop --auto-gen-configon legacy codebases to generate a.rubocop_todo.ymlbaseline, then reduce it over time. - Integrate RuboCop as a git pre-commit hook using
overcommitorlefthookto 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:
# 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 85in 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):
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-rubywithbundler-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 githubon GitHub Actions -- this format creates inline PR annotations rather than plain text output. - Run
bundle-auditin every CI job -- it checksGemfile.lockagainst 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=trueenvironment 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.
Rakefile:
# frozen_string_literal: true
require 'rspec/core/rake_task'
require 'rubocop/rake_task'
RSpec::Core::RakeTask.new(:spec)
RuboCop::RakeTask.new
task default: %i[rubocop spec]
For gems, add release automation:
require 'bundler/gem_tasks'
# Now `rake release` tags, pushes, and publishes the gem
bin/setup:
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
echo "==> Installing dependencies..."
bundle install
echo "==> Copying environment template..."
[ -f .env ] || cp .env.example .env
echo "==> Setup complete. Run 'bin/console' to start an interactive session."
.env.example (never .env -- that stays in .gitignore):
APP_ENV=development
LOG_LEVEL=debug
DATABASE_URL=postgres://localhost:5432/myapp_development
.gitignore essentials:
# Bundler
.bundle/
vendor/bundle/
*.gem
# Environment
.env
.env.local
.env.*.local
# Coverage
coverage/
.simplecov
# Editor
.idea/
.vscode/
*.swp
# OS
.DS_Store
CHANGELOG.md -- start with a template following Keep a Changelog format:
# 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:
# 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:
# lib/payment_gateway.rb
require 'zeitwerk'
loader = Zeitwerk::Loader.for_gem
loader.setup
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."
Output:
Ruby Project Setup: data_validator Gem
Project Classification
- Type: Public Ruby Gem
- Ruby Version: 3.3.4 (development); supports 3.1.0+
- Team Size: 4 engineers
- Multi-version Support: Yes -- 3.1, 3.2, 3.3 (CI matrix)
- Publishing Target: RubyGems.org
Toolchain Decision Matrix
| Tool | Selection | Rationale |
|---|---|---|
| Version Manager | rbenv | 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
# 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
# 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
# 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
# frozen_string_literal: true
module DataValidator
VERSION = '0.1.0'
end
lib/data_validator.rb
# 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
.rubocop.yml
require:
- rubocop-rspec
- rubocop-performance
AllCops:
NewCops: enable
TargetRubyVersion: 3.1 # lowest supported version for cop compatibility
Exclude:
- 'vendor/**/*'
- 'bin/**/*'
- 'exe/**/*'
- 'data_validator.gemspec'
Layout/LineLength:
Max: 120
Style/FrozenStringLiteralComment:
Enabled: true
EnforcedStyle: always
Style/StringLiterals:
EnforcedStyle: single_quotes
Style/Documentation:
Enabled: true # enforce documentation for public gem API
Metrics/MethodLength:
Max: 15
Metrics/ClassLength:
Max: 150
RSpec/ExampleLength:
Max: 8
RSpec/MultipleExpectations:
Max: 2
spec/spec_helper.rb
# 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
.github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
name: Ruby ${{ matrix.ruby }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
ruby: ['3.1', '3.2', '3.3']
os: [ubuntu-latest]
include:
- ruby: '3.3'
os: ubuntu-latest
coverage: true
steps:
- uses: actions/checkout@v4
- name: Set up Ruby ${{ matrix.ruby }}
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Lint with RuboCop
run: bundle exec rubocop --format github
if: matrix.ruby == '3.3' # lint once on latest Ruby
- name: Run tests
run: bundle exec rspec
env:
COVERAGE: ${{ matrix.coverage || 'false' }}
- name: Check for security vulnerabilities
run: |
bundle exec bundle-audit update
bundle exec bundle-audit check
if: matrix.ruby == '3.3'
Rakefile
# frozen_string_literal: true
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
require 'rubocop/rake_task'
RSpec::Core::RakeTask.new(:spec)
RuboCop::RakeTask.new
task default: %i[rubocop spec]
bin/setup
#!/usr/bin/env bash
set -euo pipefail
echo "==> Installing Ruby gems..."
bundle install
echo ""
echo "==> Setup complete!"
echo " Run 'bundle exec rake' to execute linting and tests."
echo " Run 'bin/console' to start an interactive session."
chmod +x bin/setup bin/console
bin/console
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'bundler/setup'
require 'data_validator'
require 'irb'
IRB.start(__FILE__)
Initial Setup Commands
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.gemspecwith the real description before the first release. - Create
lib/data_validator/schema.rbandlib/data_validator/validator.rbas the first domain files -- Zeitwerk will autoload them asDataValidator::SchemaandDataValidator::Validator. - Write the first failing RSpec example before writing implementation code -- the test infrastructure is ready immediately.
- Add
CHANGELOG.mdentries 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_requiredin gemspec), then runbundle exec rake release.