Module: Handshake

Defined in:
lib/handshake.rb,
lib/handshake/version.rb,
lib/handshake/block_contract.rb,
lib/handshake/clause_methods.rb

Overview

:nodoc:

Defined Under Namespace

Modules: ClassMethods, ClauseMethods, InstanceMethods, VERSION Classes: AssertionFailed, Block, Clause, ContractError, ContractViolation, Invariant, MethodCondition, MethodContract, ProcContract, Proxy

Class Method Summary collapse

Class Method Details

.catch_contract(mesg = nil, &block) ⇒ Object

Catches any thrown :contract exception raised within the given block, appends the given message to the violation message, and re-raises the exception.



16
17
18
19
20
21
22
23
24
25
# File 'lib/handshake.rb', line 16

def Handshake.catch_contract(mesg=nil, &block) # :nodoc:
  violation = catch(:contract, &block)
  if violation.is_a?(Exception)
    # Re-raise the violation with the given message, ensuring that the
    # callback stack begins with the caller of this method rather than
    # this method.
    message = ( mesg.nil? ? "" : ( mesg + ": " ) ) + violation.message
    raise violation.class, message, caller
  end
end

.included(base) ⇒ Object

When Handshake is included in a class, that class’s new method is overridden to provide custom functionality. A proxy object, returned in place of the real object, filters all external method calls through any contracts that have been defined. <b>N.B.:<b> Handshake is designed to act as a barrier between an object and its callers. However, anything that takes place within that barrier is not checked. This means that Handshake is, at the moment, unable to enforce contracts on methods called only internally, notably private methods.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/handshake.rb', line 36

def Handshake.included(base)
  base.extend(ClassMethods)
  base.extend(ClauseMethods)

  base.send(:include, Test::Unit::Assertions)
  base.send(:include, Handshake::InstanceMethods)

  base.class_inheritable_array :invariants
  base.write_inheritable_array :invariants, []

  base.class_inheritable_hash :method_contracts
  base.write_inheritable_hash :method_contracts, {}

  class << base
    alias :instantiate :new
    # Override the class-level new method of every class that includes
    # Contract and cause it to return a proxy object for the original.
    def new(*args, &block)
      if @non_instantiable
        raise ContractViolation, "This class has been marked as abstract and cannot be instantiated."
      end
      o = nil

      # Special case:  at this stage it's only possible to check arguments
      # (before) and invariants (after).  Maybe postconditions?
      Handshake.catch_contract("Contract violated in call to constructor of class #{self}") do
        if contract_defined? :initialize
          method_contracts[:initialize].check_accepts!(*args, &block)
        end
      end

      ### Instantiate the object itself. 
      o = self.instantiate(*args, &block)

      Handshake.catch_contract("Invariant violated by constructor of class #{self}") do
        o.check_invariants!
      end

      raise ContractError, "Could not instantiate object" if o.nil?

      ### Wrap the object in a proxy.
      p = Proxy.new( o )
      # Make sure that the object has a reference back to the proxy.
      o.instance_variable_set("@checked_self", p)
      p
    end
  end
end