1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 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
|
# frozen_string_literal: true
module Contracts
module Engine
# Contracts engine
class Base
# Enable contracts engine for klass
#
# @param [Class] klass - target class
def self.apply(klass)
Engine::Target.new(klass).apply
end
# Returns true if klass has contracts engine
#
# @param [Class] klass - target class
# @return [Bool]
def self.applied?(klass)
Engine::Target.new(klass).applied?
end
# Fetches contracts engine out of klass
#
# @param [Class] klass - target class
# @return [Engine::Base or Engine::Eigenclass]
def self.fetch_from(klass)
Engine::Target.new(klass).engine
end
# Creates new instance of contracts engine
#
# @param [Class] klass - class that owns this engine
def initialize(klass)
@klass = klass
end
# Adds provided decorator to the engine
# It validates that decorator can be added to this engine at the
# moment
#
# @param [Decorator:Class] decorator_class
# @param args - arguments for decorator
def decorate(decorator_class, *args)
validate!
decorators << [decorator_class, args]
end
# Sets eigenclass' owner to klass
def set_eigenclass_owner
eigenclass_engine.owner_class = klass
end
# Fetches all accumulated decorators (both this engine and
# corresponding eigenclass' engine)
# It clears all accumulated decorators
#
# @return [ArrayOf[Decorator]]
def all_decorators
pop_decorators + eigenclass_engine.all_decorators
end
# Fetches decorators of specified type for method with name
#
# @param [Or[:class_methods, :instance_methods]] type - method type
# @param [Symbol] name - method name
# @return [ArrayOf[Decorator]]
def decorated_methods_for(type, name)
Array(decorated_methods[type][name])
end
# Returns true if there are any decorated methods
#
# @return [Bool]
def decorated_methods?
!decorated_methods[:class_methods].empty? ||
!decorated_methods[:instance_methods].empty?
end
# Adds method decorator
#
# @param [Or[:class_methods, :instance_methods]] type - method type
# @param [Symbol] name - method name
# @param [Decorator] decorator - method decorator
def add_method_decorator(type, name, decorator)
decorated_methods[type][name] ||= []
decorated_methods[type][name] << decorator
end
# Returns nearest ancestor's engine that has decorated methods
#
# @return [Engine::Base or Engine::Eigenclass]
def nearest_decorated_ancestor
current = klass
current_engine = self
ancestors = current.ancestors[1..]
while current && current_engine && !current_engine.decorated_methods?
current = ancestors.shift
current_engine = Engine.fetch_from(current)
end
current_engine
end
private
attr_reader :klass
def decorated_methods
@_decorated_methods ||= { :class_methods => {}, :instance_methods => {} }
end
# No-op because it is safe to add decorators to normal classes
def validate!; end
def pop_decorators
decorators.tap { clear_decorators }
end
def eigenclass
Support.eigenclass_of(klass)
end
def eigenclass_engine
Eigenclass.lift(eigenclass, klass)
end
def decorators
@_decorators ||= []
end
def clear_decorators
@_decorators = []
end
end
end
end
|