Thank you for your interest in contributing to StructuredParams! This document provides guidelines and information for developers.
- Ruby 3.2 or higher (Ruby 3.3+ required for RBS inline features)
- Bundler
# Clone the repository
git clone https://github.com/Syati/structured_params.git
cd structured_params
# Setup everything (installs dependencies, RBS collection, and git hooks)
bin/setup# Run the specs of every gem (from the repository root)
bundle exec rake spec
# The repository holds two gems, each in its own folder:
# structured_params/ the core gem
# structured_params-openapi/ the OpenAPI drift check gem
cd structured_params
# Run all tests of the gem
bundle exec rspec
# Run specific test file
bundle exec rspec spec/params_spec.rb
# Run with coverage
bundle exec rspec --format documentation# Run RuboCop
bundle exec rubocop
# Auto-correct RuboCop offenses
bundle exec rubocop -a
# Run Steep type checker (Ruby 3.3+ only)
bundle exec steep checkThis project uses rbs-inline for type annotations. RBS signature files in the sig/ directory are auto-generated from inline type annotations in Ruby files.
DO NOT manually edit files in the sig/ directory.
Automatically regenerate signatures on file changes:
./bin/devThis command uses fswatch to monitor the lib/ directory of each gem and automatically runs rbs-inline when files change.
Generate all signatures manually:
# Generate for all lib files (run inside each gem folder, e.g. structured_params/)
cd structured_params && bundle exec rbs-inline --output=sig libRBS signatures are automatically generated before each commit via Lefthook:
# Manually trigger the pre-commit hook
lefthook run prepare-commit-msgThe git hook configuration is in lefthook.yml:
prepare-commit-msg:
commands:
rbs-inline:
run: (cd structured_params && bundle exec rbs-inline --output=sig lib) && ...This project uses rbs-inline with the method_type_signature style:
# Good: method_type_signature style
class Example
#: () -> String
def method_name
"result"
end
#: (String) -> Integer
def method_with_param(value)
value.length
end
#: (String value, ?Integer default) -> String
def method_with_optional(value, default: 0)
"#{value}: #{default}"
end
endException: Instance variables must use doc style (# @rbs):
# Instance variable
class Example
# @rbs @name: String?
#: (String) -> void
def initialize(name)
@name = name
end
end
# Class instance variable (use self.@)
class Example
class << self
# @rbs self.@cache: Hash[Symbol, String]?
def cache
@cache ||= {}
end
end
endDO NOT use doc style for method signatures:
# Bad: doc style for methods (do not use)
# @rbs return: String
def method_name
"result"
endThe project enforces this style via RuboCop:
Style/RbsInline/MissingTypeAnnotation:
EnforcedStyle: method_type_signatureYou can test against different Rails versions using the RAILS_VERSION environment variable:
# Test with Rails 7.2
RAILS_VERSION="~> 7.2.0" bundle update && bundle exec rake spec
# Test with Rails 8.0
RAILS_VERSION="~> 8.0.0" bundle update && bundle exec rake spec. # repository root: README, Gemfile, Rakefile, Steepfile, lefthook.yml, RuboCop config
├── structured_params/ # core gem
│ ├── structured_params.gemspec
│ ├── Rakefile # build/release tasks for this gem
│ ├── CHANGELOG.md
│ ├── lib/
│ │ ├── structured_params.rb # Main entry point
│ │ └── structured_params/
│ │ ├── params.rb # Core Params class
│ │ ├── errors.rb # Enhanced error handling
│ │ ├── version.rb # Version constant
│ │ └── type/
│ │ ├── array.rb # Array type handler
│ │ └── object.rb # Object type handler
│ ├── sig/ # Auto-generated RBS files (DO NOT EDIT)
│ ├── spec/ # RSpec tests
│ │ ├── factories/ # Test parameter classes
│ │ ├── support/ # Test helpers
│ │ └── *_spec.rb # Test files
│ └── docs/ # Documentation
└── structured_params-openapi/ # OpenAPI drift check gem (same layout: gemspec, Rakefile, CHANGELOG.md, lib/, sig/, spec/, docs/)
The repository holds two gems, each released from a GitHub Release with its own tag. The core gem is released first when a change needs both (the openapi gem depends on structured_params >= 0.9.5; raise that bound if it needs newer core behaviour).
| Gem | Release tag | Workflow | Version file |
|---|---|---|---|
structured_params |
vX.Y.Z |
release.yml |
structured_params/lib/structured_params/version.rb |
structured_params-openapi |
openapi-vX.Y.Z |
release-openapi.yml |
structured_params-openapi/lib/structured_params/openapi/version.rb |
To release a gem:
- Make sure
maincontains the changes to ship and CI is green. Do not editversion.rborCHANGELOG.mdyourself; the workflow writes both. - Create a GitHub Release (published, not a draft or pre-release) with a new tag from the table, targeting
main, and write the release notes. Pushing a tag alone does nothing. - Publishing the Release runs the workflow, which:
- checks that the tag looks like
vX.Y.Z/openapi-vX.Y.Z, that its commit is onmain, and that the version does not go backwards; - sets
VERSION, prepends the release notes to the gem'sCHANGELOG.mdand commits both; - builds the gem, pushes that commit to
main, moves the Release's tag onto it, then publishes the gem to RubyGems through Trusted Publishing.
- checks that the tag looks like
Things to know:
- The Release creates its tag first, then the workflow commits the version and CHANGELOG and force-moves the tag onto that commit, so the tag contains exactly what is published. This needs permission to update tags (tag protection rules that forbid it would fail the job before anything is published).
- If a run fails, re-run it: the steps are idempotent (the CHANGELOG entry is not added twice and an already pushed commit is a no-op). A gem that was already published to RubyGems cannot be published again, so a failure after publishing needs a new version.
- Pre-releases are ignored by the workflows, and a tag that matches neither
v*noropenapi-v*releases nothing. Ifmainhas branch protection, thegithub-actionsbot must be allowed to push to it. - The two workflows share a
concurrencygroup because both push tomain; keep the tag prefixes consistent in three places: the workflowif:and tag parsing, andtag_prefixin the gem'sRakefile.
One-time setup for a new gem: on rubygems.org, register a (pending) Trusted Publisher for structured_params-openapi with owner Syati, repository structured_params, workflow filename release-openapi.yml and no environment. The publisher is bound to the workflow's file name, so do not rename release.yml or release-openapi.yml without updating it. The first publish of a new gem name consumes the pending publisher.
- Fork the repository and create your branch from
main - Write tests for your changes
- Update documentation if needed (README, docs/, inline comments)
- Ensure all tests pass:
bundle exec rake spec - Ensure code quality:
bundle exec rubocop - Ensure type safety (Ruby 3.3+):
bundle exec steep check - RBS signatures will be auto-generated by git hooks
- Write a clear commit message describing your changes
- Submit a pull request with a description of your changes
When reporting issues, please include:
- Ruby version (
ruby -v) - Rails version (if applicable)
- Gem version
- Steps to reproduce
- Expected behavior
- Actual behavior
- Code samples or error messages
If you have questions about contributing, feel free to:
- Open an issue with the
questionlabel - Start a discussion in GitHub Discussions
- Contact the maintainers
This project follows the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code.
By contributing to StructuredParams, you agree that your contributions will be licensed under the MIT License.