TUTORIAL.md in contracts-0.9 vs TUTORIAL.md in contracts-0.10
- old
+ new
@@ -65,29 +65,45 @@
`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` module in your class/module.
contracts.ruby comes with a lot of built-in contracts, including the following:
-* [`Num`](http://www.rubydoc.info/gems/contracts/Contracts/Num) – checks that the argument is `Numeric`
-* [`Pos`](http://www.rubydoc.info/gems/contracts/Contracts/Pos) – checks that the argument is a positive number
-* [`Neg`](http://www.rubydoc.info/gems/contracts/Contracts/Neg) – checks that the argument is a negative number
-* [`Nat`](http://www.rubydoc.info/gems/contracts/Contracts/Nat) – checks that the argument is a natural number
-* [`Bool`](http://www.rubydoc.info/gems/contracts/Contracts/Bool) – checks that the argument is `true` or `false`
-* [`Any`](http://www.rubydoc.info/gems/contracts/Contracts/Any) – Passes for any argument. Use when the argument has no constraints.
-* [`None`](http://www.rubydoc.info/gems/contracts/Contracts/None) – Fails for any argument. Use when the method takes no arguments.
-* [`Or`](http://www.rubydoc.info/gems/contracts/Contracts/Or) – passes if any of the given contracts pass, e.g. `Or[Fixnum, Float]`
-* [`Xor`](http://www.rubydoc.info/gems/contracts/Contracts/Xor) – passes if exactly one of the given contracts pass, e.g. `Xor[Fixnum, Float]`
-* [`And`](http://www.rubydoc.info/gems/contracts/Contracts/And) – passes if all contracts pass, e.g. `And[Fixnum, Float]`
-* [`Not`](http://www.rubydoc.info/gems/contracts/Contracts/Not) – passes if all contracts fail for the given argument, e.g. `Not[nil]`
-* [`ArrayOf`](http://www.rubydoc.info/gems/contracts/Contracts/ArrayOf) – checks that the argument is an array, and all elements pass the given contract, e.g. `ArrayOf[Num]`
-* [`SetOf`](http://www.rubydoc.info/gems/contracts/Contracts/SetOf) – checks that the argument is a set, and all elements pass the given contract, e.g. `SetOf[Num]`
-* [`HashOf`](http://www.rubydoc.info/gems/contracts/Contracts/HashOf) – checks that the argument is a hash, and all keys and values pass the given contract, e.g. `HashOf[Symbol => String]`
-* [`Maybe`](http://www.rubydoc.info/gems/contracts/Contracts/Maybe) – passes if the argument is `nil`, or if the given contract passes
-* [`RespondTo`](http://www.rubydoc.info/gems/contracts/Contracts/RespondTo) – checks that the argument responds to all of the given methods, e.g. `RespondTo[:password, :credit_card]`
-* [`Send`](http://www.rubydoc.info/gems/contracts/Contracts/Send) – checks that all named methods return true, e.g. `Send[:valid?]`
-* [`Exactly`](http://www.rubydoc.info/gems/contracts/Contracts/Exactly) – checks that the argument has the given type, not accepting sub-classes, e.g. `Exactly[Numeric]`.
+* Basic types
+ * [`Num`](http://www.rubydoc.info/gems/contracts/Contracts/Num) – checks that the argument is `Numeric`
+ * [`Pos`](http://www.rubydoc.info/gems/contracts/Contracts/Pos) – checks that the argument is a positive number
+ * [`Neg`](http://www.rubydoc.info/gems/contracts/Contracts/Neg) – checks that the argument is a negative number
+ * [`Nat`](http://www.rubydoc.info/gems/contracts/Contracts/Nat) – checks that the argument is a natural number (>= 0)
+ * [`Bool`](http://www.rubydoc.info/gems/contracts/Contracts/Bool) – checks that the argument is `true` or `false`
+ * [`Any`](http://www.rubydoc.info/gems/contracts/Contracts/Any) – Passes for any argument. Use when the argument has no constraints.
+ * [`None`](http://www.rubydoc.info/gems/contracts/Contracts/None) – Fails for any argument. Use when the method takes no arguments.
+* Logical combinations
+ * [`Maybe`](http://www.rubydoc.info/gems/contracts/Contracts/Maybe) – specifies that a value _may be_ nil, e.g. `Maybe[String]` (equivalent to `Or[String,nil]`)
+ * [`Or`](http://www.rubydoc.info/gems/contracts/Contracts/Or) – passes if any of the given contracts pass, e.g. `Or[Fixnum, Float]`
+ * [`Xor`](http://www.rubydoc.info/gems/contracts/Contracts/Xor) – passes if exactly one of the given contracts pass, e.g. `Xor[Fixnum, Float]`
+ * [`And`](http://www.rubydoc.info/gems/contracts/Contracts/And) – passes if all contracts pass, e.g. `And[Nat, -> (n) { n.even? }]`
+ * [`Not`](http://www.rubydoc.info/gems/contracts/Contracts/Not) – passes if all contracts fail for the given argument, e.g. `Not[nil]`
+
+* Collections
+ * [`ArrayOf`](http://www.rubydoc.info/gems/contracts/Contracts/ArrayOf) – checks that the argument is an array, and all elements pass the given contract, e.g. `ArrayOf[Num]`
+ * [`SetOf`](http://www.rubydoc.info/gems/contracts/Contracts/SetOf) – checks that the argument is a set, and all elements pass the given contract, e.g. `SetOf[Num]`
+ * [`HashOf`](http://www.rubydoc.info/gems/contracts/Contracts/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]`
+ * [`RangeOf`](http://www.rubydoc.info/gems/contracts/Contracts/RangeOf) – checks that the argument is a range whose elements (#first and #last) pass the given contract, e.g. `RangeOf[Date]`
+
+* Keyword arguments
+ * [`KeywordArgs`](http://www.rubydoc.info/gems/contracts/Contracts/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]]`
+ * [`Optional`](http://www.rubydoc.info/gems/contracts/Contracts/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]`
+
+* Duck typing
+ * [`RespondTo`](http://www.rubydoc.info/gems/contracts/Contracts/RespondTo) – checks that the argument responds to all of the given methods, e.g. `RespondTo[:password, :credit_card]`
+ * [`Send`](http://www.rubydoc.info/gems/contracts/Contracts/Send) – checks that all named methods return a truthy value, e.g. `Send[:valid?]`
+
+* Miscellaneous
+ * [`Exactly`](http://www.rubydoc.info/gems/contracts/Contracts/Exactly) – checks that the argument has the given type, not accepting sub-classes, e.g. `Exactly[Numeric]`.
+ * [`Eq`](http://www.rubydoc.info/gems/contracts/Contracts/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.
+ * [`Func`](http://www.rubydoc.info/gems/contracts/Contracts/Func) – specifies the contract for a proc/lambda e.g. `Contract ArrayOf[Num], Func[Num => Num] => ArrayOf[Num]`. See section "Contracts On Functions".
+
To see all the built-in contracts and their full descriptions, check out the [RDoc](http://rubydoc.info/gems/contracts/Contracts).
## More Examples
### Hello, World
@@ -237,10 +253,63 @@
# fails
give_largest_value("a" => 1, 2 => 2, c: 3)
```
+### Contracts On Keyword Arguments
+
+ruby 2.0+, but can be used for normal hashes too, when keyword arguments are
+not available
+
+Lets say you are writing a simple function and require a bunch of keyword arguments:
+
+```ruby
+def connect(host, port:, user:, password:)
+```
+
+You can of course put `Hash` contract on it:
+
+```ruby
+Contract String, { :port => Num, :user => String, :password => String } => Connection
+def connect(host, port:, user:, password:)
+```
+
+But this will not quite work if you want to have a default values:
+
+```ruby
+Contract String, { :port => Num, :user => String, :password => String } => Connection
+def connect(host, port: 5000, user:, password:)
+ # ...
+end
+
+# No value is passed for port
+connect("example.org", user: "me", password: "none")
+```
+
+Results in:
+
+```
+ContractError: Contract violation for argument 2 of 2:
+ Expected: {:port=>Num, :user=>String, :password=>String},
+ Actual: {:user=>"me", :password=>"none"}
+ Value guarded in: Object::connect
+ With Contract: String, Hash => Connection
+ At: (irb):12
+```
+
+This can be fixed with contract `{ :port => Maybe[Num], ... }`, but that will
+allow `nil` to be passed in, which is not the original intent.
+
+So that is where `KeywordArgs` and `Optional` contracts jump in:
+
+```ruby
+Contract String, KeywordArgs[ :port => Optional[Num], :user => String, :password => String ] => Connection
+def connect(host, port: 5000, user:, password:)
+```
+
+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`.
+
### Contracts On Functions
Lets say you are writing a simple map function:
```ruby
@@ -258,11 +327,11 @@
```ruby
p map([1, 2, 3], lambda { |x| x + 1 }) # works
```
-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 it's argument, and uses that contract on the function that you pass in.
+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.
Here's a `map` function that requires an array of numbers, and a function that takes a number and returns a number:
```ruby
Contract ArrayOf[Num], Func[Num => Num] => ArrayOf[Num]
@@ -282,10 +351,16 @@
```ruby
p map([1, 2, 3], lambda { |x| x + 1 }) # works
p map([1, 2, 3], lambda { |x| "oops" }) # fails, the lambda returns a string.
```
+The above examples showed a method accepting a `Proc` as the last argument, but the same contract works on methods that accept a block:
+
+```ruby
+def map(arr, &block)
+```
+
NOTE: This is not valid:
```ruby
Contract ArrayOf[Num], Func => ArrayOf[Num]
def map(arr, &func)
@@ -446,10 +521,40 @@
:contracts => the contract object
}
If your failure callback returns `false`, the method that the contract is guarding will not be called (the default behaviour).
+## Providing your own custom validators
+
+This can be done with `Contract.override_validator`:
+
+```ruby
+# Make contracts accept all RSpec doubles
+Contract.override_validator(:class) do |contract|
+ lambda do |arg|
+ arg.is_a?(RSpec::Mocks::Double) ||
+ arg.is_a?(contract)
+ end
+end
+```
+
+The block you provide should always return lambda accepting one argument - validated argument. Block itself accepts contract as an argument.
+
+Possible validator overrides:
+
+- `override_validator(MyCustomContract)` - allows to add some special behaviour for custom contracts,
+- `override_validator(Proc)` - e.g. `lambda { true }`,
+- `override_validator(Array)` - e.g. `[Num, String]`,
+- `override_validator(Hash)` - e.g. `{ :a => Num, :b => String }`,
+- `override_validator(Contracts::Args)` - e.g. `Args[Num]`,
+- `override_validator(Contracts::Func)` - e.g. `Func[Num => Num]`,
+- `override_validator(:valid)` - allows to override how contracts that respond to `:valid?` are handled,
+- `override_validator(:class)` - allows to override how class/module contract constants are handled,
+- `override_validator(:default)` - otherwise, raw value contracts.
+
+Default validators can be found here: [lib/contracts/validators.rb](https://github.com/egonSchiele/contracts.ruby/blob/master/lib/contracts/validators.rb).
+
## Disabling contracts
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.
## Method overloading
@@ -508,16 +613,15 @@
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.
## Contracts in modules
-To use contracts on module you need to include both `Contracts` and `Contracts::Modules` into it:
+Usage is the same as contracts in classes:
```ruby
module M
include Contracts
- include Contracts::Modules
Contract String => String
def self.parse
# do some hard parsing
end
@@ -559,9 +663,20 @@
Value guarded in: MyBirthday::silly_next_day!
At: main.rb:9
```
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.
+
+## Using contracts within your own code
+
+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:
+
+```ruby
+data = parse(user_input)
+unless Contract.valid?(data, HashOf[String,Nat])
+ raise UserInputError.new(user_input)
+end
+```
## Auto-generate documentation using contracts
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!