class Contracts::MethodHandler

Handles class and instance methods addition Represents single such method

Constants

METHOD_REFERENCE_FACTORY
RAW_METHOD_STRATEGY

Attributes

is_class_method[R]
method_name[R]
target[R]

Public Class Methods

new(method_name, is_class_method, target) click to toggle source

Creates new instance of MethodHandler

@param [Symbol] method_name @param [Bool] is_class_method @param [Class] target - class that method got added to

# File lib/contracts/method_handler.rb, line 22
def initialize(method_name, is_class_method, target)
  @method_name = method_name
  @is_class_method = is_class_method
  @target = target
end

Public Instance Methods

handle() click to toggle source

Handles method addition

# File lib/contracts/method_handler.rb, line 29
def handle
  return unless engine?
  return if decorators.empty?

  validate_decorators!
  validate_pattern_matching!

  engine.add_method_decorator(method_type, method_name, decorator)
  mark_pattern_matching_decorators
  method_reference.make_alias(target)
  redefine_method
end

Private Instance Methods

_method_type()

_method_type is required for assigning it to local variable with the same name. See: redefine_method

Alias for: method_type
decorated_methods() click to toggle source
# File lib/contracts/method_handler.rb, line 77
def decorated_methods
  @_decorated_methods ||= engine.decorated_methods_for(method_type, method_name)
end
decorator() click to toggle source
# File lib/contracts/method_handler.rb, line 93
def decorator
  @_decorator ||= decorator_class.new(target, method_reference, *decorator_args)
end
decorator_args() click to toggle source
# File lib/contracts/method_handler.rb, line 101
def decorator_args
  decorators.first[1]
end
decorator_class() click to toggle source
# File lib/contracts/method_handler.rb, line 97
def decorator_class
  decorators.first[0]
end
decorators() click to toggle source
# File lib/contracts/method_handler.rb, line 54
def decorators
  @_decorators ||= engine.all_decorators
end
engine() click to toggle source
# File lib/contracts/method_handler.rb, line 50
def engine
  Engine.fetch_from(target)
end
engine?() click to toggle source
# File lib/contracts/method_handler.rb, line 46
def engine?
  Engine.applied?(target)
end
ignore_decorators?() click to toggle source
# File lib/contracts/method_handler.rb, line 73
def ignore_decorators?
  ENV["NO_CONTRACTS"] && !pattern_matching?
end
mark_pattern_matching_decorators() click to toggle source
# File lib/contracts/method_handler.rb, line 87
def mark_pattern_matching_decorators
  return unless pattern_matching?

  decorated_methods.each(&:pattern_match!)
end
method_reference() click to toggle source
# File lib/contracts/method_handler.rb, line 65
def method_reference
  @_method_reference ||= METHOD_REFERENCE_FACTORY[method_type].new(method_name, raw_method)
end
method_type() click to toggle source
# File lib/contracts/method_handler.rb, line 58
def method_type
  @_method_type ||= is_class_method ? :class_methods : :instance_methods
end
Also aliased as: _method_type
pattern_matching?() click to toggle source
# File lib/contracts/method_handler.rb, line 81
def pattern_matching?
  return @_pattern_matching if defined?(@_pattern_matching)

  @_pattern_matching = decorated_methods.any? { |x| x.method != method_reference }
end
raw_method() click to toggle source
# File lib/contracts/method_handler.rb, line 69
def raw_method
  RAW_METHOD_STRATEGY[method_type].call(target, method_name)
end
redefine_method() click to toggle source
# File lib/contracts/method_handler.rb, line 105
def redefine_method
  return if ignore_decorators?

  # Those are required for instance_eval to be able to refer them
  name = method_name
  method_type = _method_type
  current_engine = engine

  # We are gonna redefine original method here
  method_reference.make_definition(target) do |*args, **kargs, &blk|
    engine = current_engine.nearest_decorated_ancestor

    # If we weren't able to find any ancestor that has decorated methods
    # FIXME : this looks like untested code (commenting it out doesn't make specs red)
    unless engine
      fail "Couldn't find decorator for method #{self.class.name}:#{name}.\nDoes this method look correct to you? If you are using contracts from rspec, rspec wraps classes in it's own class.\nLook at the specs for contracts.ruby as an example of how to write contracts in this case."
    end

    # Fetch decorated methods out of the contracts engine
    decorated_methods = engine.decorated_methods_for(method_type, name)

    # This adds support for overloading methods. Here we go
    # through each method and call it with the arguments.
    # If we get a failure_exception, we move to the next
    # function. Otherwise we return the result.
    # If we run out of functions, we raise the last error, but
    # convert it to_contract_error.

    expected_error = decorated_methods[0].failure_exception
    last_error = nil

    decorated_methods.each do |decorated_method|
      result = decorated_method.call_with_inner(true, self, *args, **kargs, &blk)
      return result unless result.is_a?(ParamContractError)

      last_error = result
    end

    begin
      if ::Contract.failure_callback(last_error&.data, use_pattern_matching: false)
        decorated_methods.last.call_with_inner(false, self, *args, **kargs, &blk)
      end
      # rubocop:disable Naming/RescuedExceptionsVariableName
    rescue expected_error => final_error
      raise final_error.to_contract_error
      # rubocop:enable Naming/RescuedExceptionsVariableName
    end
  end
end
validate_decorators!() click to toggle source
# File lib/contracts/method_handler.rb, line 155
    def validate_decorators!
      return if decorators.size == 1

      fail %{
Oops, it looks like method '#{method_name}' has multiple contracts:
#{decorators.map { |x| x[1][0].inspect }.join("\n")}

Did you accidentally put more than one contract on a single function, like so?

Contract String => String
Contract Num => String
def foo x
end

If you did NOT, then you have probably discovered a bug in this library.
Please file it along with the relevant code at:
https://github.com/egonSchiele/contracts.ruby/issues
      }
    end
validate_pattern_matching!() click to toggle source
# File lib/contracts/method_handler.rb, line 175
    def validate_pattern_matching!
      new_args_contract = decorator.args_contracts
      matched = decorated_methods.select do |contract|
        contract.args_contracts == new_args_contract
      end

      return if matched.empty?

      fail ContractError.new(
        %{
It looks like you are trying to use pattern-matching, but
multiple definitions for function '#{method_name}' have the same
contract for input parameters:

#{(matched + [decorator]).map(&:to_s).join("\n")}

Each definition needs to have a different contract for the parameters.
        },
        {},
      )
    end