entitlements 0.1.8 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/bin/deploy-entitlements +10 -1
- data/lib/contracts-ruby2/CHANGELOG.markdown +115 -0
- data/lib/contracts-ruby2/Gemfile +17 -0
- data/lib/contracts-ruby2/LICENSE +23 -0
- data/lib/contracts-ruby2/README.md +108 -0
- data/lib/contracts-ruby2/Rakefile +8 -0
- data/lib/contracts-ruby2/TODO.markdown +6 -0
- data/lib/contracts-ruby2/TUTORIAL.md +773 -0
- data/lib/contracts-ruby2/benchmarks/bench.rb +67 -0
- data/lib/contracts-ruby2/benchmarks/hash.rb +69 -0
- data/lib/contracts-ruby2/benchmarks/invariants.rb +91 -0
- data/lib/contracts-ruby2/benchmarks/io.rb +62 -0
- data/lib/contracts-ruby2/benchmarks/wrap_test.rb +57 -0
- data/lib/contracts-ruby2/contracts.gemspec +17 -0
- data/lib/contracts-ruby2/cucumber.yml +1 -0
- data/lib/contracts-ruby2/dependabot.yml +20 -0
- data/lib/contracts-ruby2/features/README.md +17 -0
- data/lib/contracts-ruby2/features/basics/functype.feature +71 -0
- data/lib/contracts-ruby2/features/basics/pretty-print.feature +241 -0
- data/lib/contracts-ruby2/features/basics/simple_example.feature +210 -0
- data/lib/contracts-ruby2/features/builtin_contracts/README.md +22 -0
- data/lib/contracts-ruby2/features/builtin_contracts/and.feature +103 -0
- data/lib/contracts-ruby2/features/builtin_contracts/any.feature +44 -0
- data/lib/contracts-ruby2/features/builtin_contracts/args.feature +80 -0
- data/lib/contracts-ruby2/features/builtin_contracts/array_of.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/bool.feature +64 -0
- data/lib/contracts-ruby2/features/builtin_contracts/enum.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/eq.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/exactly.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/func.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/hash_of.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/int.feature +93 -0
- data/lib/contracts-ruby2/features/builtin_contracts/keyword_args.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/maybe.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/nat.feature +115 -0
- data/lib/contracts-ruby2/features/builtin_contracts/nat_pos.feature +119 -0
- data/lib/contracts-ruby2/features/builtin_contracts/neg.feature +115 -0
- data/lib/contracts-ruby2/features/builtin_contracts/none.feature +145 -0
- data/lib/contracts-ruby2/features/builtin_contracts/not.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/num.feature +64 -0
- data/lib/contracts-ruby2/features/builtin_contracts/or.feature +83 -0
- data/lib/contracts-ruby2/features/builtin_contracts/pos.feature +116 -0
- data/lib/contracts-ruby2/features/builtin_contracts/range_of.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/respond_to.feature +78 -0
- data/lib/contracts-ruby2/features/builtin_contracts/send.feature +147 -0
- data/lib/contracts-ruby2/features/builtin_contracts/set_of.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/xor.feature +99 -0
- data/lib/contracts-ruby2/features/support/env.rb +6 -0
- data/lib/contracts-ruby2/lib/contracts/attrs.rb +24 -0
- data/lib/contracts-ruby2/lib/contracts/builtin_contracts.rb +542 -0
- data/lib/contracts-ruby2/lib/contracts/call_with.rb +108 -0
- data/lib/contracts-ruby2/lib/contracts/core.rb +52 -0
- data/lib/contracts-ruby2/lib/contracts/decorators.rb +47 -0
- data/lib/contracts-ruby2/lib/contracts/engine/base.rb +136 -0
- data/lib/contracts-ruby2/lib/contracts/engine/eigenclass.rb +50 -0
- data/lib/contracts-ruby2/lib/contracts/engine/target.rb +70 -0
- data/lib/contracts-ruby2/lib/contracts/engine.rb +26 -0
- data/lib/contracts-ruby2/lib/contracts/errors.rb +71 -0
- data/lib/contracts-ruby2/lib/contracts/formatters.rb +136 -0
- data/lib/contracts-ruby2/lib/contracts/invariants.rb +68 -0
- data/lib/contracts-ruby2/lib/contracts/method_handler.rb +187 -0
- data/lib/contracts-ruby2/lib/contracts/method_reference.rb +100 -0
- data/lib/contracts-ruby2/lib/contracts/support.rb +61 -0
- data/lib/contracts-ruby2/lib/contracts/validators.rb +139 -0
- data/lib/contracts-ruby2/lib/contracts/version.rb +3 -0
- data/lib/contracts-ruby2/lib/contracts.rb +281 -0
- data/lib/contracts-ruby2/script/docs-release +3 -0
- data/lib/contracts-ruby2/script/docs-staging +3 -0
- data/lib/contracts-ruby2/script/rubocop.rb +5 -0
- data/lib/contracts-ruby2/spec/attrs_spec.rb +119 -0
- data/lib/contracts-ruby2/spec/builtin_contracts_spec.rb +461 -0
- data/lib/contracts-ruby2/spec/contracts_spec.rb +770 -0
- data/lib/contracts-ruby2/spec/fixtures/fixtures.rb +730 -0
- data/lib/contracts-ruby2/spec/invariants_spec.rb +17 -0
- data/lib/contracts-ruby2/spec/methods_spec.rb +54 -0
- data/lib/contracts-ruby2/spec/module_spec.rb +18 -0
- data/lib/contracts-ruby2/spec/override_validators_spec.rb +162 -0
- data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
- data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
- data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
- data/lib/contracts-ruby2/spec/spec_helper.rb +102 -0
- data/lib/contracts-ruby2/spec/support.rb +10 -0
- data/lib/contracts-ruby2/spec/support_spec.rb +21 -0
- data/lib/contracts-ruby2/spec/validators_spec.rb +47 -0
- data/lib/contracts-ruby3/CHANGELOG.markdown +117 -0
- data/lib/contracts-ruby3/Gemfile +21 -0
- data/lib/contracts-ruby3/LICENSE +23 -0
- data/lib/contracts-ruby3/README.md +114 -0
- data/lib/contracts-ruby3/Rakefile +10 -0
- data/lib/contracts-ruby3/TODO.markdown +6 -0
- data/lib/contracts-ruby3/TUTORIAL.md +773 -0
- data/lib/contracts-ruby3/benchmarks/bench.rb +67 -0
- data/lib/contracts-ruby3/benchmarks/hash.rb +69 -0
- data/lib/contracts-ruby3/benchmarks/invariants.rb +91 -0
- data/lib/contracts-ruby3/benchmarks/io.rb +62 -0
- data/lib/contracts-ruby3/benchmarks/wrap_test.rb +57 -0
- data/lib/contracts-ruby3/contracts.gemspec +20 -0
- data/lib/contracts-ruby3/cucumber.yml +1 -0
- data/lib/contracts-ruby3/dependabot.yml +20 -0
- data/lib/contracts-ruby3/features/README.md +17 -0
- data/lib/contracts-ruby3/features/basics/functype.feature +71 -0
- data/lib/contracts-ruby3/features/basics/pretty-print.feature +241 -0
- data/lib/contracts-ruby3/features/basics/simple_example.feature +210 -0
- data/lib/contracts-ruby3/features/builtin_contracts/README.md +22 -0
- data/lib/contracts-ruby3/features/builtin_contracts/and.feature +103 -0
- data/lib/contracts-ruby3/features/builtin_contracts/any.feature +44 -0
- data/lib/contracts-ruby3/features/builtin_contracts/args.feature +80 -0
- data/lib/contracts-ruby3/features/builtin_contracts/array_of.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/bool.feature +64 -0
- data/lib/contracts-ruby3/features/builtin_contracts/enum.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/eq.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/exactly.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/func.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/hash_of.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/int.feature +93 -0
- data/lib/contracts-ruby3/features/builtin_contracts/keyword_args.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/maybe.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/nat.feature +115 -0
- data/lib/contracts-ruby3/features/builtin_contracts/nat_pos.feature +119 -0
- data/lib/contracts-ruby3/features/builtin_contracts/neg.feature +115 -0
- data/lib/contracts-ruby3/features/builtin_contracts/none.feature +145 -0
- data/lib/contracts-ruby3/features/builtin_contracts/not.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/num.feature +64 -0
- data/lib/contracts-ruby3/features/builtin_contracts/or.feature +83 -0
- data/lib/contracts-ruby3/features/builtin_contracts/pos.feature +116 -0
- data/lib/contracts-ruby3/features/builtin_contracts/range_of.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/respond_to.feature +78 -0
- data/lib/contracts-ruby3/features/builtin_contracts/send.feature +147 -0
- data/lib/contracts-ruby3/features/builtin_contracts/set_of.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/xor.feature +99 -0
- data/lib/contracts-ruby3/features/support/env.rb +8 -0
- data/lib/contracts-ruby3/lib/contracts/attrs.rb +26 -0
- data/lib/contracts-ruby3/lib/contracts/builtin_contracts.rb +575 -0
- data/lib/contracts-ruby3/lib/contracts/call_with.rb +119 -0
- data/lib/contracts-ruby3/lib/contracts/core.rb +54 -0
- data/lib/contracts-ruby3/lib/contracts/decorators.rb +50 -0
- data/lib/contracts-ruby3/lib/contracts/engine/base.rb +137 -0
- data/lib/contracts-ruby3/lib/contracts/engine/eigenclass.rb +51 -0
- data/lib/contracts-ruby3/lib/contracts/engine/target.rb +72 -0
- data/lib/contracts-ruby3/lib/contracts/engine.rb +28 -0
- data/lib/contracts-ruby3/lib/contracts/errors.rb +74 -0
- data/lib/contracts-ruby3/lib/contracts/formatters.rb +140 -0
- data/lib/contracts-ruby3/lib/contracts/invariants.rb +72 -0
- data/lib/contracts-ruby3/lib/contracts/method_handler.rb +197 -0
- data/lib/contracts-ruby3/lib/contracts/method_reference.rb +102 -0
- data/lib/contracts-ruby3/lib/contracts/support.rb +63 -0
- data/lib/contracts-ruby3/lib/contracts/validators.rb +143 -0
- data/lib/contracts-ruby3/lib/contracts/version.rb +5 -0
- data/lib/contracts-ruby3/lib/contracts.rb +290 -0
- data/lib/contracts-ruby3/script/docs-release +3 -0
- data/lib/contracts-ruby3/script/docs-staging +3 -0
- data/lib/contracts-ruby3/script/rubocop.rb +5 -0
- data/lib/contracts-ruby3/spec/attrs_spec.rb +119 -0
- data/lib/contracts-ruby3/spec/builtin_contracts_spec.rb +457 -0
- data/lib/contracts-ruby3/spec/contracts_spec.rb +773 -0
- data/lib/contracts-ruby3/spec/fixtures/fixtures.rb +725 -0
- data/lib/contracts-ruby3/spec/invariants_spec.rb +17 -0
- data/lib/contracts-ruby3/spec/methods_spec.rb +54 -0
- data/lib/contracts-ruby3/spec/module_spec.rb +18 -0
- data/lib/contracts-ruby3/spec/override_validators_spec.rb +162 -0
- data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
- data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
- data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
- data/lib/contracts-ruby3/spec/spec_helper.rb +102 -0
- data/lib/contracts-ruby3/spec/support.rb +10 -0
- data/lib/contracts-ruby3/spec/support_spec.rb +21 -0
- data/lib/contracts-ruby3/spec/validators_spec.rb +47 -0
- data/lib/entitlements/data/groups/calculated/yaml.rb +7 -1
- data/lib/entitlements/data/people/yaml.rb +9 -1
- data/lib/entitlements/extras/ldap_group/rules/ldap_group.rb +5 -1
- data/lib/entitlements/extras/orgchart/person_methods.rb +7 -1
- data/lib/entitlements.rb +13 -2
- data/lib/ruby_version_check.rb +17 -0
- metadata +209 -14
@@ -0,0 +1,773 @@
|
|
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
|
+
## Contracts with attributes
|
610
|
+
|
611
|
+
You can include the `Contracts::Attrs` module in your class/module to get access to attribute utilities:
|
612
|
+
|
613
|
+
- `attr_reader_with_contract <symbol>..., <contract>`
|
614
|
+
- Wraps `attr_reader`, validates contract upon 'getting'
|
615
|
+
- `attr_writer_with_contract <symbol>..., <contract>`
|
616
|
+
- Wraps `attr_writer`, validates contract upon 'setting'
|
617
|
+
- `attr_accessor_with_contract <symbol>..., <contract>`
|
618
|
+
- Wraps `attr_accessor`, validates contract upon 'getting' or 'setting'
|
619
|
+
|
620
|
+
### Example
|
621
|
+
|
622
|
+
```ruby
|
623
|
+
class Person
|
624
|
+
include Contracts::Core
|
625
|
+
include Contracts::Attrs
|
626
|
+
|
627
|
+
attr_accessor_with_contract :name, String
|
628
|
+
end
|
629
|
+
|
630
|
+
person = Person.new
|
631
|
+
person.name = 'Jane'
|
632
|
+
person.name = 1.4 # This results in a contract error!
|
633
|
+
```
|
634
|
+
|
635
|
+
## Disabling contracts
|
636
|
+
|
637
|
+
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.
|
638
|
+
|
639
|
+
## Method overloading
|
640
|
+
|
641
|
+
You can use contracts for method overloading! This is commonly called "pattern matching" in functional programming languages.
|
642
|
+
|
643
|
+
For example, here's a factorial function without method overloading:
|
644
|
+
|
645
|
+
```ruby
|
646
|
+
Contract C::Num => C::Num
|
647
|
+
def fact x
|
648
|
+
if x == 1
|
649
|
+
x
|
650
|
+
else
|
651
|
+
x * fact(x - 1)
|
652
|
+
end
|
653
|
+
end
|
654
|
+
```
|
655
|
+
|
656
|
+
Here it is again, re-written with method overloading:
|
657
|
+
|
658
|
+
```ruby
|
659
|
+
Contract 1 => 1
|
660
|
+
def fact x
|
661
|
+
x
|
662
|
+
end
|
663
|
+
|
664
|
+
Contract C::Num => C::Num
|
665
|
+
def fact x
|
666
|
+
x * fact(x - 1)
|
667
|
+
end
|
668
|
+
```
|
669
|
+
|
670
|
+
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.
|
671
|
+
|
672
|
+
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:
|
673
|
+
|
674
|
+
```ruby
|
675
|
+
Contract lambda{|n| n < 12 } => Ticket
|
676
|
+
def get_ticket(age)
|
677
|
+
ChildTicket.new(age: age)
|
678
|
+
end
|
679
|
+
|
680
|
+
Contract lambda{|n| n >= 12 } => Ticket
|
681
|
+
def get_ticket(age)
|
682
|
+
AdultTicket.new(age: age)
|
683
|
+
end
|
684
|
+
|
685
|
+
```
|
686
|
+
|
687
|
+
Note that the second `get_ticket` contract above could have been simplified to:
|
688
|
+
|
689
|
+
```ruby
|
690
|
+
Contract C::Num => Ticket
|
691
|
+
```
|
692
|
+
|
693
|
+
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.
|
694
|
+
|
695
|
+
## Contracts in modules
|
696
|
+
|
697
|
+
Usage is the same as contracts in classes:
|
698
|
+
|
699
|
+
```ruby
|
700
|
+
module M
|
701
|
+
include Contracts::Core
|
702
|
+
|
703
|
+
Contract String => String
|
704
|
+
def self.parse
|
705
|
+
# do some hard parsing
|
706
|
+
end
|
707
|
+
end
|
708
|
+
```
|
709
|
+
|
710
|
+
## Invariants
|
711
|
+
|
712
|
+
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.
|
713
|
+
|
714
|
+
**NOTE**: Only methods with contracts will be affected.
|
715
|
+
|
716
|
+
A simple example:
|
717
|
+
|
718
|
+
```ruby
|
719
|
+
class MyBirthday < Struct.new(:day, :month)
|
720
|
+
include Contracts::Core
|
721
|
+
include Contracts::Invariants
|
722
|
+
|
723
|
+
invariant(:day) { 1 <= day && day <= 31 }
|
724
|
+
invariant(:month) { 1 <= month && month <= 12 }
|
725
|
+
|
726
|
+
Contract C::None => Fixnum
|
727
|
+
def silly_next_day!
|
728
|
+
self.day += 1
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
birthday = MyBirthday.new(31, 12)
|
733
|
+
birthday.silly_next_day!
|
734
|
+
```
|
735
|
+
|
736
|
+
If you run it, last line will generate invariant violation:
|
737
|
+
|
738
|
+
```ruby
|
739
|
+
./invariant.rb:38:in `failure_callback': Invariant violation: (RuntimeError)
|
740
|
+
Expected: day condition to be true
|
741
|
+
Actual: false
|
742
|
+
Value guarded in: MyBirthday::silly_next_day!
|
743
|
+
At: main.rb:9
|
744
|
+
```
|
745
|
+
|
746
|
+
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.
|
747
|
+
|
748
|
+
## Using contracts within your own code
|
749
|
+
|
750
|
+
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:
|
751
|
+
|
752
|
+
```ruby
|
753
|
+
data = parse(user_input)
|
754
|
+
unless Contract.valid?(data, HashOf[String,Nat])
|
755
|
+
raise UserInputError.new(user_input)
|
756
|
+
end
|
757
|
+
```
|
758
|
+
|
759
|
+
## Auto-generate documentation using contracts
|
760
|
+
|
761
|
+
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!
|
762
|
+
|
763
|
+
## Misc
|
764
|
+
|
765
|
+
Please submit any bugs [here](https://github.com/egonSchiele/contracts.ruby/issues) and I'll try to get them resolved ASAP!
|
766
|
+
|
767
|
+
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).
|
768
|
+
|
769
|
+
If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :)
|
770
|
+
|
771
|
+
See the [wiki](https://github.com/egonSchiele/contracts.ruby/wiki) for more info.
|
772
|
+
|
773
|
+
Happy Coding!
|