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