contracts 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +34 -0
- data/README.md +75 -0
- data/TODO.markdown +6 -0
- data/TUTORIAL.md +485 -0
- data/benchmarks/bench.rb +67 -0
- data/benchmarks/invariants.rb +81 -0
- data/benchmarks/wrap_test.rb +59 -0
- data/contracts.gemspec +13 -0
- data/lib/contracts.rb +108 -23
- data/lib/{builtin_contracts.rb → contracts/builtin_contracts.rb} +1 -1
- data/lib/contracts/decorators.rb +179 -0
- data/lib/contracts/invariants.rb +75 -0
- data/lib/contracts/support.rb +22 -0
- data/lib/{testable.rb → contracts/testable.rb} +0 -0
- data/lib/contracts/version.rb +3 -0
- data/spec/builtin_contracts_spec.rb +216 -0
- data/spec/contracts_spec.rb +273 -0
- data/spec/fixtures/fixtures.rb +276 -0
- data/spec/invariants_spec.rb +19 -0
- data/spec/module_spec.rb +17 -0
- data/spec/spec_helper.rb +94 -0
- metadata +45 -43
- data/lib/decorators.rb +0 -164
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 77eb6f2fcb9b44586ae85de49add3fff7e1ece96
|
4
|
+
data.tar.gz: 43d47be3ae6f1b13b997dde2ba6701b5578f84a8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: adaf871d3c827fcfad8b1ea3731d34ddc5023ab4d952a668c797e89bff7c798c1464db1acf80ae1eb446867ba817e56fc3be0c9401dd3e35d6aedea54db69c52
|
7
|
+
data.tar.gz: 297cc7359bf60acac92450b4f2c1a96bae8e845dedf3f088b01f86e7271f2d312d4449d2c908a5ebacadd46f1f9f2031a4ee5eccd8b35bf35d3420d5b95a821c
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
contracts (0.4)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.2.5)
|
10
|
+
hirb (0.7.2)
|
11
|
+
method_profiler (2.0.1)
|
12
|
+
hirb (>= 0.6.0)
|
13
|
+
rspec (3.1.0)
|
14
|
+
rspec-core (~> 3.1.0)
|
15
|
+
rspec-expectations (~> 3.1.0)
|
16
|
+
rspec-mocks (~> 3.1.0)
|
17
|
+
rspec-core (3.1.7)
|
18
|
+
rspec-support (~> 3.1.0)
|
19
|
+
rspec-expectations (3.1.2)
|
20
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
21
|
+
rspec-support (~> 3.1.0)
|
22
|
+
rspec-mocks (3.1.3)
|
23
|
+
rspec-support (~> 3.1.0)
|
24
|
+
rspec-support (3.1.2)
|
25
|
+
ruby-prof (0.15.2)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
contracts!
|
32
|
+
method_profiler
|
33
|
+
rspec
|
34
|
+
ruby-prof
|
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# contracts.ruby
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/egonSchiele/contracts.ruby.png?branch=master)](https://travis-ci.org/egonSchiele/contracts.ruby)
|
4
|
+
|
5
|
+
Contracts let you clearly – even beautifully – express how your code behaves, and free you from writing tons of boilerplate, defensive code.
|
6
|
+
|
7
|
+
You can think of contracts as `assert` on steroids.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
gem install contracts
|
12
|
+
|
13
|
+
## Hello World
|
14
|
+
|
15
|
+
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.
|
16
|
+
|
17
|
+
Here is a simple contract:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
Contract Num => Num
|
21
|
+
def double(x)
|
22
|
+
```
|
23
|
+
|
24
|
+
This says that double expects a number and returns a number. Here's the full code:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'contracts'
|
28
|
+
include Contracts
|
29
|
+
|
30
|
+
Contract Num => Num
|
31
|
+
def double(x)
|
32
|
+
x * 2
|
33
|
+
end
|
34
|
+
|
35
|
+
puts double("oops")
|
36
|
+
```
|
37
|
+
|
38
|
+
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:
|
39
|
+
|
40
|
+
./contracts.rb:34:in `failure_callback': Contract violation: (RuntimeError)
|
41
|
+
Expected: Contracts::Num,
|
42
|
+
Actual: "oops"
|
43
|
+
Value guarded in: Object::double
|
44
|
+
With Contract: Contracts::Num, Contracts::Num
|
45
|
+
At: main.rb:6
|
46
|
+
...stack trace...
|
47
|
+
|
48
|
+
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.
|
49
|
+
|
50
|
+
## Tutorial
|
51
|
+
|
52
|
+
Check out [this awesome tutorial](http://egonschiele.github.com/contracts.ruby).
|
53
|
+
|
54
|
+
## Use Cases
|
55
|
+
|
56
|
+
Check out [this screencast](https://vimeo.com/85883356).
|
57
|
+
|
58
|
+
## Performance
|
59
|
+
|
60
|
+
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.
|
61
|
+
|
62
|
+
**Q.** What Rubies can I use this with?
|
63
|
+
|
64
|
+
**A.** It's been tested with `1.8.7`, `1.9.2`, `1.9.3`, `2.0.0`, `2.1`, `2.2`, and `jruby` (both 1.8 and 1.9 modes).
|
65
|
+
|
66
|
+
If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :)
|
67
|
+
|
68
|
+
## Credits
|
69
|
+
|
70
|
+
Inspired by [contracts.coffee](http://disnetdev.com/contracts.coffee/).
|
71
|
+
|
72
|
+
Copyright 2012 [Aditya Bhargava](http://adit.io).
|
73
|
+
Major improvements by [Alexey Fedorov](https://github.com/waterlink).
|
74
|
+
|
75
|
+
BSD Licensed.
|
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,485 @@
|
|
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 Num, Num => 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
|
+
include Contracts
|
33
|
+
|
34
|
+
Contract Num, Num => Num
|
35
|
+
def add(a, b)
|
36
|
+
a + b
|
37
|
+
end
|
38
|
+
|
39
|
+
puts add(1, "foo")
|
40
|
+
```
|
41
|
+
|
42
|
+
You'll see a detailed error message like so:
|
43
|
+
|
44
|
+
./contracts.rb:60:in `failure_callback': Contract violation: (RuntimeError)
|
45
|
+
Expected: Contracts::Num,
|
46
|
+
Actual: "foo"
|
47
|
+
Value guarded in: Object::add
|
48
|
+
With Contract: Contracts::Num, Contracts::Num
|
49
|
+
At: foo.rb:6
|
50
|
+
|
51
|
+
That tells you that your contract was violated! `add` expected a `Num`, and got a string (`"foo"`) instead.
|
52
|
+
By default, an exception is thrown when a contract fails. This can be changed to do whatever you want. More on this later.
|
53
|
+
|
54
|
+
You can also see the contract for a function with the `functype` method:
|
55
|
+
|
56
|
+
functype(:add)
|
57
|
+
=> "add :: Num, Num => Num"
|
58
|
+
|
59
|
+
This can be useful if you're in a repl and want to figure out how a function should be used.
|
60
|
+
|
61
|
+
## Builtin Contracts
|
62
|
+
|
63
|
+
`Num` is one of the builtin contracts that contracts.ruby comes with. The builtin contracts are in the `Contracts` namespace. The easiest way to use them is to put `include Contracts` at the top of your file, but beware that they will pollute your namespace with new class names.
|
64
|
+
|
65
|
+
contracts.ruby comes with a lot of builtin contracts, including:
|
66
|
+
|
67
|
+
Num, Pos, Neg, Any, None, Or, Xor, And, Not, RespondTo, Send, Exactly, ArrayOf, HashOf, Bool, Maybe
|
68
|
+
|
69
|
+
To see all the builtin contracts and what they do, check out the [rdoc](http://rubydoc.info/gems/contracts/Contracts).
|
70
|
+
|
71
|
+
## More Examples
|
72
|
+
|
73
|
+
### Hello, World
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
Contract String => nil
|
77
|
+
def hello(name)
|
78
|
+
puts "hello, #{name}!"
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
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:
|
83
|
+
|
84
|
+
- the name of a class (like `String` or `Fixnum`)
|
85
|
+
- a constant (like `nil` or `1`)
|
86
|
+
- a `Proc` that takes a value and returns true or false to indicate whether the contract passed or not
|
87
|
+
- a class that responds to the `valid?` class method (more on this later)
|
88
|
+
- an instance of a class that responds to the `valid?` method (more on this later)
|
89
|
+
|
90
|
+
### A Double Function
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
Contract Or[Fixnum, Float] => Or[Fixnum, Float]
|
94
|
+
def double(x)
|
95
|
+
2 * x
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
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.
|
100
|
+
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:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
Contract Or.new(Fixnum, Float) => Or.new(Fixnum, Float)
|
104
|
+
```
|
105
|
+
|
106
|
+
All the builtin contracts have overridden the square brackets (`[]`) to give the same functionality. So you could write
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
Contract Or[Fixnum, Float] => Or[Fixnum, Float]
|
110
|
+
```
|
111
|
+
|
112
|
+
or
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
Contract Or.new(Fixnum, Float) => Or.new(Fixnum, Float)
|
116
|
+
```
|
117
|
+
|
118
|
+
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.
|
119
|
+
|
120
|
+
### A Product Function
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
Contract ArrayOf[Num] => Num
|
124
|
+
def product(vals)
|
125
|
+
total = 1
|
126
|
+
vals.each do |val|
|
127
|
+
total *= val
|
128
|
+
end
|
129
|
+
total
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
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.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
# passes
|
137
|
+
product([1, 2, 3, 4])
|
138
|
+
|
139
|
+
# fails
|
140
|
+
product([1, 2, 3, "foo"])
|
141
|
+
```
|
142
|
+
|
143
|
+
### Another Product Function
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
Contract Args[Num] => Num
|
147
|
+
def product(*vals)
|
148
|
+
total = 1
|
149
|
+
vals.each do |val|
|
150
|
+
total *= val
|
151
|
+
end
|
152
|
+
total
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
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,
|
157
|
+
|
158
|
+
`Args[Num]` means they should all be numbers.
|
159
|
+
|
160
|
+
`Args[Or[Num, String]]` means they should all be numbers or strings.
|
161
|
+
|
162
|
+
`Args[Any]` means all arguments are allowed (`Any` is a contract that passes for any argument).
|
163
|
+
|
164
|
+
### Contracts On Arrays
|
165
|
+
|
166
|
+
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:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
# a function that takes an array of two elements...a person's age and a person's name.
|
170
|
+
Contract [Num, String] => nil
|
171
|
+
def person(data)
|
172
|
+
p data
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
If you don't know how many elements it's going to have, use `ArrayOf`.
|
177
|
+
|
178
|
+
### Contracts On Hashes
|
179
|
+
|
180
|
+
Here's a contract that requires a Hash. We can put contracts on each of the keys:
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
# note the parentheses around the hash; without those you would get a syntax error
|
184
|
+
Contract ({ :age => Num, :name => String }) => nil
|
185
|
+
def person(data)
|
186
|
+
p data
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
190
|
+
Then if someone tries to call the function with bad data, it will fail:
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
# error: age can't be nil!
|
194
|
+
person({:name => "Adit", :age => nil})
|
195
|
+
```
|
196
|
+
|
197
|
+
You don't need to put a contract on every key. So this call would succeed:
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
person({:name => "Adit", :age => 42, :foo => "bar"})
|
201
|
+
```
|
202
|
+
|
203
|
+
even though we don't specify a type for `:foo`.
|
204
|
+
|
205
|
+
Peruse this contract on the keys and values of a Hash.
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
Contract HashOf[Symbol, Num] => Num
|
209
|
+
def give_largest_value(hsh)
|
210
|
+
hsh.values.max
|
211
|
+
end
|
212
|
+
```
|
213
|
+
Which you use like so:
|
214
|
+
```ruby
|
215
|
+
# succeeds
|
216
|
+
give_largest_value(a: 1, b: 2, c: 3) # returns 3
|
217
|
+
|
218
|
+
# fails
|
219
|
+
give_largest_value("a" => 1, 2 => 2, c: 3)
|
220
|
+
```
|
221
|
+
|
222
|
+
### Contracts On Functions
|
223
|
+
|
224
|
+
If you're writing higher-order functions (functions that take functions as parameters) and want to write a contract for the passed-in function, you can!
|
225
|
+
Use the `Func` contract. `Func` takes a contract as it's argument, and uses that contract on the function that you pass in.
|
226
|
+
|
227
|
+
Here's a `map` function that requires an array of numbers, and a function that takes a number and returns a number:
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
Contract ArrayOf[Num], Func[Num => Num] => ArrayOf[Num]
|
231
|
+
def map(arr, func)
|
232
|
+
ret = []
|
233
|
+
arr.each do |x|
|
234
|
+
ret << func[x]
|
235
|
+
end
|
236
|
+
ret
|
237
|
+
end
|
238
|
+
```
|
239
|
+
|
240
|
+
This will add the contract `Num => Num` on `func`. Try it with these two examples:
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
p map([1, 2, 3], lambda { |x| x + 1 }) # works
|
244
|
+
p map([1, 2, 3], lambda { |x| "oops" }) # fails, the lambda returns a string.
|
245
|
+
```
|
246
|
+
|
247
|
+
### Returning Multiple Values
|
248
|
+
Treat the return value as an array. For example, here's a function that returns two numbers:
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
Contract Num => [Num, Num]
|
252
|
+
def mult(x)
|
253
|
+
return x, x+1
|
254
|
+
end
|
255
|
+
```
|
256
|
+
|
257
|
+
## Synonyms For Contracts
|
258
|
+
|
259
|
+
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:
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
Contract String => Or[Hash, nil]
|
263
|
+
def some_func(str)
|
264
|
+
```
|
265
|
+
|
266
|
+
You can make your contract more meaningful with a synonym:
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
# the synonym
|
270
|
+
Person = Or[Hash, nil]
|
271
|
+
|
272
|
+
# use the synonym here
|
273
|
+
Contract String => Person
|
274
|
+
def some_func(str)
|
275
|
+
```
|
276
|
+
|
277
|
+
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.
|
278
|
+
|
279
|
+
## Defining Your Own Contracts
|
280
|
+
|
281
|
+
Contracts are very easy to define. To re-iterate, there are 5 kinds of contracts:
|
282
|
+
|
283
|
+
- the name of a class (like `String` or `Fixnum`)
|
284
|
+
- a constant (like `nil` or `1`)
|
285
|
+
- a `Proc` that takes a value and returns true or false to indicate whether the contract passed or not
|
286
|
+
- a class that responds to the `valid?` class method (more on this later)
|
287
|
+
- an instance of a class that responds to the `valid?` method (more on this later)
|
288
|
+
|
289
|
+
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:
|
290
|
+
|
291
|
+
### A Proc
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
Contract lambda { |x| x.is_a? Numeric } => Num
|
295
|
+
def double(x)
|
296
|
+
```
|
297
|
+
|
298
|
+
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.
|
299
|
+
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:
|
300
|
+
|
301
|
+
### A Class With `valid?` As a Class Method
|
302
|
+
|
303
|
+
Here's how the `Num` class is defined. It does exactly what the `lambda` did in the previous example:
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
class Num
|
307
|
+
def self.valid? val
|
308
|
+
val.is_a? Numeric
|
309
|
+
end
|
310
|
+
end
|
311
|
+
```
|
312
|
+
|
313
|
+
The `valid?` class method takes one parameter: the argument that is getting passed to the function. It returns true or false.
|
314
|
+
|
315
|
+
### A Class With `valid?` As an Instance Method
|
316
|
+
|
317
|
+
Here's how the `Or` class is defined:
|
318
|
+
|
319
|
+
```ruby
|
320
|
+
class Or < CallableClass
|
321
|
+
def initialize(*vals)
|
322
|
+
@vals = vals
|
323
|
+
end
|
324
|
+
|
325
|
+
def valid?(val)
|
326
|
+
@vals.any? do |contract|
|
327
|
+
res, _ = Contract.valid?(val, contract)
|
328
|
+
res
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
```
|
333
|
+
|
334
|
+
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.
|
335
|
+
|
336
|
+
This class inherits from `CallableClass`, which allows us to use `[]` when using the class:
|
337
|
+
|
338
|
+
```ruby
|
339
|
+
Contract Or[Fixnum, Float] => Num
|
340
|
+
def double(x)
|
341
|
+
2 * x
|
342
|
+
end
|
343
|
+
```
|
344
|
+
|
345
|
+
Without `CallableClass`, we would have to use `.new` instead:
|
346
|
+
|
347
|
+
```ruby
|
348
|
+
Contract Or.new(Fixnum, Float) => Num
|
349
|
+
def double(x)
|
350
|
+
# etc
|
351
|
+
```
|
352
|
+
|
353
|
+
You can use `CallableClass` in your own contracts to make them callable using `[]`.
|
354
|
+
|
355
|
+
## Customizing Error Messages
|
356
|
+
|
357
|
+
When a contract fails, part of the error message prints the contract:
|
358
|
+
|
359
|
+
...
|
360
|
+
Expected: Contracts::Num,
|
361
|
+
...
|
362
|
+
|
363
|
+
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:
|
364
|
+
|
365
|
+
```ruby
|
366
|
+
def Num.to_s
|
367
|
+
"a number please"
|
368
|
+
end
|
369
|
+
```
|
370
|
+
|
371
|
+
Now the error says:
|
372
|
+
|
373
|
+
...
|
374
|
+
Expected: a number please,
|
375
|
+
...
|
376
|
+
|
377
|
+
## Failure Callbacks
|
378
|
+
|
379
|
+
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.
|
380
|
+
|
381
|
+
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:
|
382
|
+
|
383
|
+
```ruby
|
384
|
+
Contract.override_failure_callback do |data|
|
385
|
+
puts "You had an error"
|
386
|
+
puts failure_msg(data)
|
387
|
+
end
|
388
|
+
```
|
389
|
+
|
390
|
+
`failure_msg` is a function that prints out information about the failure. Your failure callback gets a hash with the following values:
|
391
|
+
|
392
|
+
{
|
393
|
+
:arg => the argument to the method,
|
394
|
+
:contract => the contract that got violated,
|
395
|
+
:class => the method's class,
|
396
|
+
:method => the method,
|
397
|
+
:contracts => the contract object
|
398
|
+
}
|
399
|
+
|
400
|
+
If your failure callback returns `false`, the method that the contract is guarding will not be called (the default behaviour).
|
401
|
+
|
402
|
+
## Disabling contracts
|
403
|
+
|
404
|
+
If you want to disable contracts, set the `NO_CONTRACTS` environment variable. This will disable contracts completely and you won't have a performance hit.
|
405
|
+
|
406
|
+
## Method overloading
|
407
|
+
|
408
|
+
You can use contracts for method overloading! For example, here's a factorial function without method overloading:
|
409
|
+
|
410
|
+
```ruby
|
411
|
+
Contract Num => Num
|
412
|
+
def fact x
|
413
|
+
if x == 1
|
414
|
+
x
|
415
|
+
else
|
416
|
+
x * fact(x - 1)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
```
|
420
|
+
|
421
|
+
Here it is again, re-written with method overloading:
|
422
|
+
|
423
|
+
```ruby
|
424
|
+
Contract 1 => 1
|
425
|
+
def fact x
|
426
|
+
x
|
427
|
+
end
|
428
|
+
|
429
|
+
Contract Num => Num
|
430
|
+
def fact x
|
431
|
+
x * fact(x - 1)
|
432
|
+
end
|
433
|
+
```
|
434
|
+
|
435
|
+
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.
|
436
|
+
|
437
|
+
## Invariants
|
438
|
+
|
439
|
+
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.
|
440
|
+
|
441
|
+
**NOTE**: Only methods with contracts will be affected.
|
442
|
+
|
443
|
+
A simple example:
|
444
|
+
|
445
|
+
```ruby
|
446
|
+
class MyBirthday < Struct.new(:day, :month)
|
447
|
+
include Contracts
|
448
|
+
include Contracts:Invariants
|
449
|
+
|
450
|
+
Invariant(:day) { 1 <= day && day <= 31 }
|
451
|
+
Invariant(:month) { 1 <= month && month <= 12 }
|
452
|
+
|
453
|
+
Contract None => Fixnum
|
454
|
+
def silly_next_day!
|
455
|
+
self.day += 1
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
birthday = MyBirthday.new(31, 12)
|
460
|
+
birthday.silly_next_day!
|
461
|
+
```
|
462
|
+
|
463
|
+
If you run it, last line will generate invariant violation:
|
464
|
+
|
465
|
+
```ruby
|
466
|
+
./invariant.rb:38:in `failure_callback': Invariant violation: (RuntimeError)
|
467
|
+
Expected: day condition to be true
|
468
|
+
Actual: false
|
469
|
+
Value guarded in: MyBirthday::silly_next_day!
|
470
|
+
At: main.rb:9
|
471
|
+
```
|
472
|
+
|
473
|
+
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.
|
474
|
+
|
475
|
+
## Misc
|
476
|
+
|
477
|
+
Please submit any bugs [here](https://github.com/egonSchiele/contracts.ruby/issues) and I'll try to get them resolved ASAP!
|
478
|
+
|
479
|
+
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).
|
480
|
+
|
481
|
+
If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :)
|
482
|
+
|
483
|
+
See the [wiki](https://github.com/egonSchiele/contracts.ruby/wiki) for more info.
|
484
|
+
|
485
|
+
Happy Coding!
|