entitlements-app 0.1.8 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/bin/deploy-entitlements +10 -1
  4. data/lib/contracts-ruby2/CHANGELOG.markdown +115 -0
  5. data/lib/contracts-ruby2/Gemfile +17 -0
  6. data/lib/contracts-ruby2/LICENSE +23 -0
  7. data/lib/contracts-ruby2/README.md +108 -0
  8. data/lib/contracts-ruby2/Rakefile +8 -0
  9. data/lib/contracts-ruby2/TODO.markdown +6 -0
  10. data/lib/contracts-ruby2/TUTORIAL.md +773 -0
  11. data/lib/contracts-ruby2/benchmarks/bench.rb +67 -0
  12. data/lib/contracts-ruby2/benchmarks/hash.rb +69 -0
  13. data/lib/contracts-ruby2/benchmarks/invariants.rb +91 -0
  14. data/lib/contracts-ruby2/benchmarks/io.rb +62 -0
  15. data/lib/contracts-ruby2/benchmarks/wrap_test.rb +57 -0
  16. data/lib/contracts-ruby2/contracts.gemspec +17 -0
  17. data/lib/contracts-ruby2/cucumber.yml +1 -0
  18. data/lib/contracts-ruby2/dependabot.yml +20 -0
  19. data/lib/contracts-ruby2/features/README.md +17 -0
  20. data/lib/contracts-ruby2/features/basics/functype.feature +71 -0
  21. data/lib/contracts-ruby2/features/basics/pretty-print.feature +241 -0
  22. data/lib/contracts-ruby2/features/basics/simple_example.feature +210 -0
  23. data/lib/contracts-ruby2/features/builtin_contracts/README.md +22 -0
  24. data/lib/contracts-ruby2/features/builtin_contracts/and.feature +103 -0
  25. data/lib/contracts-ruby2/features/builtin_contracts/any.feature +44 -0
  26. data/lib/contracts-ruby2/features/builtin_contracts/args.feature +80 -0
  27. data/lib/contracts-ruby2/features/builtin_contracts/array_of.feature +1 -0
  28. data/lib/contracts-ruby2/features/builtin_contracts/bool.feature +64 -0
  29. data/lib/contracts-ruby2/features/builtin_contracts/enum.feature +1 -0
  30. data/lib/contracts-ruby2/features/builtin_contracts/eq.feature +1 -0
  31. data/lib/contracts-ruby2/features/builtin_contracts/exactly.feature +1 -0
  32. data/lib/contracts-ruby2/features/builtin_contracts/func.feature +1 -0
  33. data/lib/contracts-ruby2/features/builtin_contracts/hash_of.feature +1 -0
  34. data/lib/contracts-ruby2/features/builtin_contracts/int.feature +93 -0
  35. data/lib/contracts-ruby2/features/builtin_contracts/keyword_args.feature +1 -0
  36. data/lib/contracts-ruby2/features/builtin_contracts/maybe.feature +1 -0
  37. data/lib/contracts-ruby2/features/builtin_contracts/nat.feature +115 -0
  38. data/lib/contracts-ruby2/features/builtin_contracts/nat_pos.feature +119 -0
  39. data/lib/contracts-ruby2/features/builtin_contracts/neg.feature +115 -0
  40. data/lib/contracts-ruby2/features/builtin_contracts/none.feature +145 -0
  41. data/lib/contracts-ruby2/features/builtin_contracts/not.feature +1 -0
  42. data/lib/contracts-ruby2/features/builtin_contracts/num.feature +64 -0
  43. data/lib/contracts-ruby2/features/builtin_contracts/or.feature +83 -0
  44. data/lib/contracts-ruby2/features/builtin_contracts/pos.feature +116 -0
  45. data/lib/contracts-ruby2/features/builtin_contracts/range_of.feature +1 -0
  46. data/lib/contracts-ruby2/features/builtin_contracts/respond_to.feature +78 -0
  47. data/lib/contracts-ruby2/features/builtin_contracts/send.feature +147 -0
  48. data/lib/contracts-ruby2/features/builtin_contracts/set_of.feature +1 -0
  49. data/lib/contracts-ruby2/features/builtin_contracts/xor.feature +99 -0
  50. data/lib/contracts-ruby2/features/support/env.rb +6 -0
  51. data/lib/contracts-ruby2/lib/contracts/attrs.rb +24 -0
  52. data/lib/contracts-ruby2/lib/contracts/builtin_contracts.rb +542 -0
  53. data/lib/contracts-ruby2/lib/contracts/call_with.rb +108 -0
  54. data/lib/contracts-ruby2/lib/contracts/core.rb +52 -0
  55. data/lib/contracts-ruby2/lib/contracts/decorators.rb +47 -0
  56. data/lib/contracts-ruby2/lib/contracts/engine/base.rb +136 -0
  57. data/lib/contracts-ruby2/lib/contracts/engine/eigenclass.rb +50 -0
  58. data/lib/contracts-ruby2/lib/contracts/engine/target.rb +70 -0
  59. data/lib/contracts-ruby2/lib/contracts/engine.rb +26 -0
  60. data/lib/contracts-ruby2/lib/contracts/errors.rb +71 -0
  61. data/lib/contracts-ruby2/lib/contracts/formatters.rb +136 -0
  62. data/lib/contracts-ruby2/lib/contracts/invariants.rb +68 -0
  63. data/lib/contracts-ruby2/lib/contracts/method_handler.rb +187 -0
  64. data/lib/contracts-ruby2/lib/contracts/method_reference.rb +100 -0
  65. data/lib/contracts-ruby2/lib/contracts/support.rb +61 -0
  66. data/lib/contracts-ruby2/lib/contracts/validators.rb +139 -0
  67. data/lib/contracts-ruby2/lib/contracts/version.rb +3 -0
  68. data/lib/contracts-ruby2/lib/contracts.rb +281 -0
  69. data/lib/contracts-ruby2/script/docs-release +3 -0
  70. data/lib/contracts-ruby2/script/docs-staging +3 -0
  71. data/lib/contracts-ruby2/script/rubocop.rb +5 -0
  72. data/lib/contracts-ruby2/spec/attrs_spec.rb +119 -0
  73. data/lib/contracts-ruby2/spec/builtin_contracts_spec.rb +461 -0
  74. data/lib/contracts-ruby2/spec/contracts_spec.rb +770 -0
  75. data/lib/contracts-ruby2/spec/fixtures/fixtures.rb +730 -0
  76. data/lib/contracts-ruby2/spec/invariants_spec.rb +17 -0
  77. data/lib/contracts-ruby2/spec/methods_spec.rb +54 -0
  78. data/lib/contracts-ruby2/spec/module_spec.rb +18 -0
  79. data/lib/contracts-ruby2/spec/override_validators_spec.rb +162 -0
  80. data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
  81. data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
  82. data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
  83. data/lib/contracts-ruby2/spec/spec_helper.rb +102 -0
  84. data/lib/contracts-ruby2/spec/support.rb +10 -0
  85. data/lib/contracts-ruby2/spec/support_spec.rb +21 -0
  86. data/lib/contracts-ruby2/spec/validators_spec.rb +47 -0
  87. data/lib/contracts-ruby3/CHANGELOG.markdown +117 -0
  88. data/lib/contracts-ruby3/Gemfile +21 -0
  89. data/lib/contracts-ruby3/LICENSE +23 -0
  90. data/lib/contracts-ruby3/README.md +114 -0
  91. data/lib/contracts-ruby3/Rakefile +10 -0
  92. data/lib/contracts-ruby3/TODO.markdown +6 -0
  93. data/lib/contracts-ruby3/TUTORIAL.md +773 -0
  94. data/lib/contracts-ruby3/benchmarks/bench.rb +67 -0
  95. data/lib/contracts-ruby3/benchmarks/hash.rb +69 -0
  96. data/lib/contracts-ruby3/benchmarks/invariants.rb +91 -0
  97. data/lib/contracts-ruby3/benchmarks/io.rb +62 -0
  98. data/lib/contracts-ruby3/benchmarks/wrap_test.rb +57 -0
  99. data/lib/contracts-ruby3/contracts.gemspec +20 -0
  100. data/lib/contracts-ruby3/cucumber.yml +1 -0
  101. data/lib/contracts-ruby3/dependabot.yml +20 -0
  102. data/lib/contracts-ruby3/features/README.md +17 -0
  103. data/lib/contracts-ruby3/features/basics/functype.feature +71 -0
  104. data/lib/contracts-ruby3/features/basics/pretty-print.feature +241 -0
  105. data/lib/contracts-ruby3/features/basics/simple_example.feature +210 -0
  106. data/lib/contracts-ruby3/features/builtin_contracts/README.md +22 -0
  107. data/lib/contracts-ruby3/features/builtin_contracts/and.feature +103 -0
  108. data/lib/contracts-ruby3/features/builtin_contracts/any.feature +44 -0
  109. data/lib/contracts-ruby3/features/builtin_contracts/args.feature +80 -0
  110. data/lib/contracts-ruby3/features/builtin_contracts/array_of.feature +1 -0
  111. data/lib/contracts-ruby3/features/builtin_contracts/bool.feature +64 -0
  112. data/lib/contracts-ruby3/features/builtin_contracts/enum.feature +1 -0
  113. data/lib/contracts-ruby3/features/builtin_contracts/eq.feature +1 -0
  114. data/lib/contracts-ruby3/features/builtin_contracts/exactly.feature +1 -0
  115. data/lib/contracts-ruby3/features/builtin_contracts/func.feature +1 -0
  116. data/lib/contracts-ruby3/features/builtin_contracts/hash_of.feature +1 -0
  117. data/lib/contracts-ruby3/features/builtin_contracts/int.feature +93 -0
  118. data/lib/contracts-ruby3/features/builtin_contracts/keyword_args.feature +1 -0
  119. data/lib/contracts-ruby3/features/builtin_contracts/maybe.feature +1 -0
  120. data/lib/contracts-ruby3/features/builtin_contracts/nat.feature +115 -0
  121. data/lib/contracts-ruby3/features/builtin_contracts/nat_pos.feature +119 -0
  122. data/lib/contracts-ruby3/features/builtin_contracts/neg.feature +115 -0
  123. data/lib/contracts-ruby3/features/builtin_contracts/none.feature +145 -0
  124. data/lib/contracts-ruby3/features/builtin_contracts/not.feature +1 -0
  125. data/lib/contracts-ruby3/features/builtin_contracts/num.feature +64 -0
  126. data/lib/contracts-ruby3/features/builtin_contracts/or.feature +83 -0
  127. data/lib/contracts-ruby3/features/builtin_contracts/pos.feature +116 -0
  128. data/lib/contracts-ruby3/features/builtin_contracts/range_of.feature +1 -0
  129. data/lib/contracts-ruby3/features/builtin_contracts/respond_to.feature +78 -0
  130. data/lib/contracts-ruby3/features/builtin_contracts/send.feature +147 -0
  131. data/lib/contracts-ruby3/features/builtin_contracts/set_of.feature +1 -0
  132. data/lib/contracts-ruby3/features/builtin_contracts/xor.feature +99 -0
  133. data/lib/contracts-ruby3/features/support/env.rb +8 -0
  134. data/lib/contracts-ruby3/lib/contracts/attrs.rb +26 -0
  135. data/lib/contracts-ruby3/lib/contracts/builtin_contracts.rb +575 -0
  136. data/lib/contracts-ruby3/lib/contracts/call_with.rb +119 -0
  137. data/lib/contracts-ruby3/lib/contracts/core.rb +54 -0
  138. data/lib/contracts-ruby3/lib/contracts/decorators.rb +50 -0
  139. data/lib/contracts-ruby3/lib/contracts/engine/base.rb +137 -0
  140. data/lib/contracts-ruby3/lib/contracts/engine/eigenclass.rb +51 -0
  141. data/lib/contracts-ruby3/lib/contracts/engine/target.rb +72 -0
  142. data/lib/contracts-ruby3/lib/contracts/engine.rb +28 -0
  143. data/lib/contracts-ruby3/lib/contracts/errors.rb +74 -0
  144. data/lib/contracts-ruby3/lib/contracts/formatters.rb +140 -0
  145. data/lib/contracts-ruby3/lib/contracts/invariants.rb +72 -0
  146. data/lib/contracts-ruby3/lib/contracts/method_handler.rb +197 -0
  147. data/lib/contracts-ruby3/lib/contracts/method_reference.rb +102 -0
  148. data/lib/contracts-ruby3/lib/contracts/support.rb +63 -0
  149. data/lib/contracts-ruby3/lib/contracts/validators.rb +143 -0
  150. data/lib/contracts-ruby3/lib/contracts/version.rb +5 -0
  151. data/lib/contracts-ruby3/lib/contracts.rb +290 -0
  152. data/lib/contracts-ruby3/script/docs-release +3 -0
  153. data/lib/contracts-ruby3/script/docs-staging +3 -0
  154. data/lib/contracts-ruby3/script/rubocop.rb +5 -0
  155. data/lib/contracts-ruby3/spec/attrs_spec.rb +119 -0
  156. data/lib/contracts-ruby3/spec/builtin_contracts_spec.rb +457 -0
  157. data/lib/contracts-ruby3/spec/contracts_spec.rb +773 -0
  158. data/lib/contracts-ruby3/spec/fixtures/fixtures.rb +725 -0
  159. data/lib/contracts-ruby3/spec/invariants_spec.rb +17 -0
  160. data/lib/contracts-ruby3/spec/methods_spec.rb +54 -0
  161. data/lib/contracts-ruby3/spec/module_spec.rb +18 -0
  162. data/lib/contracts-ruby3/spec/override_validators_spec.rb +162 -0
  163. data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
  164. data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
  165. data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
  166. data/lib/contracts-ruby3/spec/spec_helper.rb +102 -0
  167. data/lib/contracts-ruby3/spec/support.rb +10 -0
  168. data/lib/contracts-ruby3/spec/support_spec.rb +21 -0
  169. data/lib/contracts-ruby3/spec/validators_spec.rb +47 -0
  170. data/lib/entitlements/data/groups/calculated/yaml.rb +7 -1
  171. data/lib/entitlements/data/people/yaml.rb +9 -1
  172. data/lib/entitlements/extras/ldap_group/rules/ldap_group.rb +5 -1
  173. data/lib/entitlements/extras/orgchart/person_methods.rb +7 -1
  174. data/lib/entitlements.rb +13 -2
  175. data/lib/ruby_version_check.rb +17 -0
  176. 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!