## With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

No credit card required

# 10.16. Enforcing Software Contracts

Credit: Maurice Codik

## Problem

You want your methods to to validate their arguments, using techniques like duck typing and range validation, without filling your code with tons of conditions to test arguments.

## Solution

Here's a ` Contracts` module that you can mix in to your classes. Your methods can then define and enforce contracts.

` module Contracts def valid_contract(input) if @user_defined and @user_defined[input] @user_defined[input] else case input when :number lambda { |x| x.is_a? Numeric } when :string lambda { |x| x.respond_to? :to_str } when :anything lambda { |x| true } else lambda { |x| false } end end end class ContractViolation < StandardError end def define_data(inputs={}.freeze) @user_defined ||= {} inputs.each do |name, contract| @user_defined[name] = contract if contract.respond_to? :call end end def contract(method, *inputs) @contracts ||= {} @contracts[method] = inputs method_added(method) end def setup_contract(method, inputs) @contracts[method] = nil method_renamed = "__#{method}".intern conditions = "" inputs.flatten.each_with_index do |input, i| conditions << %{ if not self.class.valid_contract(#{input.inspect}).call(args[#{i}]) raise ContractViolation, "argument #{i+1} of method '#{method}' must" + "satisfy the '#{input}' contract", caller end } end class_eval %{ alias_method #{method_renamed.inspect}, #{method.inspect} def #{method}(*args) #{conditions} return #{method_renamed}(*args) end } end def method_added(method) inputs ...`

## With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

No credit card required