contracts 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem "rspec"
7
+ end
8
+
9
+ group :development do
10
+ gem "method_profiler"
11
+ gem "ruby-prof"
12
+ end
@@ -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
@@ -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.
@@ -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
@@ -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!