{"id":16167512,"url":"https://github.com/samcarlberg/method_contracts","last_synced_at":"2025-07-06T12:34:40.274Z","repository":{"id":82586537,"uuid":"468595561","full_name":"SamCarlberg/method_contracts","owner":"SamCarlberg","description":"Adds runtime type and value checking to method parameters and return values","archived":false,"fork":false,"pushed_at":"2022-10-28T23:08:10.000Z","size":65,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-07T05:51:29.449Z","etag":null,"topics":["ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SamCarlberg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-11T03:33:10.000Z","updated_at":"2024-12-26T15:45:48.000Z","dependencies_parsed_at":null,"dependency_job_id":"e234c23c-2bc2-44e4-8fbc-d2a627635c2b","html_url":"https://github.com/SamCarlberg/method_contracts","commit_stats":{"total_commits":26,"total_committers":1,"mean_commits":26.0,"dds":0.0,"last_synced_commit":"8b943232294371c07aed27fad883ab2083f908ab"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/SamCarlberg/method_contracts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamCarlberg%2Fmethod_contracts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamCarlberg%2Fmethod_contracts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamCarlberg%2Fmethod_contracts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamCarlberg%2Fmethod_contracts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SamCarlberg","download_url":"https://codeload.github.com/SamCarlberg/method_contracts/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamCarlberg%2Fmethod_contracts/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263901178,"owners_count":23527375,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ruby"],"created_at":"2024-10-10T03:08:25.242Z","updated_at":"2025-07-06T12:34:40.237Z","avatar_url":"https://github.com/SamCarlberg.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MethodContracts\n\nA pure Ruby implementation of parameter and return value checking.\n\nThis adds runtime type checking and validation to method parameters and return values.  By default, this gem only supports validating types and exact values, but custom matchers can be created and specified.  Contracts can be specified on parameters using the `params \u003cparam_name\u003e, \u003ccontract\u003e` syntax; contracts on return values can be specified by `returns \u003ccontract\u003e`, `returns_a \u003cType\u003e`, or `returns_an \u003cType\u003e` (eg `returns_a String`, `returns_an ArrayOf(String)`).\n\nContracts can be specified as a module/class, which will test for inheritance (`param :foo, String`); an explicit value, which will test for equality `param :foo, 'A string'`; a regular expression (`param :foo, /An? (array|string)/`); an array of elements that all match the same contract (`param :foo, ArrayOf(String)`); a hash with keys matching one contract and values matching another (`param :hash, HashOf(Symbol, Integer)`); or an array of contracts, which will test that any contract in that array matches (`param :foo, [String, Symbol]` matches when `foo` is either a string or a symbol).\n\nIf none of the built-in contract types work, you can subclass `MethodContracts::Matchers::Base` and define the `match?(value)` and `to_s` methods, then pass an instance of your custom matcher to the `param` or `return` call.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'method_contracts', git: 'git@github.com:SamCarlberg/method_contracts.git'\n```\n\nAnd then execute:\n\n    $ bundle install\n\nOr install it yourself as:\n\n    $ gem install method_contracts\n\n## Usage\n\n### Type checks\n\n```ruby\nclass C\n  param :x, String\n  param :y, Numeric\n  param :z, Hash\n  returns_an Array\n  def takes_three(x, y, z)\n    [x, y, z]\n  end\nend\n\nC.new.takes_three('x', 1, { z: :z }) # =\u003e ['x', 1, { :z =\u003e :z }]\nC.new.takes_three(:x, 1, { z: :z })  # =\u003e error!  x is a Symbol, but should be a String\n```\n\n### Value checks\n\n```ruby\nclass C\n  param :x, 1\n  returns 'ok'\n  def takes_specific_value(x)\n    'ok'\n  end\nend\n\nC.new.takes_specific_value(1) # =\u003e 'ok'\nC.new.takes_specific_value(2) # =\u003e error!\n```\n\n### Regex checks\n\n```ruby\nclass C\n  param :value, /^mo[dmo]$/\n  returns 'ok'\n  def foo(value)\n    'ok'\n  end\nend\n\nC.new.foo('mod') # =\u003e 'ok'\nC.new.foo('mom') # =\u003e 'ok'\nC.new.foo('moo') # =\u003e 'ok'\nC.new.foo('mop') # =\u003e error!\n```\n\n### Array checks\n\n```ruby\nclass C\n  param :strategy, %i[foo bar baz]\n  returns 'ok'\n  def takes_any_of(strategy)\n    'ok'\n  end\nend\n\nC.new.takes_any_of(:foo) # =\u003e 'ok'\nC.new.takes_any_of(:bar) # =\u003e 'ok'\nC.new.takes_any_of(:baz) # =\u003e 'ok'\nC.new.takes_any_of(:wrong) # =\u003e error!\n```\n\n### Array checks with nested matchers\n\n```ruby\nclass C\n  param :x, [String, Symbol, 0, nil]\n  returns 'ok'\n  def takes_generic(x)\n    'ok'\n  end\nend\n\nC.new.takes_generic('string') # =\u003e 'ok'\nC.new.takes_generic(:symbol) # =\u003e 'ok'\nC.new.takes_generic(0) # =\u003e 'ok'\nC.new.takes_generic(nil) # =\u003e 'ok'\nC.new.takes_generic(1234) # =\u003e error!\n```\n\n### Structure checks\n\nChecks can be added to check the structure of array or hash parameters and return values.  Use the `ArrayOf` helper method to define a contract that matches to every element of an array (it also checks that the parameter is itself an `Array`).  Similarly, the `HashOf` helper method can be used to define a contract for every key and a separate contract for every value in the hash.  Elementwise or key-/value-wise contracts can be specified as usual, so `param :nested_array, ArrayOf(ArrayOf(Integer))` will only match an array of arrays like `[[1, 2, 3], [4, 5, 6]]` but not `[[1, 2, 3], 4, 5, 6]`\n\n```ruby\nclass C\n  # :array takes an array of zero or more integers\n  param :array, ArrayOf(Integer)\n  param :hash, HashOf(Symbol, [String, Symbol])\n  returns 'ok'\n  def structured_args(array, hash)\n    'ok'\n  end\n\n  param :array, ArrayOf(ArrayOf(Integer))\n  returns 'ok'\n  def accepts_nested_arrays(array)\n    'ok'\n  end\nend\n\nC.new.structured_args([], {}) # =\u003e 'ok'\nC.new.structured_args([1, 2, 3], { foo: 'bar', bar: :baz }) # =\u003e 'ok'\nC.new.structured_args(%w[1 2 3], {}) # =\u003e error!\nC.new.structured_args([], { 'foo' =\u003e 'bar' }) # error!\n\nC.new.accepts_nested_arrays([]) # =\u003e 'ok'\nC.new.accepts_nested_arrays([[]]) # =\u003e 'ok'\nC.new.accepts_nested_arrays([1, 2, 3]) # =\u003e error!  not a nested array\nC.new.accepts_nested_arrays([[1, 2, 3]]) # =\u003e 'ok'\n```\n\n### Custom matcher logic\n\n```ruby\nclass C\n  param :x, -\u003e(x) { x == [1, 2, 3] }\n  returns 'ok'\n  def custom_matcher_logic(x)\n    'ok'\n  end\nend\n\nC.new.custom_matcher_logic([1, 2, 3]) # =\u003e 'ok'\nC.new.custom_matcher_logic([1, 2, 4]) # =\u003e error!\n```\n\nThis can also be used by passing a block to the `param` or `returns` call.  Note that `param` needs parens around the parameter name if using curly braces for the block definition.\n\n```ruby\nclass C\n  param(:x) { |x| x == [1, 2, 3] }\n  returns { |r| r == 'ok' }\n  def custom_matcher_logic(x)\n    'ok'\n  end\nend\n```\n\n### Create a custom matcher\n\n```ruby\nclass PositiveNumber \u003c MethodContracts::Matchers::Base\n  def match?(value)\n    value.is_a?(Numeric) \u0026\u0026 value \u003e 0\n  end\n\n  def to_s\n    'be a positive number'\n  end\nend\n\nclass UsingCustomMatcher\n  param :bar, PositiveNumber\n  def foo(bar)\n    \"ok\"\n  end\nend\n\nUsingCustomMatcher.new.foo(1) # =\u003e \"ok\"\nUsingCustomMatcher.new.foo(0) # =\u003e MethodContracts::BrokenParamContractError: UsingCustomMatcher#foo.bar was 0, which does not match: be a positive number\n```\n\n### Configuration\n\nType contracts are disabled by default, and can be enabled via the configuration object:\n\n```ruby\nMethodContracts.configure do |config|\n  config.enabled = true\nend\n```\n\nThis must be set before any classes that use type contracts are loaded.\n\nContracts can be added globally to all modules via `config.include_everywhere!`; however, be careful when using this since it can potentially cause conflicts with modules that already define methods named `annotations`, `param`, `returns`, `returns_a`, or `returns_an`.\n\nContracts can be added to specific modules by extending the `MethodContracts::T` module:\n\n```ruby\nmodule MyModule\n  extend MethodContracts::T\n\n  # ... module contents ...\nend\n\nclass MyClass\n  extend MethodContracts::T\n\n  # ... class contents ...\nend\n```\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/samcarlberg/method_contracts.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Alternatives\n\n### RBS\n\nRuby 3 introduced RBS, which is a built-in type system in the Ruby language.  However, it requires maintaining a separate file listing method signatures for each Ruby file, which requires manual work to keep the separate files in sync.\n\n### Sorbet\n\n[Sorbet](https://github.com/sorbet/sorbet) is probably better than this Gem.  I just didn't like the DSL for it.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamcarlberg%2Fmethod_contracts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamcarlberg%2Fmethod_contracts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamcarlberg%2Fmethod_contracts/lists"}