Class: Contract

Inherits:
Object
  • Object
show all
Defined in:
lib/obvious/contract.rb

Constant Summary collapse

@@disable_override =
false

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.contract_for(method, contract) ⇒ Object

Public: Defines a contract for a method

method - a symbol representing the method name contract - a hash with the keys :input and :output holding the respective shapes.

Examples

class FooJackContract < Contract

  contract_for :save, {
    :input  => Foo.shape,
    :output => Foo.shape,
  }

end


44
45
46
47
48
49
50
51
52
53
54
# File 'lib/obvious/contract.rb', line 44

def self.contract_for method, contract
  method_alias    = "#{method}_alias".to_sym
  method_contract = "#{method}_contract".to_sym

  define_method method_contract do |*args|
    input = args[0]
    call_method method_alias, input, contract[:input], contract[:output]
  end

  contracts( *contract_list, method )
end

.contract_listObject

Provides a default empty array for method_added Overriden by self.contracts



6
7
8
# File 'lib/obvious/contract.rb', line 6

def self.contract_list
  []
end

.contracts(*contracts) ⇒ Object

Public: Sets the contracts

contracts - a list of contracts, as String or as Symbols.

Examples

class FooJackContract < Contract
  contracts :save, :get, :list

end

Returns Nothing.



22
23
24
25
26
# File 'lib/obvious/contract.rb', line 22

def self.contracts *contracts
  singleton_class.send :define_method, :contract_list do
    contracts
  end
end

.method_added(name) ⇒ Object

This method will move methods defined in @contracts into new methods. Each entry in @contracts will cause the method with the same name to become method_name_alias and for the original method to point to method_name_contract.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/obvious/contract.rb', line 60

def self.method_added name
  unless @@disable_override
    self.contract_list.each do |method|
      if name == method.to_sym
        method_alias = "#{method}_alias".to_sym
        method_contract = "#{method}_contract".to_sym

        @@disable_override = true # to stop the new build method
        self.send :alias_method, method_alias, name
        self.send :remove_method, name
        self.send :alias_method, name, method_contract

        @@disable_override = false
      else
        # puts self.inspect
        # puts "defining other method #{name}"
      end
    end
  end
end

Instance Method Details

#call_method(method, input, input_shape, output_shape) ⇒ Object

This method is used as a shorthand to mak the contract method calling pattern more DRY It starts by checking if you are sending in input and if so will check the input shape for errors. If no errors are found it calls the method via the passed in symbol(method).

Output checking is more complicated because of the types of output we check for. Nil is never valid output. If we pass in the output shape of true, that means we are looking for result to be the object True. If the output shape is an array, that is actually a shorthand for telling our output check to look at the output as an array and compare it to the shape stored in output_shape. If we pass in the symbol :true_false it means we are looking for the result to be either true or false. The default case will just check if result has the shape of the output_shape.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/obvious/contract.rb', line 92

def call_method method, input, input_shape, output_shape
  if input != nil && input_shape != nil
    has_shape, error_field = input.has_shape? input_shape, true
    unless has_shape 
      raise ContractInputError, "incorrect input data format field #{error_field}"
    end

    result = self.send method, input
  else
    result = self.send method
  end

  # check output
  # output should never be nil
  if result == nil
    raise ContractOutputError, 'incorrect output data format'
  end

  if result === {}
    raise DataNotFoundError, 'data was not found'
  end

  # we are looking for result to be a True object
  if output_shape === true
    if output_shape == result
      return result
    else
      raise ContractOutputError, 'incorrect output data format'
    end
  end

  # we want to check the shape of each item in the result array
  if output_shape.class == Array
    if result.class == Array
      inner_shape = output_shape[0]
      result.each do |item|
        has_shape, error_field = item.has_shape? inner_shape, true
        unless has_shape 
          raise ContractOutputError, "incorrect output data format field #{error_field}"
        end
      end

      return result
    end
    raise ContractOutputError, 'incorrect output data format'
  end

  # we want result to be true or false
  if output_shape == :true_false
    unless result == true || result == false
      raise ContractOutputError, 'incorrect output data format'
    end

    return result
  end

  # we want result to be output_shape's shape
  has_shape, error_field = result.has_shape? output_shape, true
  unless has_shape 
    raise ContractOutputError, "incorrect output data format field #{error_field}"
  end

  result
end