contracts-lite 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.markdown +80 -0
- data/Gemfile +16 -0
- data/LICENSE +23 -0
- data/README.md +102 -0
- data/TODO.markdown +6 -0
- data/TUTORIAL.md +747 -0
- data/benchmarks/bench.rb +67 -0
- data/benchmarks/hash.rb +69 -0
- data/benchmarks/invariants.rb +91 -0
- data/benchmarks/io.rb +62 -0
- data/benchmarks/wrap_test.rb +57 -0
- data/contracts.gemspec +13 -0
- data/lib/contracts.rb +231 -0
- data/lib/contracts/builtin_contracts.rb +541 -0
- data/lib/contracts/call_with.rb +97 -0
- data/lib/contracts/core.rb +52 -0
- data/lib/contracts/decorators.rb +47 -0
- data/lib/contracts/engine.rb +26 -0
- data/lib/contracts/engine/base.rb +136 -0
- data/lib/contracts/engine/eigenclass.rb +50 -0
- data/lib/contracts/engine/target.rb +70 -0
- data/lib/contracts/error_formatter.rb +121 -0
- data/lib/contracts/errors.rb +71 -0
- data/lib/contracts/formatters.rb +134 -0
- data/lib/contracts/invariants.rb +68 -0
- data/lib/contracts/method_handler.rb +195 -0
- data/lib/contracts/method_reference.rb +100 -0
- data/lib/contracts/support.rb +59 -0
- data/lib/contracts/validators.rb +139 -0
- data/lib/contracts/version.rb +3 -0
- data/script/rubocop +7 -0
- data/spec/builtin_contracts_spec.rb +461 -0
- data/spec/contracts_spec.rb +748 -0
- data/spec/error_formatter_spec.rb +68 -0
- data/spec/fixtures/fixtures.rb +710 -0
- data/spec/invariants_spec.rb +17 -0
- data/spec/module_spec.rb +18 -0
- data/spec/override_validators_spec.rb +162 -0
- data/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
- data/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
- data/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
- data/spec/spec_helper.rb +102 -0
- data/spec/support.rb +10 -0
- data/spec/support_spec.rb +21 -0
- data/spec/validators_spec.rb +47 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5a0087eb9cbfbfbac59abc12f636caccf3699b81
|
4
|
+
data.tar.gz: eb6161c0401a24b4a37ac8fff330de398198fd74
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 681aa159502c17ae1dccc23060193b73cd70f81034a724398003edf886156ecd7c5e23199c53a1a5c1f90a0cd03257ace3de9733058c76c616d2e0af2356fb27
|
7
|
+
data.tar.gz: 170938443f38a9cbc575e1ae4261dc6365d44c7615108fc5cd25195af0554e8f4afc82395f04bdd17e9f0f4eff859d0a262a57a00c527f5ee01843ecb350cbc7
|
data/CHANGELOG.markdown
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
## v0.15.0
|
2
|
+
|
3
|
+
## v0.14.0
|
4
|
+
- Enhancement: Add StrictHash contract - [Fyodor](https://github.com/cbrwizard) [#236](https://github.com/egonSchiele/contracts.ruby/pull/236)
|
5
|
+
- Bugfix: dont fail if something other than a hash is passed to a KeywordArgs - [Dan Padilha](https://github.com/dpad) [#234](https://github.com/egonSchiele/contracts.ruby/pull/234)
|
6
|
+
- LICENSE ADDED: Simplified BSD (same as what is specified in the readme) - [Charles Dale](https://github.com/chuckd) [#233](https://github.com/egonSchiele/contracts.ruby/pull/233)
|
7
|
+
- Bugfix: fix constant looking when including a module that includes contracts (requires removing the check to see if contracts is already included) - [Aditya Bhargava](https://github.com/egonSchiele) [#232](https://github.com/egonSchiele/contracts.ruby/pull/232)
|
8
|
+
- Bugfix for err case when KeywordArgs and Proc are used together - [Aditya Bhargava](https://github.com/egonSchiele) [#230](https://github.com/egonSchiele/contracts.ruby/pull/230)
|
9
|
+
- Enhancement: Add DescendantOf contract - [Miguel Palhas](https://github.com/naps62) [#227](https://github.com/egonSchiele/contracts.ruby/pull/227)
|
10
|
+
|
11
|
+
## v0.13.0
|
12
|
+
|
13
|
+
- Enhancement: Add support for Ruby 2.3 - [Oleksii Fedorov](https://github.com/waterlink) [#216](https://github.com/egonSchiele/contracts.ruby/pull/216)
|
14
|
+
- Enhancement: Added Int, Nat and NatPos builtin contracts - [Simon George](https://github.com/sfcgeorge) [#212](https://github.com/egonSchiele/contracts.ruby/pull/212)
|
15
|
+
- Bugfix: Allow contracts on singleton of subclass - [Oleksii Federov](https://github.com/waterlink) [#211](https://github.com/egonSchiele/contracts.ruby/pull/211)
|
16
|
+
|
17
|
+
## v0.12.0
|
18
|
+
|
19
|
+
- Feature: add `Regexp` validator - [Gert Goet](https://github.com/eval) [#196](https://github.com/egonSchiele/contracts.ruby/pull/196)
|
20
|
+
- Docs: bootstrap cucumber/aruba/relish setup - [Oleksii Fedorov](https://github.com/waterlink) [#195](https://github.com/egonSchiele/contracts.ruby/pull/195)
|
21
|
+
- Bugfix: allow to `extend` module, that has `Contracts` or `Contracts::Core` included without harming current module/class `Contracts` functionality, see: [#176](https://github.com/egonSchiele/contracts.ruby/issues/176) - [Oleksii Fedorov](https://github.com/waterlink) [#198](https://github.com/egonSchiele/contracts.ruby/pull/198)
|
22
|
+
- Enhancement: add `include Contracts::Builtin` to allow users to use builtin contracts without `Contracts::` prefix together with `include Contracts::Core` - [PikachuEXE](https://github.com/PikachuEXE) [#199](https://github.com/egonSchiele/contracts.ruby/pull/199)
|
23
|
+
|
24
|
+
## v0.11.0
|
25
|
+
|
26
|
+
- Enhancement: add `include Contracts::Core` that doesn't pollute the namespace as much as `include Contracts` - [Oleksii Federov](https://github.com/waterlink) [#185](https://github.com/egonSchiele/contracts.ruby/pull/185)
|
27
|
+
- Bugfix: fail if a non-hash is provided to a `HashOf` contract - [Abe Voelker](https://github.com/abevoelker) [#190](https://github.com/egonSchiele/contracts.ruby/pull/190)
|
28
|
+
- Bugfix: bugfix for using varargs and `Maybe[Proc]` together - [Adit Bhargava](https://github.com/egonSchiele) [#188](https://github.com/egonSchiele/contracts.ruby/pull/188)
|
29
|
+
- Bugfix: make KeywordArgs fail if unexpected keys are passed in - [Abe Voelker](https://github.com/abevoelker) [#187](https://github.com/egonSchiele/contracts.ruby/pull/187)
|
30
|
+
- Feature: range contract added - [Oleksii Fedorov](https://github.com/waterlink) [#184](https://github.com/egonSchiele/contracts.ruby/pull/184)
|
31
|
+
- Feature: enum contract added - [Dennis Günnewig](https://github.com/dg-ratiodata) [#181](https://github.com/egonSchiele/contracts.ruby/pull/181)
|
32
|
+
|
33
|
+
## v0.10.1
|
34
|
+
|
35
|
+
- Enhancement: make `@pattern_match` instance variable not render ruby warning. Required to use new aruba versions in rspec tests - [Dennis Günnewig](https://github.com/dg-ratiodata) [#179](https://github.com/egonSchiele/contracts.ruby/pull/179)
|
36
|
+
|
37
|
+
## v0.10
|
38
|
+
|
39
|
+
- Bugfix: make `Maybe[Proc]` work correctly - [Simon George](https://github.com/sfcgeorge) [#142](https://github.com/egonSchiele/contracts.ruby/pull/142)
|
40
|
+
- Bugfix: make `Func` contract verified when used as return contract - [Rob Rosenbaum](https://github.com/robnormal) [#145](https://github.com/egonSchiele/contracts.ruby/pull/145)
|
41
|
+
- Bugfix: make `Pos`, `Neg` and `Nat` contracts handle non-numeric values correctly - [Matt Griffin](https://github.com/betamatt) and [Gavin Sinclair](https://github.com/gsinclair) [#147](https://github.com/egonSchiele/contracts.ruby/pull/147) [#173](https://github.com/egonSchiele/contracts.ruby/pull/173)
|
42
|
+
- Enhancement: reduce user class pollution through introduction of contracts engine - [Oleksii Fedorov](https://github.com/waterlink) [#141](https://github.com/egonSchiele/contracts.ruby/pull/141)
|
43
|
+
- Feature: add builtin `KeywordArgs` and `Optional` contracts for keyword arguments handling - [Oleksii Fedorov](https://github.com/waterlink) [#151](https://github.com/egonSchiele/contracts.ruby/pull/151)
|
44
|
+
- Feature: recognize module as a class contract - [Oleksii Fedorov](https://github.com/waterlink) [#153](https://github.com/egonSchiele/contracts.ruby/pull/153)
|
45
|
+
- Feature: custom validators with `Contract.override_validator` - [Oleksii Fedorov](https://github.com/waterlink) [#159](https://github.com/egonSchiele/contracts.ruby/pull/159)
|
46
|
+
- Feature: add builtin `RangeOf[...]` contract - [Gavin Sinclair](https://github.com/gsinclair) [#171](https://github.com/egonSchiele/contracts.ruby/pull/171)
|
47
|
+
|
48
|
+
## v0.9
|
49
|
+
|
50
|
+
- MAJOR fix in pattern-matching: If the return contract for a pattern-matched function fails, it should NOT try the next pattern-match function. Pattern-matching is only for params, not return values.
|
51
|
+
- raise an error if multiple defns have the same contract for pattern matching.
|
52
|
+
|
53
|
+
- New syntax for functions with no input params (the old style still works)
|
54
|
+
Old way:
|
55
|
+
```ruby
|
56
|
+
Contract nil => 1
|
57
|
+
def one
|
58
|
+
```
|
59
|
+
New way:
|
60
|
+
```
|
61
|
+
Contract 1
|
62
|
+
def one
|
63
|
+
```
|
64
|
+
|
65
|
+
- Prettier HashOf contract can now be written like this: `HashOf[Num => String]`
|
66
|
+
- Add `SetOf` contract
|
67
|
+
- various small fixes
|
68
|
+
|
69
|
+
## v0.8
|
70
|
+
|
71
|
+
- code refactored (very slight loss of performance, big increase in readability)
|
72
|
+
- fail when defining a contract on a module without `include Contracts::Modules`
|
73
|
+
- fixed several bugs in argument parsing, functions with complex params get contracts applied correctly now.
|
74
|
+
- added rubocop to ci.
|
75
|
+
- if a contract is set on a protected method, it should not become public.
|
76
|
+
- fixed pattern matching when the multiple definitions of functions have different arities.
|
77
|
+
- couple of new built-in contracts: Nat, Eq.
|
78
|
+
- changed `Invariant` to `invariant`: `invariant(:day) { 1 <= day && day <= 31 }`
|
79
|
+
- prettier error messages (`Contracts::Num` is now just `Num`, for example)
|
80
|
+
- support for yard-contracts
|
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
group :test do
|
6
|
+
gem "rspec"
|
7
|
+
gem "rubocop", "~> 0.29.1", :platform => [:ruby_20, :ruby_21, :ruby_22, :ruby_23]
|
8
|
+
end
|
9
|
+
|
10
|
+
group :development do
|
11
|
+
gem "relish"
|
12
|
+
gem "method_profiler"
|
13
|
+
gem "ruby-prof"
|
14
|
+
gem "rake"
|
15
|
+
gem "byebug"
|
16
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2012-2016 Aditya Bhargava
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
15
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
16
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
17
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
18
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
19
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
20
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
21
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
22
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
23
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# contracts.ruby [![Build Status](https://travis-ci.org/ddd-ruby/contracts.ruby.png?branch=master)](https://travis-ci.org/ddd-ruby/contracts.ruby) [![Join the chat at https://gitter.im/egonSchiele/contracts.ruby](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg)](https://gitter.im/egonSchiele/contracts.ruby)
|
2
|
+
|
3
|
+
Contracts let you clearly – even beautifully – express how your code behaves, and free you from writing tons of boilerplate, defensive code.
|
4
|
+
|
5
|
+
You can think of contracts as `assert` on steroids.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
gem install contracts
|
10
|
+
|
11
|
+
## Hello World
|
12
|
+
|
13
|
+
A contract is one line of code that you write above a method definition. It validates the arguments to the method, and validates the return value of the method.
|
14
|
+
|
15
|
+
Here is a simple contract:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
Contract Num => Num
|
19
|
+
def double(x)
|
20
|
+
```
|
21
|
+
|
22
|
+
This says that double expects a number and returns a number. Here's the full code:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'contracts'
|
26
|
+
|
27
|
+
class Example
|
28
|
+
include Contracts::Core
|
29
|
+
include Contracts::Builtin
|
30
|
+
|
31
|
+
Contract Num => Num
|
32
|
+
def double(x)
|
33
|
+
x * 2
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
puts Example.new.double("oops")
|
38
|
+
```
|
39
|
+
|
40
|
+
Save this in a file and run it. Notice we are calling `double` with `"oops"`, which is not a number. The contract fails with a detailed error message:
|
41
|
+
|
42
|
+
```
|
43
|
+
ParamContractError: Contract violation for argument 1 of 1:
|
44
|
+
Expected: Num,
|
45
|
+
Actual: "oops"
|
46
|
+
Value guarded in: Example::double
|
47
|
+
With Contract: Num => Num
|
48
|
+
At: main.rb:8
|
49
|
+
...stack trace...
|
50
|
+
```
|
51
|
+
|
52
|
+
Instead of throwing an exception, you could log it, print a clean error message for your user...whatever you want. contracts.ruby is here to help you handle bugs better, not to get in your way.
|
53
|
+
|
54
|
+
## Tutorial
|
55
|
+
|
56
|
+
Check out [this awesome tutorial](http://egonschiele.github.com/contracts.ruby).
|
57
|
+
|
58
|
+
## Use Cases
|
59
|
+
|
60
|
+
Check out [this screencast](https://vimeo.com/85883356).
|
61
|
+
|
62
|
+
## Development
|
63
|
+
|
64
|
+
To get started do the following:
|
65
|
+
|
66
|
+
1. Install required gems for development
|
67
|
+
|
68
|
+
`bundle install`
|
69
|
+
|
70
|
+
2. Run our test suite
|
71
|
+
|
72
|
+
`bundle exec rake`
|
73
|
+
|
74
|
+
## Performance
|
75
|
+
|
76
|
+
Using contracts.ruby results in very little slowdown. Check out [this blog post](http://adit.io/posts/2013-03-04-How-I-Made-My-Ruby-Project-10x-Faster.html#seconds-6) for more info.
|
77
|
+
|
78
|
+
**Q.** What Rubies can I use this with?
|
79
|
+
|
80
|
+
**A.** It's been tested with `1.9.2`, `1.9.3`, `2.0.0`, `2.1`, `2.2`, and `jruby` (1.9 mode).
|
81
|
+
|
82
|
+
If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :)
|
83
|
+
|
84
|
+
## Testimonials
|
85
|
+
|
86
|
+
> Contracts literally saves us hours of pain at Snowplow every day
|
87
|
+
|
88
|
+
Alexander Dean, creator of [Snowplow](https://github.com/snowplow/snowplow)
|
89
|
+
|
90
|
+
> Contracts caught a bug that saved us several hundred dollars. It took less than 30 seconds to add the contract.
|
91
|
+
|
92
|
+
Michael Tomer
|
93
|
+
|
94
|
+
## Credits
|
95
|
+
|
96
|
+
Inspired by [contracts.coffee](http://disnetdev.com/contracts.coffee/).
|
97
|
+
|
98
|
+
Copyright 2012-2015 [Aditya Bhargava](http://adit.io).
|
99
|
+
Major improvements by [Alexey Fedorov](https://github.com/waterlink).
|
100
|
+
|
101
|
+
BSD Licensed.
|
102
|
+
|
data/TODO.markdown
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
- maybe make some screencasts
|
2
|
+
|
3
|
+
- you can now do something like Haskell's quickcheck. Every contract has a method 'test_data' or something. You can use that data to automatically check methods with contracts to make sure they are correct.
|
4
|
+
- http://www.cse.chalmers.se/~rjmh/QuickCheck/manual.html
|
5
|
+
- for stuff like the Not contract, should I make a standard set of classes to check those functions with? Would that be useful at all?
|
6
|
+
- also write specs for this stuff
|
data/TUTORIAL.md
ADDED
@@ -0,0 +1,747 @@
|
|
1
|
+
# The contracts.ruby tutorial
|
2
|
+
|
3
|
+
## Introduction
|
4
|
+
|
5
|
+
contracts.ruby brings code contracts to the Ruby language. Code contracts allow you make some assertions about your code, and then checks them to make sure they hold. This lets you
|
6
|
+
|
7
|
+
- catch bugs faster
|
8
|
+
- make it very easy to catch certain types of bugs
|
9
|
+
- make sure that the user gets proper messaging when a bug occurs.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
gem install contracts
|
14
|
+
|
15
|
+
## Basics
|
16
|
+
|
17
|
+
A simple example:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
Contract Contracts::Num, Contracts::Num => Contracts::Num
|
21
|
+
def add(a, b)
|
22
|
+
a + b
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
Here, the contract is `Contract Num, Num => Num`. This says that the `add` function takes two numbers and returns a number.
|
27
|
+
|
28
|
+
Copy this code into a file and run it:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
require 'contracts'
|
32
|
+
|
33
|
+
class Math
|
34
|
+
include Contracts::Core
|
35
|
+
|
36
|
+
Contract Contracts::Num, Contracts::Num => Contracts::Num
|
37
|
+
def self.add(a, b)
|
38
|
+
a + b
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
puts Math.add(1, "foo")
|
43
|
+
```
|
44
|
+
|
45
|
+
You'll see a detailed error message like so:
|
46
|
+
|
47
|
+
./contracts.rb:60:in `failure_callback': Contract violation: (RuntimeError)
|
48
|
+
Expected: Contracts::Num,
|
49
|
+
Actual: "foo"
|
50
|
+
Value guarded in: Object::add
|
51
|
+
With Contract: Contracts::Num, Contracts::Num
|
52
|
+
At: foo.rb:6
|
53
|
+
|
54
|
+
That tells you that your contract was violated! `add` expected a `Num`, and got a string (`"foo"`) instead.
|
55
|
+
By default, an exception is thrown when a contract fails. This can be changed to do whatever you want. More on this later.
|
56
|
+
|
57
|
+
You can also see the contract for a function with the `functype` method:
|
58
|
+
|
59
|
+
functype(:add)
|
60
|
+
=> "add :: Num, Num => Num"
|
61
|
+
|
62
|
+
This can be useful if you're in a REPL and want to figure out how a function should be used.
|
63
|
+
|
64
|
+
## Built-in Contracts
|
65
|
+
|
66
|
+
`Num` is one of the built-in contracts that contracts.ruby comes with. The built-in contracts are in the `Contracts` namespace. The easiest way to use them is to include the `Contracts::Builtin` module in your class/module.
|
67
|
+
|
68
|
+
contracts.ruby comes with a lot of built-in contracts, including the following:
|
69
|
+
|
70
|
+
* Basic types
|
71
|
+
* [`Num`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Num) – checks that the argument is `Numeric`
|
72
|
+
* [`Pos`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Pos) – checks that the argument is a positive number
|
73
|
+
* [`Neg`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Neg) – checks that the argument is a negative number
|
74
|
+
* [`Int`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Int) – checks that the argument is an integer
|
75
|
+
* [`Nat`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Nat) – checks that the argument is a natural number (>= 0)
|
76
|
+
* [`NatPos`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/NatPos) – checks that the argument is a positive natural number (> 0)
|
77
|
+
* [`Bool`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Bool) – checks that the argument is `true` or `false`
|
78
|
+
* [`Any`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Any) – Passes for any argument. Use when the argument has no constraints.
|
79
|
+
* [`None`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/None) – Fails for any argument. Use when the method takes no arguments.
|
80
|
+
|
81
|
+
* Logical combinations
|
82
|
+
* [`Maybe`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Maybe) – specifies that a value _may be_ nil, e.g. `Maybe[String]` (equivalent to `Or[String,nil]`)
|
83
|
+
* [`Or`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Or) – passes if any of the given contracts pass, e.g. `Or[Fixnum, Float]`
|
84
|
+
* [`Xor`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Xor) – passes if exactly one of the given contracts pass, e.g. `Xor[Fixnum, Float]`
|
85
|
+
* [`And`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/And) – passes if all contracts pass, e.g. `And[Nat, -> (n) { n.even? }]`
|
86
|
+
* [`Not`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Not) – passes if all contracts fail for the given argument, e.g. `Not[nil]`
|
87
|
+
|
88
|
+
* Collections
|
89
|
+
* [`ArrayOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/ArrayOf) – checks that the argument is an array, and all elements pass the given contract, e.g. `ArrayOf[Num]`
|
90
|
+
* [`SetOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/SetOf) – checks that the argument is a set, and all elements pass the given contract, e.g. `SetOf[Num]`
|
91
|
+
* [`HashOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/HashOf) – checks that the argument is a hash, and all keys and values pass the given contract, e.g. `HashOf[Symbol => String]` or `HashOf[Symbol,String]`
|
92
|
+
* [`StrictHash`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/StrictHash) – checks that the argument is a hash, and every key passed is present in the given contract, e.g. `StrictHash[{ :description => String, :number => Fixnum }]`
|
93
|
+
* [`RangeOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/RangeOf) – checks that the argument is a range whose elements (#first and #last) pass the given contract, e.g. `RangeOf[Date]`
|
94
|
+
* [`Enum`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Enum) – checks that the argument is part of a given collection of objects, e.g. `Enum[:a, :b, :c]`
|
95
|
+
|
96
|
+
* Keyword arguments
|
97
|
+
* [`KeywordArgs`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/KeywordArgs) – checks that the argument is an options hash, and all required keyword arguments are present, and all values pass their respective contracts, e.g. `KeywordArgs[:number => Num, :description => Optional[String]]`
|
98
|
+
* [`Optional`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Optional) – checks that the keyword argument is either not present or pass the given contract, can not be used outside of `KeywordArgs` contract, e.g. `Optional[Num]`
|
99
|
+
|
100
|
+
* Duck typing
|
101
|
+
* [`RespondTo`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/RespondTo) – checks that the argument responds to all of the given methods, e.g. `RespondTo[:password, :credit_card]`
|
102
|
+
* [`Send`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Send) – checks that all named methods return a truthy value, e.g. `Send[:valid?]`
|
103
|
+
|
104
|
+
* Miscellaneous
|
105
|
+
* [`Exactly`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Exactly) – checks that the argument has the given type, not accepting sub-classes, e.g. `Exactly[Numeric]`.
|
106
|
+
* [`Eq`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Eq) – checks that the argument is precisely equal to the given value, e.g. `Eq[String]` matches the class `String` and not a string instance.
|
107
|
+
* [`Func`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Func) – specifies the contract for a proc/lambda e.g. `Contract ArrayOf[Num], Func[Num => Num] => ArrayOf[Num]`. See section "Contracts On Functions".
|
108
|
+
|
109
|
+
To see all the built-in contracts and their full descriptions, check out the [RDoc](http://rubydoc.info/gems/contracts/Contracts/Builtin).
|
110
|
+
|
111
|
+
It is recommended to use shortcut for referring builtin contracts:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
# define shortcut somewhere at the top level of your codebase:
|
115
|
+
C = Contracts
|
116
|
+
|
117
|
+
# and use it:
|
118
|
+
Contract C::Maybe[C::Num], String => C::Num
|
119
|
+
```
|
120
|
+
|
121
|
+
Shortcut name should not be necessary `C`, can be anything that you are comfort
|
122
|
+
with while typing and anything that does not conflict with libraries you use.
|
123
|
+
|
124
|
+
All examples after this point assume you have chosen a shortcut as `C::`.
|
125
|
+
|
126
|
+
If you are sure, that builtin contracts will not nameclash with your own code
|
127
|
+
and libraries you may use, then you can include all builtin contracts in your
|
128
|
+
class/module:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class Example
|
132
|
+
include Contracts::Core
|
133
|
+
include Contracts::Builtin
|
134
|
+
|
135
|
+
Contract Maybe[Num], Or[Float, String] => Bool
|
136
|
+
def complicated_algorithm(a, b)
|
137
|
+
# ...
|
138
|
+
end
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
142
|
+
## More Examples
|
143
|
+
|
144
|
+
### Hello, World
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
Contract String => nil
|
148
|
+
def hello(name)
|
149
|
+
puts "hello, #{name}!"
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
You always need to specify a contract for the return value. In this example, `hello` doesn't return anything, so the contract is `nil`. Now you know that you can use a constant like `nil` as the end of a contract. Valid values for a contract are:
|
154
|
+
|
155
|
+
- the name of a class (like `String` or `Fixnum`)
|
156
|
+
- a constant (like `nil` or `1`)
|
157
|
+
- a `Proc` that takes a value and returns true or false to indicate whether the contract passed or not
|
158
|
+
- a class that responds to the `valid?` class method (more on this later)
|
159
|
+
- an instance of a class that responds to the `valid?` method (more on this later)
|
160
|
+
|
161
|
+
### A Double Function
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
Contract C::Or[Fixnum, Float] => C::Or[Fixnum, Float]
|
165
|
+
def double(x)
|
166
|
+
2 * x
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
Sometimes you want to be able to choose between a few contracts. `Or` takes a variable number of contracts and checks the argument against all of them. If it passes for any of the contracts, then the `Or` contract passes.
|
171
|
+
This introduces some new syntax. One of the valid values for a contract is an instance of a class that responds to the `valid?` method. This is what `Or[Fixnum, Float]` is. The longer way to write it would have been:
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
Contract C::Or.new(Fixnum, Float) => C::Or.new(Fixnum, Float)
|
175
|
+
```
|
176
|
+
|
177
|
+
All the built-in contracts have overridden the square brackets (`[]`) to give the same functionality. So you could write
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
Contract C::Or[Fixnum, Float] => C::Or[Fixnum, Float]
|
181
|
+
```
|
182
|
+
|
183
|
+
or
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
Contract C::Or.new(Fixnum, Float) => C::Or.new(Fixnum, Float)
|
187
|
+
```
|
188
|
+
|
189
|
+
whichever you prefer. They both mean the same thing here: make a new instance of `Or` with `Fixnum` and `Float`. Use that instance to validate the argument.
|
190
|
+
|
191
|
+
### A Product Function
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
Contract C::ArrayOf[C::Num] => C::Num
|
195
|
+
def product(vals)
|
196
|
+
total = 1
|
197
|
+
vals.each do |val|
|
198
|
+
total *= val
|
199
|
+
end
|
200
|
+
total
|
201
|
+
end
|
202
|
+
```
|
203
|
+
|
204
|
+
This contract uses the `ArrayOf` contract. Here's how `ArrayOf` works: it takes a contract. It expects the argument to be a list. Then it checks every value in that list to see if it satisfies that contract.
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
# passes
|
208
|
+
product([1, 2, 3, 4])
|
209
|
+
|
210
|
+
# fails
|
211
|
+
product([1, 2, 3, "foo"])
|
212
|
+
```
|
213
|
+
|
214
|
+
### Another Product Function
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
Contract C::Args[C::Num] => C::Num
|
218
|
+
def product(*vals)
|
219
|
+
total = 1
|
220
|
+
vals.each do |val|
|
221
|
+
total *= val
|
222
|
+
end
|
223
|
+
total
|
224
|
+
end
|
225
|
+
```
|
226
|
+
|
227
|
+
This function uses varargs (`*args`) instead of an array. To make a contract on varargs, use the `Args` contract. It takes one contract as an argument and uses it to validate every element passed in through `*args`. So for example,
|
228
|
+
|
229
|
+
`Args[Num]` means they should all be numbers.
|
230
|
+
|
231
|
+
`Args[Or[Num, String]]` means they should all be numbers or strings.
|
232
|
+
|
233
|
+
`Args[Any]` means all arguments are allowed (`Any` is a contract that passes for any argument).
|
234
|
+
|
235
|
+
### Contracts On Arrays
|
236
|
+
|
237
|
+
If an array is one of the arguments and you know how many elements it's going to have, you can put a contract on it:
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
# a function that takes an array of two elements...a person's age and a person's name.
|
241
|
+
Contract [C::Num, String] => nil
|
242
|
+
def person(data)
|
243
|
+
p data
|
244
|
+
end
|
245
|
+
```
|
246
|
+
|
247
|
+
If you don't know how many elements it's going to have, use `ArrayOf`.
|
248
|
+
|
249
|
+
### Contracts On Hashes
|
250
|
+
|
251
|
+
Here's a contract that requires a Hash. We can put contracts on each of the keys:
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
# note the parentheses around the hash; without those you would get a syntax error
|
255
|
+
Contract ({ :age => C::Num, :name => String }) => nil
|
256
|
+
def person(data)
|
257
|
+
p data
|
258
|
+
end
|
259
|
+
```
|
260
|
+
|
261
|
+
Then if someone tries to call the function with bad data, it will fail:
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
# error: age can't be nil!
|
265
|
+
person({:name => "Adit", :age => nil})
|
266
|
+
```
|
267
|
+
|
268
|
+
You don't need to put a contract on every key. So this call would succeed:
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
person({:name => "Adit", :age => 42, :foo => "bar"})
|
272
|
+
```
|
273
|
+
|
274
|
+
even though we don't specify a type for `:foo`. If you need this check though, use `StrictHash` instead.
|
275
|
+
|
276
|
+
Peruse this contract on the keys and values of a Hash.
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
Contract C::HashOf[Symbol, C::Num] => C::Num
|
280
|
+
def give_largest_value(hsh)
|
281
|
+
hsh.values.max
|
282
|
+
end
|
283
|
+
```
|
284
|
+
Which you use like so:
|
285
|
+
```ruby
|
286
|
+
# succeeds
|
287
|
+
give_largest_value(a: 1, b: 2, c: 3) # returns 3
|
288
|
+
|
289
|
+
# fails
|
290
|
+
give_largest_value("a" => 1, 2 => 2, c: 3)
|
291
|
+
```
|
292
|
+
|
293
|
+
### Contracts On Strings
|
294
|
+
|
295
|
+
When you want a contract to match not just any string (i.e. `Contract String => nil`), you can use regular expressions:
|
296
|
+
```ruby
|
297
|
+
Contract /World|Mars/i => nil
|
298
|
+
def greet(name)
|
299
|
+
puts "Hello #{name}!"
|
300
|
+
end
|
301
|
+
```
|
302
|
+
|
303
|
+
Using logical combinations you can combine existing definitions, instead of writing 1 big regular expression:
|
304
|
+
```ruby
|
305
|
+
Contract C::And[default_mail_regexp, /#{AppConfig.domain}\z/] => nil
|
306
|
+
def send_admin_invite(email)
|
307
|
+
```
|
308
|
+
|
309
|
+
### Contracts On Keyword Arguments
|
310
|
+
|
311
|
+
ruby 2.0+, but can be used for normal hashes too, when keyword arguments are
|
312
|
+
not available
|
313
|
+
|
314
|
+
Lets say you are writing a simple function and require a bunch of keyword arguments:
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
def connect(host, port:, user:, password:)
|
318
|
+
```
|
319
|
+
|
320
|
+
You can of course put `Hash` contract on it:
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
Contract String, { :port => C::Num, :user => String, :password => String } => Connection
|
324
|
+
def connect(host, port:, user:, password:)
|
325
|
+
```
|
326
|
+
|
327
|
+
But this will not quite work if you want to have a default values:
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
Contract String, { :port => C::Num, :user => String, :password => String } => Connection
|
331
|
+
def connect(host, port: 5000, user:, password:)
|
332
|
+
# ...
|
333
|
+
end
|
334
|
+
|
335
|
+
# No value is passed for port
|
336
|
+
connect("example.org", user: "me", password: "none")
|
337
|
+
```
|
338
|
+
|
339
|
+
Results in:
|
340
|
+
|
341
|
+
```
|
342
|
+
ContractError: Contract violation for argument 2 of 2:
|
343
|
+
Expected: {:port=>Num, :user=>String, :password=>String},
|
344
|
+
Actual: {:user=>"me", :password=>"none"}
|
345
|
+
Value guarded in: Object::connect
|
346
|
+
With Contract: String, Hash => Connection
|
347
|
+
At: (irb):12
|
348
|
+
```
|
349
|
+
|
350
|
+
This can be fixed with contract `{ :port => C::Maybe[C::Num], ... }`, but that will
|
351
|
+
allow `nil` to be passed in, which is not the original intent.
|
352
|
+
|
353
|
+
So that is where `KeywordArgs` and `Optional` contracts jump in:
|
354
|
+
|
355
|
+
```ruby
|
356
|
+
Contract String, C::KeywordArgs[ :port => C::Optional[C::Num], :user => String, :password => String ] => Connection
|
357
|
+
def connect(host, port: 5000, user:, password:)
|
358
|
+
```
|
359
|
+
|
360
|
+
It looks just like the hash contract, but wrapped in `KeywordArgs` contract. Notice the usage of `Optional` contract - this way you specify that `:port` argument is optional. And it will not fail, when you omit this argument, but it will fail when you pass in `nil`.
|
361
|
+
|
362
|
+
### Contracts On Functions
|
363
|
+
|
364
|
+
Lets say you are writing a simple map function:
|
365
|
+
|
366
|
+
```ruby
|
367
|
+
def map(arr, func)
|
368
|
+
```
|
369
|
+
|
370
|
+
`map` takes an array, and a function. Suppose you want to add a contract to this function. You could try this:
|
371
|
+
|
372
|
+
```ruby
|
373
|
+
Contract C::ArrayOf[C::Any], Proc => C::ArrayOf[C::Any]
|
374
|
+
def map(arr, func)
|
375
|
+
```
|
376
|
+
|
377
|
+
This says that the second argument should be a `Proc`. You can call the function like so:
|
378
|
+
|
379
|
+
```ruby
|
380
|
+
p map([1, 2, 3], lambda { |x| x + 1 }) # works
|
381
|
+
```
|
382
|
+
|
383
|
+
But suppose you want to have a contract on the Proc too! Suppose you want to make sure that the Proc returns a number. Use the `Func` contract. `Func` takes a contract as its argument, and uses that contract on the function that you pass in.
|
384
|
+
|
385
|
+
Here's a `map` function that requires an array of numbers, and a function that takes a number and returns a number:
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
Contract C::ArrayOf[C::Num], C::Func[C::Num => C::Num] => C::ArrayOf[C::Num]
|
389
|
+
def map(arr, func)
|
390
|
+
ret = []
|
391
|
+
arr.each do |x|
|
392
|
+
ret << func[x]
|
393
|
+
end
|
394
|
+
ret
|
395
|
+
end
|
396
|
+
```
|
397
|
+
|
398
|
+
Earlier, we used `Proc`, which just says "make sure the second variable is a Proc". Now we are using `Func[Num => Num]`, which says "make sure the second variable is a Proc that takes a number and returns a number". Better!
|
399
|
+
|
400
|
+
Try this map function with these two examples:
|
401
|
+
|
402
|
+
```ruby
|
403
|
+
p map([1, 2, 3], lambda { |x| x + 1 }) # works
|
404
|
+
p map([1, 2, 3], lambda { |x| "oops" }) # fails, the lambda returns a string.
|
405
|
+
```
|
406
|
+
|
407
|
+
The above examples showed a method accepting a `Proc` as the last argument, but the same contract works on methods that accept a block:
|
408
|
+
|
409
|
+
```ruby
|
410
|
+
def map(arr, &block)
|
411
|
+
```
|
412
|
+
|
413
|
+
NOTE: This is not valid:
|
414
|
+
|
415
|
+
```ruby
|
416
|
+
Contract C::ArrayOf[C::Num], C::Func => C::ArrayOf[C::Num]
|
417
|
+
def map(arr, &func)
|
418
|
+
```
|
419
|
+
|
420
|
+
Here I am using `Func` without specifying a contract, like `Func[Num => Num]`. That's not a legal contract. If you just want to validate that the second argument is a proc, use `Proc`.
|
421
|
+
|
422
|
+
### Returning Multiple Values
|
423
|
+
Treat the return value as an array. For example, here's a function that returns two numbers:
|
424
|
+
|
425
|
+
```ruby
|
426
|
+
Contract C::Num => [C::Num, C::Num]
|
427
|
+
def mult(x)
|
428
|
+
return x, x+1
|
429
|
+
end
|
430
|
+
```
|
431
|
+
|
432
|
+
## Synonyms For Contracts
|
433
|
+
|
434
|
+
If you use a contract a lot, it's a good idea to give it a meaningful synonym that tells the reader more about what your code returns. For example, suppose you have many functions that return a `Hash` or `nil`. If a `Hash` is returned, it contains information about a person. Your contact might look like this:
|
435
|
+
|
436
|
+
```ruby
|
437
|
+
Contract String => C::Or[Hash, nil]
|
438
|
+
def some_func(str)
|
439
|
+
```
|
440
|
+
|
441
|
+
You can make your contract more meaningful with a synonym:
|
442
|
+
|
443
|
+
```ruby
|
444
|
+
# the synonym
|
445
|
+
Person = Or[Hash, nil]
|
446
|
+
|
447
|
+
# use the synonym here
|
448
|
+
Contract String => Person
|
449
|
+
def some_func(str)
|
450
|
+
```
|
451
|
+
|
452
|
+
Now you can use `Person` wherever you would have used `Or[Hash, nil]`. Your code is now cleaner and more clearly says what the function is doing.
|
453
|
+
|
454
|
+
## Defining Your Own Contracts
|
455
|
+
|
456
|
+
Contracts are very easy to define. To re-iterate, there are 5 kinds of contracts:
|
457
|
+
|
458
|
+
- the name of a class (like `String` or `Fixnum`)
|
459
|
+
- a constant (like `nil` or `1`)
|
460
|
+
- a `Proc` that takes a value and returns true or false to indicate whether the contract passed or not
|
461
|
+
- a class that responds to the `valid?` class method (more on this later)
|
462
|
+
- an instance of a class that responds to the `valid?` method (more on this later)
|
463
|
+
|
464
|
+
The first two don't need any extra work to define: you can just use any constant or class name in your contract and it should just work. Here are examples for the rest:
|
465
|
+
|
466
|
+
### A Proc
|
467
|
+
|
468
|
+
```ruby
|
469
|
+
Contract lambda { |x| x.is_a? Numeric } => C::Num
|
470
|
+
def double(x)
|
471
|
+
```
|
472
|
+
|
473
|
+
The lambda takes one parameter: the argument that is getting passed to the function. It checks to see if it's a `Numeric`. If it is, it returns true. Otherwise it returns false.
|
474
|
+
It's not good practice to write a lambda right in your contract...if you find yourself doing it often, write it as a class instead:
|
475
|
+
|
476
|
+
### A Class With `valid?` As a Class Method
|
477
|
+
|
478
|
+
Here's how the `Num` class is defined. It does exactly what the `lambda` did in the previous example:
|
479
|
+
|
480
|
+
```ruby
|
481
|
+
class Num
|
482
|
+
def self.valid? val
|
483
|
+
val.is_a? Numeric
|
484
|
+
end
|
485
|
+
end
|
486
|
+
```
|
487
|
+
|
488
|
+
The `valid?` class method takes one parameter: the argument that is getting passed to the function. It returns true or false.
|
489
|
+
|
490
|
+
### A Class With `valid?` As an Instance Method
|
491
|
+
|
492
|
+
Here's how the `Or` class is defined:
|
493
|
+
|
494
|
+
```ruby
|
495
|
+
class Or < CallableClass
|
496
|
+
def initialize(*vals)
|
497
|
+
@vals = vals
|
498
|
+
end
|
499
|
+
|
500
|
+
def valid?(val)
|
501
|
+
@vals.any? do |contract|
|
502
|
+
res, _ = Contract.valid?(val, contract)
|
503
|
+
res
|
504
|
+
end
|
505
|
+
end
|
506
|
+
end
|
507
|
+
```
|
508
|
+
|
509
|
+
The `Or` contract takes a sequence of contracts, and passes if any of them pass. It uses `Contract.valid?` to validate the value against the contracts.
|
510
|
+
|
511
|
+
This class inherits from `CallableClass`, which allows us to use `[]` when using the class:
|
512
|
+
|
513
|
+
```ruby
|
514
|
+
Contract C::Or[Fixnum, Float] => C::Num
|
515
|
+
def double(x)
|
516
|
+
2 * x
|
517
|
+
end
|
518
|
+
```
|
519
|
+
|
520
|
+
Without `CallableClass`, we would have to use `.new` instead:
|
521
|
+
|
522
|
+
```ruby
|
523
|
+
Contract C::Or.new(Fixnum, Float) => C::Num
|
524
|
+
def double(x)
|
525
|
+
# etc
|
526
|
+
```
|
527
|
+
|
528
|
+
You can use `CallableClass` in your own contracts to make them callable using `[]`.
|
529
|
+
|
530
|
+
## Customizing Error Messages
|
531
|
+
|
532
|
+
When a contract fails, part of the error message prints the contract:
|
533
|
+
|
534
|
+
...
|
535
|
+
Expected: Contracts::Num,
|
536
|
+
...
|
537
|
+
|
538
|
+
You can customize this message by overriding the `to_s` method on your class or proc. For example, suppose we overrode `Num`'s `to_s` method:
|
539
|
+
|
540
|
+
```ruby
|
541
|
+
def Num.to_s
|
542
|
+
"a number please"
|
543
|
+
end
|
544
|
+
```
|
545
|
+
|
546
|
+
Now the error says:
|
547
|
+
|
548
|
+
...
|
549
|
+
Expected: a number please,
|
550
|
+
...
|
551
|
+
|
552
|
+
## Failure Callbacks
|
553
|
+
|
554
|
+
Supposing you don't want contract failures to become exceptions. You run a popular website, and when there's a contract exception you would rather log it and continue than throw an exception and break your site.
|
555
|
+
|
556
|
+
contracts.ruby provides a failure callback that gets called when a contract fails. For example, here we log every failure instead of raising an error:
|
557
|
+
|
558
|
+
```ruby
|
559
|
+
Contract.override_failure_callback do |data|
|
560
|
+
puts "You had an error"
|
561
|
+
puts failure_msg(data)
|
562
|
+
end
|
563
|
+
```
|
564
|
+
|
565
|
+
`failure_msg` is a function that prints out information about the failure. Your failure callback gets a hash with the following values:
|
566
|
+
|
567
|
+
{
|
568
|
+
:arg => the argument to the method,
|
569
|
+
:contract => the contract that got violated,
|
570
|
+
:class => the method's class,
|
571
|
+
:method => the method,
|
572
|
+
:contracts => the contract object
|
573
|
+
}
|
574
|
+
|
575
|
+
If your failure callback returns `false`, the method that the contract is guarding will not be called (the default behaviour).
|
576
|
+
|
577
|
+
## Providing your own custom validators
|
578
|
+
|
579
|
+
This can be done with `Contract.override_validator`:
|
580
|
+
|
581
|
+
```ruby
|
582
|
+
# Make contracts accept all RSpec doubles
|
583
|
+
Contract.override_validator(:class) do |contract|
|
584
|
+
lambda do |arg|
|
585
|
+
arg.is_a?(RSpec::Mocks::Double) ||
|
586
|
+
arg.is_a?(contract)
|
587
|
+
end
|
588
|
+
end
|
589
|
+
```
|
590
|
+
|
591
|
+
The block you provide should always return lambda accepting one argument - validated argument. Block itself accepts contract as an argument.
|
592
|
+
|
593
|
+
Possible validator overrides:
|
594
|
+
|
595
|
+
- `override_validator(MyCustomContract)` - allows to add some special behaviour for custom contracts,
|
596
|
+
- `override_validator(Proc)` - e.g. `lambda { true }`,
|
597
|
+
- `override_validator(Array)` - e.g. `[C::Num, String]`,
|
598
|
+
- `override_validator(Hash)` - e.g. `{ :a => C::Num, :b => String }`,
|
599
|
+
- `override_validator(Range)` - e.g. `(1..10)`,
|
600
|
+
- `override_validator(Regexp)` - e.g. `/foo/`,
|
601
|
+
- `override_validator(Contracts::Args)` - e.g. `C::Args[C::Num]`,
|
602
|
+
- `override_validator(Contracts::Func)` - e.g. `C::Func[C::Num => C::Num]`,
|
603
|
+
- `override_validator(:valid)` - allows to override how contracts that respond to `:valid?` are handled,
|
604
|
+
- `override_validator(:class)` - allows to override how class/module contract constants are handled,
|
605
|
+
- `override_validator(:default)` - otherwise, raw value contracts.
|
606
|
+
|
607
|
+
Default validators can be found here: [lib/contracts/validators.rb](https://github.com/egonSchiele/contracts.ruby/blob/master/lib/contracts/validators.rb).
|
608
|
+
|
609
|
+
## Disabling contracts
|
610
|
+
|
611
|
+
If you want to disable contracts, set the `NO_CONTRACTS` environment variable. This will disable contracts and you won't have a performance hit. Pattern matching will still work if you disable contracts in this way! With NO_CONTRACTS only pattern-matching contracts are defined.
|
612
|
+
|
613
|
+
## Method overloading
|
614
|
+
|
615
|
+
You can use contracts for method overloading! This is commonly called "pattern matching" in functional programming languages.
|
616
|
+
|
617
|
+
For example, here's a factorial function without method overloading:
|
618
|
+
|
619
|
+
```ruby
|
620
|
+
Contract C::Num => C::Num
|
621
|
+
def fact x
|
622
|
+
if x == 1
|
623
|
+
x
|
624
|
+
else
|
625
|
+
x * fact(x - 1)
|
626
|
+
end
|
627
|
+
end
|
628
|
+
```
|
629
|
+
|
630
|
+
Here it is again, re-written with method overloading:
|
631
|
+
|
632
|
+
```ruby
|
633
|
+
Contract 1 => 1
|
634
|
+
def fact x
|
635
|
+
x
|
636
|
+
end
|
637
|
+
|
638
|
+
Contract C::Num => C::Num
|
639
|
+
def fact x
|
640
|
+
x * fact(x - 1)
|
641
|
+
end
|
642
|
+
```
|
643
|
+
|
644
|
+
For an argument, each function will be tried in order. The first function that doesn't raise a `ContractError` will be used. So in this case, if x == 1, the first function will be used. For all other values, the second function will be used.
|
645
|
+
|
646
|
+
This allows you write methods more declaratively, rather than using conditional branching. This feature is not only useful for recursion; you can use it to keep parallel use cases separate:
|
647
|
+
|
648
|
+
```ruby
|
649
|
+
Contract lambda{|n| n < 12 } => Ticket
|
650
|
+
def get_ticket(age)
|
651
|
+
ChildTicket.new(age: age)
|
652
|
+
end
|
653
|
+
|
654
|
+
Contract lambda{|n| n >= 12 } => Ticket
|
655
|
+
def get_ticket(age)
|
656
|
+
AdultTicket.new(age: age)
|
657
|
+
end
|
658
|
+
|
659
|
+
```
|
660
|
+
|
661
|
+
Note that the second `get_ticket` contract above could have been simplified to:
|
662
|
+
|
663
|
+
```ruby
|
664
|
+
Contract C::Num => Ticket
|
665
|
+
```
|
666
|
+
|
667
|
+
This is because the first contract eliminated the possibility of `age` being less than 12. However, the simpler contract is less explicit; you may want to "spell out" the age condition for clarity, especially if the method is overloaded with many contracts.
|
668
|
+
|
669
|
+
## Contracts in modules
|
670
|
+
|
671
|
+
Usage is the same as contracts in classes:
|
672
|
+
|
673
|
+
```ruby
|
674
|
+
module M
|
675
|
+
include Contracts::Core
|
676
|
+
|
677
|
+
Contract String => String
|
678
|
+
def self.parse
|
679
|
+
# do some hard parsing
|
680
|
+
end
|
681
|
+
end
|
682
|
+
```
|
683
|
+
|
684
|
+
## Invariants
|
685
|
+
|
686
|
+
Invariants are conditions on objects that should always hold. If after any method call on given object, any of the Invariants fails, then Invariant violation error will be generated.
|
687
|
+
|
688
|
+
**NOTE**: Only methods with contracts will be affected.
|
689
|
+
|
690
|
+
A simple example:
|
691
|
+
|
692
|
+
```ruby
|
693
|
+
class MyBirthday < Struct.new(:day, :month)
|
694
|
+
include Contracts::Core
|
695
|
+
include Contracts::Invariants
|
696
|
+
|
697
|
+
invariant(:day) { 1 <= day && day <= 31 }
|
698
|
+
invariant(:month) { 1 <= month && month <= 12 }
|
699
|
+
|
700
|
+
Contract C::None => Fixnum
|
701
|
+
def silly_next_day!
|
702
|
+
self.day += 1
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
706
|
+
birthday = MyBirthday.new(31, 12)
|
707
|
+
birthday.silly_next_day!
|
708
|
+
```
|
709
|
+
|
710
|
+
If you run it, last line will generate invariant violation:
|
711
|
+
|
712
|
+
```ruby
|
713
|
+
./invariant.rb:38:in `failure_callback': Invariant violation: (RuntimeError)
|
714
|
+
Expected: day condition to be true
|
715
|
+
Actual: false
|
716
|
+
Value guarded in: MyBirthday::silly_next_day!
|
717
|
+
At: main.rb:9
|
718
|
+
```
|
719
|
+
|
720
|
+
Which means, that after `#silly_next_day!` all checks specified in `invariant` statement will be verified, and if at least one fail, then invariant violation error will be raised.
|
721
|
+
|
722
|
+
## Using contracts within your own code
|
723
|
+
|
724
|
+
contracts.ruby is obviously designed to check method parameters and return values. But if you want to check whether some other data obeys a contract, you can use `Contract.valid?(value, contract)`. For instance:
|
725
|
+
|
726
|
+
```ruby
|
727
|
+
data = parse(user_input)
|
728
|
+
unless Contract.valid?(data, HashOf[String,Nat])
|
729
|
+
raise UserInputError.new(user_input)
|
730
|
+
end
|
731
|
+
```
|
732
|
+
|
733
|
+
## Auto-generate documentation using contracts
|
734
|
+
|
735
|
+
If you are generating documentation for your code with [YARD](http://yardoc.org/), check out [yard-contracts](https://github.com/sfcgeorge/yard-contracts). It will automatically annotate your functions with contracts information. Instead of documenting each parameter for a function yourself, you can just add a contract and yard-contracts will generate the documentation for you!
|
736
|
+
|
737
|
+
## Misc
|
738
|
+
|
739
|
+
Please submit any bugs [here](https://github.com/egonSchiele/contracts.ruby/issues) and I'll try to get them resolved ASAP!
|
740
|
+
|
741
|
+
See any mistakes in this tutorial? I try to make it bug-free, but they can creep in. [File an issue](https://github.com/egonSchiele/contracts.ruby/issues).
|
742
|
+
|
743
|
+
If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :)
|
744
|
+
|
745
|
+
See the [wiki](https://github.com/egonSchiele/contracts.ruby/wiki) for more info.
|
746
|
+
|
747
|
+
Happy Coding!
|