Module: Handshake::ClassMethods
- Defined in:
- lib/handshake/handshake.rb
Overview
This module contains methods that are mixed into any class that includes Handshake. They allow you to define constraints on that class and its methods. Subclasses will inherit the contracts and invariants of its superclass, but Handshake contracts currently can’t be mixed-in via a module.
This module defines three kinds of contracts: class invariants, method signature constraints, and more general method pre- and post-conditions. Invariants accept a block which should return a boolean. Pre- and post- conditions expect you to use assertions (all of Test::Unit’s standard assertions are available) and will pass unless an assertion fails. Method signature contracts map inputs clauses to output clauses. A “clause” is defined as any object that implements the === method.
All method contracts are defined on the method defined immediately after their declaration unless a method name is specified. For example,
contract :foo, String => Integer
is equivalent to
contract String => Integer
def foo ...
in the sense that the contract will be applied to all calls to the “foo” method either way.
Method signature contracts
contract String => Integer
contract [ String, 1..5, [ Integer ], Block ] => [ String, String ]
contract clause("must equal 'foo'") { |o| o == "foo" } => anything
A method signature contract is defined as a mapping from valid inputs to to valid outputs. A clause here is any object which implements the ===
method. Classes, ranges, regexes, and other useful objects are thus all valid values for a method signature contract.
Multiple arguments are specified as an array. To specify that a method accepts varargs, define a nested array as the last or second-to-last item in the array. To specify that a method accepts a block, place the Block constant as the last item in the array. Expect this to change in the future to allow for block contracts.
New clauses may be created easily with the Handshake::ClauseMethods#clause method. Handshake::ClauseMethods also provides a number of useful contract combinators for specifying rich input and output contracts.
Contract-checked accessors
contract_reader :foo => String, :bar => Integer
contract_writer ...
contract_accessor ...
Defines contract-checked accessors. Method names and clauses are specified in a hash. Hash values are any valid clause.
Invariants
invariant() { returns true }
Aliased as always
. Has access to instance variables and methods of object but calls to same are unchecked.
Pre/post-conditions
before(optional_message) { |arg1, ...| assert condition }
after(optional_message) { |arg1, ..., returned| assert condition }
around(optional_message) { |arg1, ...| assert condition }
Check a set of conditions, using assertions, before and after method invocation. before
and after
are aliased as requires
and ensures
respectively. around
currently throws a block argument warning; this should be fixed soon. Same scope rules as invariants, so you can check instance variables and local methods. All Test::Unit::Assertions are available for use, but any such AssertionFailed errors encountered are re-raised by Handshake as Handshake::AssertionFailed errors to avoid confusion with test case execution.
Abstract class decorator
class SuperDuperContract
include Handshake; abstract!
...
end
To define a class as non-instantiable and have Handshake raise a ContractViolation if a caller attempts to do so, call abstract!
at the top of the class definition. This attribute is not inherited by subclasses, but is useful if you would like to define a pure-contract superclass that isn’t intended to be instantiated directly.
Instance Method Summary collapse
-
#===(other) ⇒ Object
In order for contract clauses to work in conjunction with Handshake proxy objects, the === method must be redefined in terms of is_a?.
-
#abstract! ⇒ Object
Define this class as non-instantiable.
-
#after(meth_or_mesg = nil, mesg = nil, &block) ⇒ Object
(also: #ensures)
Specify a postcondition.
-
#around(meth_or_mesg = nil, mesg = nil, &block) ⇒ Object
Specify a bothcondition.
-
#before(meth_or_mesg = nil, mesg = nil, &block) ⇒ Object
(also: #requires)
Specify a precondition.
-
#contract(meth_or_hash, contract_hash = nil) ⇒ Object
Specify an argument contract, with argument clauses on one side of the hash arrow and returned values on the other.
-
#contract_accessor(meth_to_clause) ⇒ Object
Defines contract-checked attribute accessors for the given hash of method name to clause.
-
#contract_defined?(method) ⇒ Boolean
Returns true if a contract is defined for the named method.
-
#contract_for(method) ⇒ Object
Returns the MethodContract for the given method name.
-
#contract_reader(meth_to_clause) ⇒ Object
Defines contract-checked attribute readers with the given hash of method name to clause.
-
#contract_writer(meth_to_clause) ⇒ Object
Defines contract-checked attribute writers with the given hash of method name to clause.
-
#invariant(mesg = nil, &block) ⇒ Object
(also: #always)
Specify an invariant, with a block and an optional error message.
-
#method_added(meth_name) ⇒ Object
Callback from method add event.
Instance Method Details
#===(other) ⇒ Object
In order for contract clauses to work in conjunction with Handshake proxy objects, the === method must be redefined in terms of is_a?.
212 213 214 |
# File 'lib/handshake/handshake.rb', line 212 def ===(other) other.is_a? self end |
#abstract! ⇒ Object
Define this class as non-instantiable. Subclasses do not inherit this attribute.
199 200 201 |
# File 'lib/handshake/handshake.rb', line 199 def abstract! @non_instantiable = true end |
#after(meth_or_mesg = nil, mesg = nil, &block) ⇒ Object Also known as: ensures
Specify a postcondition.
235 236 237 |
# File 'lib/handshake/handshake.rb', line 235 def after(meth_or_mesg=nil, mesg=nil, &block) condition(:after, meth_or_mesg, mesg, &block) end |
#around(meth_or_mesg = nil, mesg = nil, &block) ⇒ Object
Specify a bothcondition.
241 242 243 |
# File 'lib/handshake/handshake.rb', line 241 def around(meth_or_mesg=nil, mesg=nil, &block) condition(:around, meth_or_mesg, mesg, &block) end |
#before(meth_or_mesg = nil, mesg = nil, &block) ⇒ Object Also known as: requires
Specify a precondition.
229 230 231 |
# File 'lib/handshake/handshake.rb', line 229 def before(meth_or_mesg=nil, mesg=nil, &block) condition(:before, meth_or_mesg, mesg, &block) end |
#contract(meth_or_hash, contract_hash = nil) ⇒ Object
Specify an argument contract, with argument clauses on one side of the hash arrow and returned values on the other. Each clause must implement the === method or have been created with the assert method. This method should generally not be called directly.
220 221 222 223 224 225 226 |
# File 'lib/handshake/handshake.rb', line 220 def contract(meth_or_hash, contract_hash=nil) if meth_or_hash.is_a? Hash defer :contract, meth_or_hash else define_contract(meth_or_hash, contract_hash) end end |
#contract_accessor(meth_to_clause) ⇒ Object
Defines contract-checked attribute accessors for the given hash of method name to clause.
282 283 284 285 |
# File 'lib/handshake/handshake.rb', line 282 def contract_accessor(meth_to_clause) contract_reader meth_to_clause contract_writer meth_to_clause end |
#contract_defined?(method) ⇒ Boolean
Returns true if a contract is defined for the named method.
258 259 260 |
# File 'lib/handshake/handshake.rb', line 258 def contract_defined?(method) method_contracts.has_key?(method) end |
#contract_for(method) ⇒ Object
Returns the MethodContract for the given method name. Side effect: creates one if none defined.
247 248 249 250 251 252 253 254 255 |
# File 'lib/handshake/handshake.rb', line 247 def contract_for(method) if contract_defined?(method) method_contracts[method] else contract = MethodContract.new("#{self}##{method}") write_inheritable_hash :method_contracts, { method => contract } contract end end |
#contract_reader(meth_to_clause) ⇒ Object
Defines contract-checked attribute readers with the given hash of method name to clause.
264 265 266 267 268 269 |
# File 'lib/handshake/handshake.rb', line 264 def contract_reader(meth_to_clause) attr_reader *(meth_to_clause.keys) meth_to_clause.each do |meth, cls| contract meth, nil => cls end end |
#contract_writer(meth_to_clause) ⇒ Object
Defines contract-checked attribute writers with the given hash of method name to clause.
273 274 275 276 277 278 |
# File 'lib/handshake/handshake.rb', line 273 def contract_writer(meth_to_clause) attr_writer *(meth_to_clause.keys) meth_to_clause.each do |meth, cls| contract "#{meth}=".to_sym, cls => anything end end |
#invariant(mesg = nil, &block) ⇒ Object Also known as: always
Specify an invariant, with a block and an optional error message.
204 205 206 207 |
# File 'lib/handshake/handshake.rb', line 204 def invariant(mesg=nil, &block) # :yields: write_inheritable_array(:invariants, [ Invariant.new(mesg, &block) ] ) nil end |
#method_added(meth_name) ⇒ Object
Callback from method add event. If a previous method contract declaration was deferred, complete it now with the name of the newly- added method.
290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/handshake/handshake.rb', line 290 def method_added(meth_name) @deferred ||= {} unless @deferred.empty? @deferred.each do |k, v| case k when :before, :after, :around define_condition meth_name, k, v when :contract define_contract meth_name, v end end @deferred.clear end end |