SpecyDocs
Capture RSpec request examples and serve a mountable docs UI.
- Use it if you want readable low-config docs for your team.
- Use it to uncover endpoints that you may not know exist.
- Use it if swagger and the hoops you have to jump are annoying, when you just want to show teams how to use your API with your tested code.
Installation
# Gemfile
gem 'specy_docs'
bundle install
Mount the engine
# config/routes.rb
mount SpecyDocs::Engine => '/docs'
Any path works, e.g. /api-docs or /internal/specy.
Capture request specs
Prefer the setup helper so include filters follow your configuration:
# spec/rails_helper.rb
RSpec.configure do |config|
SpecyDocs.setup_rspec(config)
end
Or include manually:
config.include SpecyDocs::Capturable, type: :request
Capture folders (legacy controller specs without type:)
If controller (or other) specs are not tagged with type: :request, and adding a type breaks them, set folders to capture from instead. SpecyDocs will include Capturable by file path for those folders. opt_in / specy tags still apply.
# config/initializers/specy_docs.rb
SpecyDocs.configure do |config|
config.capture_paths = %w[spec/controllers spec/requests]
config.opt_in = true
end
# spec/rails_helper.rb — must use setup_rspec (or include by file_path yourself)
RSpec.configure do |config|
SpecyDocs.setup_rspec(config)
end
Only examples under those folders are candidates; then:
opt_in: true→ capture whenspecy: trueopt_in: false→ capture all in those folders exceptspecy: false
Opt-in mode (default: opt_in: true)
Only examples (or groups) tagged with specy: true are captured:
RSpec.describe 'Certificates', type: :request do
it 'returns the certificate', specy: true do
get '/certificates/example.com'
expect(response).to have_http_status(:ok)
end
# Not captured
it 'returns not found' do
get '/certificates/missing.com'
expect(response).to have_http_status(:not_found)
end
end
Opt-out mode (opt_in: false)
All matching request specs are captured unless tagged with specy: false:
# config/initializers/specy_docs.rb
SpecyDocs.configure do |config|
config.opt_in = false
end
RSpec.describe 'Status', type: :request do
it 'returns success status' do
get '/status' # captured
end
it 'skips noise', specy: false do
get '/status' # not captured
end
end
Generate the report
bin/rails specy_docs:report
Then open the mounted path (e.g. http://localhost:3000/docs).
Optional configuration
# config/initializers/specy_docs.rb
SpecyDocs.configure do |config|
config.title = 'CDN API'
config.eyebrow = 'cdn-api'
config.opt_in = true # default; set false to capture all except `specy: false`
config.capture_paths = %w[spec/controllers spec/requests] # optional; empty = type: :request only
config.capture_path = Rails.root.join('tmp/specy_docs/captures.json')
config.report_path = Rails.root.join('tmp/specy_docs/report.json')
config.masked_header_keys = %w[
Authorization
]
end
Notes for API-only apps
SpecyDocs loads Action Controller and Action View railties inside the engine, so the UI renders even when the host app uses config.api_only = true.
Releasing
Releases are prepared locally and published by GitHub Actions when you publish the draft GitHub release.
One-time setup
- Create a RubyGems API key with Push rubygem permission.
- Add it as a repository secret named
RUBYGEMS_API_KEY. - Ensure a long-lived
releasebranch exists (or letbin/releasecreate it on first run).
Cut a release
bin/release
You will be prompted for bump type (patch / minor / major). Release notes are
generated from commits via the GitHub API (same source as GitHub auto notes),
previewed for confirmation, then written into CHANGELOG.md and the draft release.
The script will:
- Fetch remotes and tags
- Check out
releaseand mergeorigin/main - Generate release notes from commits since the previous tag
- Bump
lib/specy_docs/version.rband prepend those notes toCHANGELOG.md - Commit, tag (
vX.Y.Z), and pushrelease+ the tag - Create a draft GitHub release with the same notes
Then open the draft on GitHub, review the notes, and click Publish release. That triggers .github/workflows/publish-gem.yml, which builds the gem and runs gem push.
Preview without changing anything:
bin/release --dry-run
Requires a clean working tree, gh authenticated to the repo, and push access to origin.