Top Level Namespace

Defined Under Namespace

Modules: ClassInterface

Instance Method Summary collapse

Instance Method Details

#extract_caller_path(caller_locations) ⇒ String

extracts the (module) class name of the caller e.g. ModuleName::ClassName

Parameters:

  • caller_locations (Array<Thread::Backtrace::Location>)

    array containing the result of caller_locations

Returns:

  • (String)

    full specified constant class name



104
105
106
107
108
109
110
111
112
113
# File 'lib/class_interface.rb', line 104

def extract_caller_path(caller_locations)
  path_elements = []
  caller_locations.each do |el|
    if el.label.start_with?('<class:') || el.label.start_with?('<module:')
      name = el.label.gsub(/\<class:|\<module:/, '')[0...-1]
      path_elements.push name
    end
  end
  path_elements.reverse.join("::")
end

#implements(interface_constant) ⇒ Object

  • ensures self.static methods to be defined (checking parameters count to be equal)

  • ensures instance methods to be defined (checking parameters count to be equal)

  • ensures CONSTANTS to be defined (checking for type of CONSTANTS)

Parameters:

  • interface_constant (Class|String)


19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/class_interface.rb', line 19

def implements(interface_constant)
  implementation_class = extract_caller_path caller_locations(1, 16)
  if Object.const_defined? interface_constant.to_s
    #--------------------------------------------------------------------------
    # check if definitions exist
    #--------------------------------------------------------------------------
    # instance methods
    interface_instance_methods = Object.const_get(interface_constant.to_s).instance_methods false
    implementation_instance_methods = Object.const_get(implementation_class.to_s).instance_methods false
    unimplemented_instance_methods = interface_instance_methods - implementation_instance_methods
    # static methods
    interface_static_methods = Object.const_get(interface_constant.to_s).methods false
    implementation_static_methods = Object.const_get(implementation_class.to_s).methods false
    unimplemented_static_methods = interface_static_methods - implementation_static_methods
    # constants
    interface_constants = Object.const_get(interface_constant.to_s).constants
    implementation_constants = Object.const_get(implementation_class.to_s).constants
    unimplemented_constants = interface_constants - implementation_constants
    if unimplemented_instance_methods.any? || unimplemented_static_methods.any? || unimplemented_constants.any?
      error_message = "\nThe interface '#{interface_constant}' was not completely implemented in class '#{implementation_class}'."
      error_message += "\n  - missing instance methods:\n      #{unimplemented_instance_methods.join(' ')}" if unimplemented_instance_methods.any?
      error_message += "\n  - missing static methods:\n      #{unimplemented_static_methods.join(' ')}" if unimplemented_static_methods.any?
      error_message += "\n  - missing constants:\n      #{unimplemented_constants.join(' ')}" if unimplemented_constants.any?
      raise ClassInterface::ImplementationIncompleteError, error_message
    end

    #--------------------------------------------------------------------------
    # check if method parameters count does match on implementation and interface
    #--------------------------------------------------------------------------
    interface_instance_methods.each do |method_name|
      if_params_count_arity = Object.const_get(interface_constant.to_s).new.method(method_name).arity
      impl_params_count_arity = Object.const_get(implementation_class).new.method(method_name).arity
      if if_params_count_arity != impl_params_count_arity
        raise ClassInterface::ImplementationParameterCountError, "Parameters of instance method '#{implementation_class}##{method_name}' do not match with interface '#{interface_constant}##{method_name}' (given #{impl_params_count_arity.abs}, expected #{if_params_count_arity.abs})"
      end
    end
    interface_static_methods.each do |method_name|
      if_params_count_arity = Object.const_get(interface_constant.to_s).method(method_name).arity
      impl_params_count_arity = Object.const_get(implementation_class).method(method_name).arity
      if if_params_count_arity != impl_params_count_arity
        raise ClassInterface::ImplementationParameterCountError, "Parameters of static method '#{implementation_class}##{method_name}' do not match with interface '#{interface_constant}##{method_name}' (given #{impl_params_count_arity.abs}, expected #{if_params_count_arity.abs})"
      end
    end

    #--------------------------------------------------------------------------
    # check if class types of CONSTANTS do match
    #--------------------------------------------------------------------------
    invalid_const_definitions = []
    if_const_class_type = nil
    interface_constants.each do |const_name|
      if_const_string = "%s::%s" % [interface_constant, const_name]
      if_const_class_type = Object.const_get(if_const_string)
      if_const_class_type = Object.const_get(if_const_string).class unless Object.const_get(if_const_string).class == Class
      invalid_const_definitions += [const_name] unless if_const_class_type.class == Class
      next if if_const_class_type.to_s == 'NilClass'
      impl_const_value_type = Object.const_get("%s::%s" % [implementation_class, const_name]).class
      ruby_version_lt_24 = (1.class.to_s == 'Fixnum')
      ruby_version_gte_24 = (1.class.to_s == 'Integer')
      # Ruby < 2.4
      if ruby_version_lt_24 && if_const_class_type.to_s == 'Numeric' && %w[Fixnum Float Bignum].include?(impl_const_value_type.to_s)
        # we are fine, specific case for numbers
      # Ruby => 2.4
      elsif ruby_version_gte_24 && if_const_class_type.to_s == 'Numeric' && %w[Integer Float].include?(impl_const_value_type.to_s)
        # we are fine, specific case for numbers
      elsif ruby_version_lt_24 && ((if_const_class_type.to_s == 'Bignum' && impl_const_value_type.to_s == 'Integer') ||
          (if_const_class_type.to_s == 'Fixnum' && impl_const_value_type.to_s == 'Integer') ||
          (if_const_class_type.to_s == 'Integer' && impl_const_value_type.to_s == 'Fixnum') ||
          (if_const_class_type.to_s == 'Integer' && impl_const_value_type.to_s == 'Bignum'))
        # Ruby 2.4 unifies Fixnum and Bignum into Integer
      elsif if_const_class_type != impl_const_value_type
        raise ClassInterface::ImplementationConstantTypeError, "Value type of constant '#{implementation_class}::#{const_name}' does not match interface '#{if_const_string}'. (#{impl_const_value_type} given, #{if_const_class_type} expected)"
      end
    end
    raise ClassInterface::InterfaceConstantDefinitionError, "Value of constant(s) '#{invalid_const_definitions.join(" ")}' of interface '#{interface_constant}' must be a class constant or nil" if invalid_const_definitions.any?
  else
    raise ClassInterface::InterfaceNotDefinedError, "Interface '#{interface_constant}' is not defined"
  end
end