File: base.rb

package info (click to toggle)
ruby-contracts 0.17-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 624 kB
  • sloc: ruby: 3,805; makefile: 4; sh: 2
file content (137 lines) | stat: -rw-r--r-- 3,716 bytes parent folder | download | duplicates (2)
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