Module: Handshake::ClauseMethods

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

Overview

A collection of methods for defining constraints on method arguments. Use them inline with method signatures:

contract any?(String, nil) => all?(Fixnum, nonzero?)

Constant Summary collapse

ANYTHING =
Clause.new("anything") { true }

Instance Method Summary collapse

Instance Method Details

#all?(*clauses) ⇒ Boolean Also known as: and?

Passes only if all of the subclauses pass on the argument.

contract all?(Integer, nonzero?)

Returns:

  • (Boolean)


69
70
71
# File 'lib/handshake/clause_methods.rb', line 69

def all?(*clauses)
  clause("all of #{clauses.inspect}") { |o| clauses.all? {|c| c === o} }
end

#any?(*clauses) ⇒ Boolean Also known as: or?

Passes if any of the subclauses pass on the argument.

contract any?(String, Symbol) => anything

Returns:

  • (Boolean)


62
63
64
# File 'lib/handshake/clause_methods.rb', line 62

def any?(*clauses)
  clause("any of #{clauses.inspect}") { |o| clauses.any? {|c| c === o} }
end

#anythingObject

Always passes.



44
# File 'lib/handshake/clause_methods.rb', line 44

def anything; ANYTHING; end

#Block(contract_hash) ⇒ Object

Block signature definition. Returns a ProcContract with the given attributes



46
47
48
49
50
# File 'lib/handshake/block_contract.rb', line 46

def Block(contract_hash)
  pc = Handshake::ProcContract.new
  pc.signature = contract_hash
  pc
end

#boolean?Boolean

Passes if argument is true or false.

contract self => boolean?
def ==(other)
  ...
end

Returns:

  • (Boolean)


56
57
58
# File 'lib/handshake/clause_methods.rb', line 56

def boolean?
  any?(TrueClass, FalseClass)
end

#clause(mesg = nil, &block) ⇒ Object

Passes if the given block returns true when passed the argument.



34
35
36
# File 'lib/handshake/clause_methods.rb', line 34

def clause(mesg=nil, &block) # :yields: argument
  Clause.new(mesg, &block)
end

#hash_contract(hash) ⇒ Object

Passes if:

  • argument is a hash, and

  • argument contains only the keys explicitly specified in the given hash, and

  • every value contract in the given hash passes every applicable value in the argument hash

E.g. hash_contract(:foo => String, :bar => Integer)

:foo => "foo"               # passes
:bar => 3                   # passes
:foo => "bar", :bar => 42   # passes
:foo => 88, :bar => "none"  # fails


140
141
142
143
144
145
146
147
# File 'lib/handshake/clause_methods.rb', line 140

def hash_contract(hash)
  value_assertions = hash.keys.map do |k|
    clause("key #{k} requires #{hash[k].inspect}") do |o|
      o.has_key?(k) ? hash[k] === o[k] : true
    end
  end
  all? hash_with_keys(*hash.keys), *value_assertions
end

#hash_of?(key_clause, value_clause) ⇒ Boolean

Passes if argument is a Hash and if the key and value clauses pass all of its keys and values, respectively. E.g. hash_of?(Symbol, String):

:foo => "bar", :baz => "qux" # passes
:foo => "bar", "baz" => 3    # fails

Returns:

  • (Boolean)


107
108
109
110
111
# File 'lib/handshake/clause_methods.rb', line 107

def hash_of?(key_clause, value_clause)
  all_keys   = many_with_map?(key_clause, "all keys")     { |kv| kv[0] }
  all_values = many_with_map?(value_clause, "all values") { |kv| kv[1] }
  all? Hash, all_keys, all_values
end

#hash_with_keys(*keys) ⇒ Object Also known as: hash_with_options

Passes only if argument is a hash and does not contain any keys except those given. E.g. hash_with_keys(:foo, :bar, :baz):

:foo => 3                                     # passes
:foo => 10, :bar => "foo"                     # passes
:foo => "eight", :chunky_bacon => "delicious" # fails


120
121
122
123
124
125
# File 'lib/handshake/clause_methods.rb', line 120

def hash_with_keys(*keys)
  key_assertion = clause("contains keys #{keys.inspect}") do |o|
    ( o.keys - keys ).empty?
  end
  all? Hash, key_assertion
end

#is?(class_symbol) ⇒ Boolean

Allows you to check whether the argument is_a? of the given symbol. For example, is?(:String). Useful for situations where you want to check for a class type that hasn’t been defined yet when Ruby evaluates the contract but will have been by the time the code runs. Note that String => anything is equivalent to is?(:String) => anything.

Returns:

  • (Boolean)


163
164
165
166
167
# File 'lib/handshake/clause_methods.rb', line 163

def is?(class_symbol)
  clause(class_symbol.to_s) { |o|
    Object.const_defined?(class_symbol) && o.is_a?(Object.const_get(class_symbol))
  }
end

#many?(clause) ⇒ Boolean

Passes if argument is Enumerable and the subclause passes on all of its objects.

class StringArray < Array
  include Handshake
  contract :+, many?(String) => self
end

Returns:

  • (Boolean)


86
87
88
# File 'lib/handshake/clause_methods.rb', line 86

def many?(clause)
  many_with_map?(clause) { |o| o }
end

#many_with_map?(clause, mesg = nil, &block) ⇒ Boolean

Passes if argument is Enumerable and the subclause passes on all of its objects, mapped over the given block.

contract many_with_map?(nonzero?, "person age") { |person| person.age } => anything

Returns:

  • (Boolean)


93
94
95
96
97
98
99
# File 'lib/handshake/clause_methods.rb', line 93

def many_with_map?(clause, mesg=nil, &block) # :yields: argument
  map_mesg = ( mesg.nil? ? "" : " after map #{mesg}" )
  many_with_map = clause("many of #{clause.inspect}#{map_mesg}") do |o|
    o.map(&block).all? { |p| clause === p }
  end
  all? Enumerable, many_with_map
end

#nonzero?Boolean

Passes if argument is numeric and nonzero.

Returns:

  • (Boolean)


75
76
77
# File 'lib/handshake/clause_methods.rb', line 75

def nonzero?
  all? Numeric, clause("nonzero") {|o| o != 0}
end

#not?(clause) ⇒ Boolean

Passes if the subclause does not pass on the argument.

Returns:

  • (Boolean)


39
40
41
# File 'lib/handshake/clause_methods.rb', line 39

def not?(clause)
  clause("not #{clause.inspect}") { |o| not ( clause === o ) }
end

#nothingObject

Distinct from nil, and only for accepts: passes if zero arguments. Since Ruby will throw an arity exception anyway, this is essentially aesthetics.



49
# File 'lib/handshake/clause_methods.rb', line 49

def nothing; []; end

#responds_to?(*methods) ⇒ Boolean

Passes if argument responds to all of the given methods.

Returns:

  • (Boolean)


150
151
152
153
154
155
# File 'lib/handshake/clause_methods.rb', line 150

def responds_to?(*methods)
  respond_assertions = methods.map do |m| 
    clause("responds to #{m}") { |o| o.respond_to? m }
  end
  all? *respond_assertions
end